1function Invoke-Stress($app, $section, $command, $output, $shared) {
2 $hierarchy = ''
3 $app.GetHierarchy($section, 4, [ref]$hierarchy, 1)
4 [IO.File]::WriteAllText("$output\hierarchy.xml", $hierarchy, [Text.Encoding]::UTF8)
5 [xml]$tree = $hierarchy
6 $pages = @($tree.SelectNodes('//*[local-name()="Page"]'))
7 if ($pages.Count -ne 1) { throw 'Expected one stress-test page.' }
8 $pageId = $pages[0].GetAttribute('ID')
9 if ($command.action -eq 'prepare-stress') {
10 for ($i = 0; $i -lt $command.clients; $i++) {
11 $xml = '<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="' + $pageId + '"><one:Outline><one:Position x="48" y="' + (150 + 75 * $i) + '" z="' + ($i + 1) + '"/><one:OEChildren><one:OE><one:T><![CDATA[Native ' + $i + ':]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>'
12 $app.UpdatePageContent($xml, [DateTime]::MinValue, 1, $false)
13 }
14 return
15 }
16 $random = New-Object Random([int]$command.seed)
17 $events = New-Object IO.StreamWriter("$output\events.jsonl", $false, [Text.Encoding]::UTF8)
18 $events.AutoFlush = $true
19 try {
20 [IO.File]::WriteAllText("$output\ready", 'ready')
21 $deadline = [DateTime]::UtcNow.AddMinutes(10)
22 while (-not (Test-Path "$shared\stress-start")) {
23 if ([DateTime]::UtcNow -gt $deadline) { throw 'Stress start timed out.' }
24 Start-Sleep -Milliseconds 20
25 }
26 for ($i = 0; $i -lt $command.operations; $i++) {
27 if (($command.PSObject.Properties.Name -contains 'maintenance') -and $command.maintenance -and $i -eq [Math]::Floor($command.operations / 2)) {
28 [IO.File]::WriteAllText("$shared\maintenance-paused-n$($command.actor)", 'paused')
29 while (-not (Test-Path "$shared\maintenance-resume")) {
30 if ([DateTime]::UtcNow -gt $deadline) { throw 'Maintenance pause timed out.' }
31 Start-Sleep -Milliseconds 100
32 }
33 }
34 Start-Sleep -Milliseconds $random.Next(10, 100)
35 $started = [DateTime]::UtcNow.Ticks
36 $content = ''
37 $app.GetPageContent($pageId, [ref]$content, 1, 1)
38 [xml]$page = $content
39 $matches = @($page.SelectNodes('//*[local-name()="T"]') | Where-Object {
40 (Get-ParagraphText $_.InnerText).StartsWith($command.prefix)
41 })
42 if ($matches.Count -ne 1) { throw 'Expected one stress-test paragraph.' }
43 $before = Get-ParagraphText $matches[0].InnerText
44 $token = ' [n' + $command.actor + ':' + $i + ']'
45 $start = $end = $before.Length
46 $replacement = $token
47 $bold = $italic = $false
48 if ($command.edit) {
49 $boundaries = New-Object 'Collections.Generic.List[int]'
50 for ($at = $command.prefix.Length; $at -le $before.Length; $at++) {
51 $boundaries.Add($at)
52 if ($at -lt $before.Length -and [char]::IsHighSurrogate($before[$at])) { $at++ }
53 }
54 $first = $boundaries[$random.Next($boundaries.Count)]
55 $second = $boundaries[$random.Next($boundaries.Count)]
56 $start = [Math]::Min($first, $second)
57 $end = [Math]::Max($first, $second)
58 $replacement = ' caf' + [char]0xe9 + ' ' + [char]::ConvertFromUtf32(0x1f980) + $token
59 $bold = $random.Next(2) -eq 1
60 $italic = $random.Next(2) -eq 1
61 }
62 $after = $before.Substring(0, $start) + $replacement + $before.Substring($end)
63 $html = [Security.SecurityElement]::Escape($after)
64 if ($command.edit) {
65 $weight = if ($bold) { 'bold' } else { 'normal' }
66 $style = if ($italic) { 'italic' } else { 'normal' }
67 $html = '<span style="font-weight:' + $weight + ';font-style:' + $style + '">' + $html + '</span>'
68 }
69 $matches[0].InnerText = $html
70 Keep-Container $page $matches[0]
71 $updateStarted = [DateTime]::UtcNow.Ticks
72 $app.UpdatePageContent($page.OuterXml, [DateTime]::MinValue, 1, $false)
73 $updated = [DateTime]::UtcNow.Ticks
74 $events.WriteLine((@{ operation=$i; before=$before; token=$token; started_ticks=$started;
75 range=@($start, $end); replacement=$replacement; bold=$bold; italic=$italic;
76 update_started_ticks=$updateStarted; updated_ticks=$updated } | ConvertTo-Json -Compress))
77 if ($i -eq 0) { [IO.File]::WriteAllText("$output\editing", 'editing') }
78 if ($command.sync_every -gt 0 -and (($i + 1) % $command.sync_every) -eq 0) {
79 $app.SyncHierarchy($section)
80 }
81 }
82 } finally { $events.Dispose() }
83}