← All courses
DevOps · Free course

Performance & Profiling

Fast apps keep users — and at scale, performance is an architecture problem, not an afterthought. This course teaches you to measure, profile and tune Wisej.NET applications so sessions stay quick under load. You'll learn to find bottlenecks with real profiling, understand where server time and memory go per session, and apply the techniques that keep a busy Wisej.NET app responsive at scale.

Measure, profile and tune Wisej.NET apps — find bottlenecks and keep sessions fast at scale.

Start this free course

Also available in: DeutschFrançaisItalianoEspañol

Curriculum

Module 1: The Wisej.NET Performance Model

Module 1 of Performance & Profiling — measure, profile and tune Wisej.NET apps so sessions stay fast at scale. Read the lesson guide and the lab / exam guide, watch the performance model walkthrough, pass the knowledge check, then complete the hands-on lab in WisejPerfLab.

  1. ReadingLesson Guide · 14 min

    The request and event cycle, WebSocket push, session state as the unit of capacity, the five cost buckets, and the scenario-plus-baseline discipline that every later trace is compared against. What this means for a Wisej.NET developer diagnosing bottlenecks in a production app — and how to read this module.

    Read the lesson guide (PDF)

  2. ReadingLab / Exam Guide · 10 min

    What you'll build in the hands-on lab, the suggested approach, and the required deliverables.

    Read the lesson guide (PDF)

  3. Video lessonWhere the time really goes in a Wisej.NET session · 14 min

    Where the time really goes in a Wisej.NET session — a guided Module 1 walkthrough, built step by step in WisejPerfLab. Runs right here in the player.

    Narration transcript

    Begin with the slow refresh in WisejPerfLab. Its elapsed time establishes the problem, but a useful investigation must explain where that time went before choosing a fix.

    Follow the click from the browser to the server and back. Server controls consume memory, while changing them also creates processing, transport, and rendering work.

    Separate computation, waiting, memory, data access, and transmitted updates. This classification determines which measurement can explain the delay; a processor trace cannot reveal every browser cost.

    Add a ScenarioProbe around the action. A disposable scope records completion on every exit path, and named duration and row-count fields make separate runs comparable.

    Count the objects retained by each session, including controls and cached rows. A small leak becomes a capacity problem when every connected session keeps another copy.

    Measure the query and the interface changes together. Otherwise, the recorded duration omits part of the work the user actually waits for during the refresh.

    Record refresh, search, and tree expansion separately on the warmed application. These single-session timings are reference points; they do not yet explain behavior under concurrent use.

    Write down the build, dataset, and warm-up alongside each baseline. Give every scenario a target and a measurement tool so later improvements can be evaluated consistently.

    Instrument all three lab scenarios before optimizing them. The baseline and budget become the evidence used by subsequent modules to distinguish an improvement from an unexplained timing change.

  4. ReadingAI Coding Exercise · 20 min

    Build this module's OpsMonitor sample with ChatGPT or Claude from a ready-made prompt that points the model at this module's specification, then review, run and extend what it gives you — including against our own reference build on GitHub.

    Read the lesson guide (PDF)

  5. Knowledge checkModule 1 Knowledge Check · 10 min · Pass mark 80%
  6. Hands-on labLab — Scenarios, Probes and the First Budget · 45 min

    Objective: Create the WisejPerfLab solution with a support dashboard, a ticket grid and a customer tree fed by seeded data, then make it measurable. Add a ScenarioProbe service that wraps a scenario in a Stopwatch scope and writes structured PERF start/end logs with the scenario name, the user action, the elapsed milliseconds and the row count, register it in the host builder, and wrap three scenarios in it: dashboard refresh, ticket search and customer tree expand. Run each scenario once to warm up, then record the baseline and write a performance budget that states the environment, build configuration, dataset size and an acceptance threshold per scenario. Deliverables: WisejPerfLab solution with a dashboard, ticket grid and customer tree over seeded data; ScenarioProbe service writing structured PERF start/end logs with elapsed milliseconds and row counts; Three instrumented scenarios: dashboard refresh, ticket search and customer tree expand; Baseline note recording environment, build configuration, dataset size and warm-up procedure; Performance budget with an acceptance threshold per scenario and the tool that would prove each one.

Module 2: Visual Studio Profiling Workflow for Wisej.NET

Module 2 of Performance & Profiling — measure, profile and tune Wisej.NET apps so sessions stay fast at scale. Read the lesson guide and the lab / exam guide, watch the profiling workflow walkthrough, pass the knowledge check, then complete the hands-on lab in WisejPerfLab.

  1. ReadingLesson Guide · 14 min

    Running the Performance Profiler against a Wisej.NET app the right way: Release builds without the debugger, the tool selection matrix, attaching to dotnet or w3wp, and reading the summary timeline, call tree, hot path and caller/callee views. What this means for a Wisej.NET developer diagnosing bottlenecks in a production app — and how to read this module.

    Read the lesson guide (PDF)

  2. ReadingLab / Exam Guide · 10 min

    What you'll build in the hands-on lab, the suggested approach, and the required deliverables.

    Read the lesson guide (PDF)

  3. Video lessonCollect a trace that actually proves something · 14 min

    Collect a trace that actually proves something — a guided Module 2 walkthrough, built step by step in WisejPerfLab. Runs right here in the player.

    Narration transcript

    A profiling capture is useful only when it represents a known experiment. Keep the build, scenario, and selected tool explicit so another developer can interpret the result.

    Warm a Release build, record one action, then stop. Excluding startup and unrelated activity prevents their costs from being mistaken for the behavior you are investigating.

    Choose the profiler from the suspected cost. Sampling explains computation; waiting, allocations, database access, and file operations need different views to expose their contribution.

    Prepare the trace note before recording. Hosting, dataset, browser, and budget explain the conditions behind the samples and make the capture reproducible later.

    Attach to the process actually serving the application. Confirm its identifier, especially when several sites or application pools create processes with the same executable name.

    Select the click interval before following the hot path. Large inclusive time in dispatch code differs from self time in FormatRow, where the processor work actually occurs.

    Repeat the same ordered workflow for every capture. On Internet Information Services, selecting the wrong worker process invalidates the measurements before analysis even begins.

    Compare sampling with instrumentation for the same refresh. Formatter calls explain computation, while the separate wait shows why a processor-only investigation would leave part of the delay unexplained.

    Save both refresh traces with their notes. State a suspected cause and evidence that would disprove it, so the next change tests a hypothesis rather than a guess.

  4. ReadingAI Coding Exercise · 20 min

    Build this module's OpsMonitor sample with ChatGPT or Claude from a ready-made prompt that points the model at this module's specification, then review, run and extend what it gives you — including against our own reference build on GitHub.

    Read the lesson guide (PDF)

  5. Knowledge checkModule 2 Knowledge Check · 10 min · Pass mark 80%
  6. Hands-on labLab — CPU Usage and Instrumentation Traces · 45 min

    Objective: Profile the slow WisejPerfLab dashboard refresh properly. Switch to Release, warm the app up once, then collect a CPU Usage trace of that one scenario with Alt+F2, narrow the summary timeline to the click itself and use Show Hot Path to find the top path below the Wisej.NET dispatch frames. Collect a second Instrumentation trace of the same scenario to get call counts and wall-clock time, decide from the two traces whether the cost is CPU inside your code or time spent waiting, and save both .diagsession files with a name that carries the module, scenario, build, dataset and timestamp. Deliverables: Release CPU Usage trace of the dashboard refresh scenario, timeline narrowed to the click; Hot path screenshot naming the top application function and its self versus total CPU; Instrumentation trace of the same scenario with call counts and wall-clock time; Trace note stating course, module, scenario, build, hosting, tool, browser and expected budget; One-paragraph suspected root cause that says which of the five buckets the evidence points at.

Module 3: CPU Hot Paths, Event Handlers and Server-Side UI Work

Module 3 of Performance & Profiling — measure, profile and tune Wisej.NET apps so sessions stay fast at scale. Read the lesson guide and the lab / exam guide, watch the CPU hot path walkthrough, pass the knowledge check, then complete the hands-on lab in WisejPerfLab.

  1. ReadingLesson Guide · 14 min

    Expensive server-side UI code in event handlers, timers, layout refreshes and formatting logic, and the caching, batching and view-model patterns that make a click cheap again. What this means for a Wisej.NET developer diagnosing bottlenecks in a production app — and how to read this module.

    Read the lesson guide (PDF)

  2. ReadingLab / Exam Guide · 10 min

    What you'll build in the hands-on lab, the suggested approach, and the required deliverables.

    Read the lesson guide (PDF)

  3. Video lessonMake the Refresh button cheap · 14 min

    Make the Refresh button cheap — a guided Module 3 walkthrough, built step by step in WisejPerfLab. Runs right here in the player.

    Narration transcript

    Start with the measured offenders: repeated row formatting and rebuilding the indicator panel. Their call patterns tell us which work to remove from the user's click.

    Look for otherwise reasonable operations repeated inside frequent events. Formatting, image conversion, reflection, and sorting become expensive when their placement multiplies the work.

    Distinguish too many calls from too much work per call. Reducing frequency addresses the first problem; making one operation cheaper addresses the second.

    Inspect the handler before changing it. Rebuilding controls and formatting all rows introduce different costs, so a single cosmetic rewrite would leave the underlying behavior intact.

    Precompute stable results, batch related changes, and update existing controls. Cache only immutable data; moving computation into an asynchronous method does not remove that computation.

    Move formatting into GetSnapshot and let the handler assign the results. Keep the original measurement scope and restore the button in finally so failures leave the interface usable.

    Repeat the same capture and inspect calls, not just elapsed time. The formatter disappearing from the hot path demonstrates that the intended work was actually removed.

    The refresh now meets its budget, but document snapshot staleness as a tradeoff. The separately identified wait remains a different problem for a later investigation.

    Replace both expensive patterns in the lab and submit comparable measurements. Your evidence should connect the snapshot and batched handler to the changed call counts.

  4. ReadingAI Coding Exercise · 20 min

    Build this module's OpsMonitor sample with ChatGPT or Claude from a ready-made prompt that points the model at this module's specification, then review, run and extend what it gives you — including against our own reference build on GitHub.

    Read the lesson guide (PDF)

  5. Knowledge checkModule 3 Knowledge Check · 10 min · Pass mark 80%
  6. Hands-on labLab — Hot Path to Cached View Model · 45 min

    Objective: Fix the WisejPerfLab Refresh button. Profile the refresh scenario with CPU Usage, open the flame graph and Show Hot Path, and identify the two offenders: a formatter that runs per cell on every refresh and a control rebuild that recreates the KPI panel each time. Replace them with a DashboardSnapshot view model whose display strings are computed once in the service, mutate the labels and the chart data source once inside a probe scope instead of row by row, and keep the button disabled in a try/finally while the work runs. Rerun the identical scenario and capture the new hot path. Deliverables: Before CPU Usage trace naming the repeated formatter and the control rebuild with their total and self CPU; DashboardSnapshot view model with display strings precomputed in the service; Batched refresh handler that mutates controls once inside a probe scope with try/finally re-enable; After CPU Usage trace of the identical scenario with the new hot path; Before/after table of total CPU, top function and call count, plus the remaining risk.

Module 4: Memory, Allocations and Session Leaks

Module 4 of Performance & Profiling — measure, profile and tune Wisej.NET apps so sessions stay fast at scale. Read the lesson guide and the lab / exam guide, watch the memory and leaks walkthrough, pass the knowledge check, then complete the hands-on lab in WisejPerfLab.

  1. ReadingLesson Guide · 14 min

    Allocation pressure versus retained memory, the snapshot-compare workflow, the Wisej.NET retention patterns (static events, timers, background tasks, global caches) and the disposal checklist that proves a leak is gone. What this means for a Wisej.NET developer diagnosing bottlenecks in a production app — and how to read this module.

    Read the lesson guide (PDF)

  2. ReadingLab / Exam Guide · 10 min

    What you'll build in the hands-on lab, the suggested approach, and the required deliverables.

    Read the lesson guide (PDF)

  3. Video lessonFind the form that never went away · 14 min

    Find the form that never went away — a guided Module 4 walkthrough, built step by step in WisejPerfLab. Runs right here in the player.

    Narration transcript

    A fast refresh does not prove healthy memory use. Repeatedly opening and closing a form reveals whether objects disappear when their visible lifetime ends.

    Measure allocation and retention separately. Temporary objects create collection work, while objects that remain reachable consume capacity for as long as their references survive.

    Take a warm baseline snapshot, repeat fifty cycles, return to idle, and compare. Repetition makes persistent growth easier to distinguish from ordinary temporary allocations.

    Follow the references from the surviving forms. A static event and a timer callback both keep the form reachable even after its window closes.

    Inspect events, timers, tasks, caches, fields, and bindings for references with longer lifetimes. Cleanup must release ownership when the session or form no longer needs them.

    Put unsubscription and disposal beside the form's lifetime management. Checking IsDisposed prevents invalid work, but it does not release the reference keeping that form alive.

    Compare search allocations after projecting the data once. The reduced string volume demonstrates less repeated work during redraws, independently of whether any object leaked.

    Repeat the same fifty cycles after cleanup. Confirm both the retained form count and the reference path disappear; either check alone leaves the explanation incomplete.

    Submit allocation and retention evidence separately. Use the resulting per-session memory figure to establish a capacity budget that later deployment checks can enforce.

  4. ReadingAI Coding Exercise · 20 min

    Build this module's OpsMonitor sample with ChatGPT or Claude from a ready-made prompt that points the model at this module's specification, then review, run and extend what it gives you — including against our own reference build on GitHub.

    Read the lesson guide (PDF)

  5. Knowledge checkModule 4 Knowledge Check · 10 min · Pass mark 80%
  6. Hands-on labLab — Allocation Pressure and Retention Roots · 45 min

    Objective: Attack both halves of the memory problem in WisejPerfLab. Run the ticket search scenario under .NET Object Allocation and find the row models and strings allocated per search, then reduce them by projecting once instead of rebuilding objects per redraw. Next take a Memory Usage snapshot after warm-up, open and close the ticket detail form fifty times, take a second snapshot and compare: the retained forms are rooted by a static GlobalTicketBus.TicketChanged subscription and a refresh timer that was never stopped. Unsubscribe and stop both in an overridden Dispose, clear the BindingSource data source, then rerun the open/close scenario and show the retained heap returning close to snapshot A. Deliverables: Allocation trace naming the top allocating type and call path for the search scenario, before and after; Snapshot A/B comparison showing the retained detail form instance count before the fix; Path-to-root screenshot identifying the static event subscription and the live timer; Dispose override that unsubscribes the static event, stops and disposes the timer and clears the binding; Per-session memory budget and a disposal checklist, with the after-snapshot proving the retention path is gone.

Module 5: Data Controls, Browser Payloads and Large UI Surfaces

Module 5 of Performance & Profiling — measure, profile and tune Wisej.NET apps so sessions stay fast at scale. Read the lesson guide and the lab / exam guide, watch the large UI surfaces walkthrough, pass the knowledge check, then complete the hands-on lab in WisejPerfLab.

  1. ReadingLesson Guide · 14 min

    Grids, lists, trees and repeaters at scale: virtual mode, paging, projection and lazy node loading, and using browser network evidence next to Visual Studio evidence to size the update payload. What this means for a Wisej.NET developer diagnosing bottlenecks in a production app — and how to read this module.

    Read the lesson guide (PDF)

  2. ReadingLab / Exam Guide · 10 min

    What you'll build in the hands-on lab, the suggested approach, and the required deliverables.

    Read the lesson guide (PDF)

  3. Video lessonFifty thousand rows without fifty thousand controls · 14 min

    Fifty thousand rows without fifty thousand controls — a guided Module 5 walkthrough, built step by step in WisejPerfLab. Runs right here in the player.

    Narration transcript

    Module five. Large grids and trees can make an application slow before the user does anything. We will reduce the data and controls loaded at once, then measure the effect on both the server and the browser.

    A large grid has costs in four places. The server stores the objects and prepares the update. The network carries it, and the browser renders it. Measuring server performance alone will miss part of the problem.

    First, project each record into a smaller object containing the fields the screen needs. Then use virtual mode to avoid keeping every row in memory. These changes solve different parts of the problem, so apply both.

    Set RowCount from a count query. When CellValueNeeded requests a value, read it from the paged projection. Avoid a database query for every cell, and keep formatting out of this handler.

    Apply the same principle to other controls. Load tree children when their parent expands, virtualize long lists, keep repeater templates simple, and update only the dashboard rows that changed.

    While the tree node is collapsed, keep a count and one placeholder child. The placeholder preserves the expand control. Create the real child nodes only when the user opens that branch.

    Compare the server measurements. The grid holds forty row objects instead of fifty thousand. Memory falls from 186 to nine megabytes, and processing time drops from 1,410 to 95 milliseconds. Next, check what changed in the browser.

    Now inspect the browser traffic. The grid update shrinks from 4.8 megabytes to 96 kilobytes. Six smaller updates serve later scrolling. Report both the number and size of requests so the measurements show the complete cost.

    For lab five, project and virtualize the grid, then load tree branches on demand. Use Visual Studio to measure the server and the network panel to measure the browser traffic. Your evidence should cover both.

  4. ReadingAI Coding Exercise · 20 min

    Build this module's OpsMonitor sample with ChatGPT or Claude from a ready-made prompt that points the model at this module's specification, then review, run and extend what it gives you — including against our own reference build on GitHub.

    Read the lesson guide (PDF)

  5. Knowledge checkModule 5 Knowledge Check · 10 min · Pass mark 80%
  6. Hands-on labLab — Virtual Grid and Lazy Tree · 45 min

    Objective: Shrink the two largest surfaces in WisejPerfLab. The ticket grid binds 50,000 full entities with navigation properties: replace the binding with a TicketGridRow projection of just the visible columns, set VirtualMode and RowCount and serve cells from a CellValueNeeded handler backed by a paged query service. The customer tree builds every node at startup: load children on expand instead, using a count placeholder for collapsed branches. Measure each screen before and after with CPU Usage and .NET Object Allocation on the server and the browser network panel for update payload size. Deliverables: TicketGridRow projection bound instead of full entities, with only the displayed columns; Virtual-mode grid with RowCount and a CellValueNeeded handler served from a paged query service; Customer tree loading child nodes on expand with a count placeholder for collapsed branches; Before/after CPU and allocation numbers for the grid load and the tree expand scenarios; Browser network evidence comparing update payload size before and after, with the caveat about compression.

Module 6: Database, File I/O, Async and External Waits

Module 6 of Performance & Profiling — measure, profile and tune Wisej.NET apps so sessions stay fast at scale. Read the lesson guide and the lab / exam guide, watch the database and async walkthrough, pass the knowledge check, then complete the hands-on lab in WisejPerfLab.

  1. ReadingLesson Guide · 14 min

    The latency that CPU Usage cannot see: the Database tool for query timings and N+1 patterns, File I/O for import and export work, and .NET Async for chains that are blocked or accidentally sequential. What this means for a Wisej.NET developer diagnosing bottlenecks in a production app — and how to read this module.

    Read the lesson guide (PDF)

  2. ReadingLab / Exam Guide · 10 min

    What you'll build in the hands-on lab, the suggested approach, and the required deliverables.

    Read the lesson guide (PDF)

  3. Video lessonLow CPU, slow screen: profiling the wait · 14 min

    Low CPU, slow screen: profiling the wait — a guided Module 6 walkthrough, built step by step in WisejPerfLab. Runs right here in the player.

    Narration transcript

    A quiet processor can accompany a slow application. Use the earlier refresh trace to investigate waiting instead of trying to optimize computation that does not explain the delay.

    Attribute the missing time to database operations, file access, and blocked threads. These waits require their own measurements because processor samples mainly describe work performed while executing.

    Inspect how the screen obtains its rows. Repeated lookups and excess fields add avoidable database work; projecting the required fields together can remove several problems at once.

    Join the customer name into the query and select only displayed fields. Order and page the results, using untracked reads when the screen does not need change tracking.

    Await input and output operations so a waiting request does not occupy a thread unnecessarily. This improves resource availability; it does not make the underlying database or disk faster.

    Remove blocking result access and stream the export instead of issuing many tiny writes. Send progress at meaningful intervals so feedback does not become another source of overhead.

    Compare query counts after the change. Dropping from two hundred and one queries to two confirms this correction, even if another cost still prevents the scenario meeting its budget.

    Repeat the export with concurrent sessions. Blocking that seems harmless for one user can exhaust available threads and delay unrelated screens when many users perform it together.

    Connect each query to its interface action, then compare before and after. Explain both the removed database work and how the export avoids blocking the click path.

  4. ReadingAI Coding Exercise · 20 min

    Build this module's OpsMonitor sample with ChatGPT or Claude from a ready-made prompt that points the model at this module's specification, then review, run and extend what it gives you — including against our own reference build on GitHub.

    Read the lesson guide (PDF)

  5. Knowledge checkModule 6 Knowledge Check · 10 min · Pass mark 80%
  6. Hands-on labLab — Query Traces and the Blocked Export · 45 min

    Objective: Profile the two wait-heavy scenarios in WisejPerfLab. Run the ticket search under the Database tool and match each query to the UI action: the grid load issues one customer query per displayed row and the details query pulls large text columns nobody shows. Replace both with a single no-tracking projection query that selects only the grid columns and pages the result. Then run the CSV export under File I/O and .NET Async, find the blocking .Result call in the click handler and the repeated small writes, and move generation into an Application.StartTask background workflow that streams the file and reports throttled progress instead of pushing every step. Deliverables: Database trace matching queries to UI actions, with the N+1 query count before the fix; Single no-tracking projection query with paging replacing the per-row and over-fetching queries; File I/O and .NET Async evidence for the export, naming the blocking call and the write pattern; Export moved to a background task that streams output and pushes progress only at meaningful steps; Before/after query count, query duration and export wall-clock time, with remaining risks.

Module 7: Scale, Health Checks, Load Balancing and Final Tuning

Module 7 of Performance & Profiling — measure, profile and tune Wisej.NET apps so sessions stay fast at scale. Read the lesson guide and the lab / exam guide, watch the scale and health-check walkthrough, pass the knowledge check, then complete the hands-on lab in WisejPerfLab.

  1. ReadingLesson Guide · 14 min

    Turning measurements into a capacity model, HealthCheck.json thresholds, session affinity and WebSocket-aware load balancing, production counters, and the final performance report that defends every change. What this means for a Wisej.NET developer diagnosing bottlenecks in a production app — and how to read this module.

    Read the lesson guide (PDF)

  2. ReadingLab / Exam Guide · 10 min

    What you'll build in the hands-on lab, the suggested approach, and the required deliverables.

    Read the lesson guide (PDF)

  3. Video lessonFrom a local trace to a capacity model · 14 min

    From a local trace to a capacity model — a guided Module 7 walkthrough, built step by step in WisejPerfLab. Runs right here in the player.

    Narration transcript

    Translate the profiling results into operating limits. A faster application still needs a defensible session capacity and a plan for directing new users away from a full server.

    Multiply measured session memory by the proposed session count and compare it with usable memory. Keep headroom because idle sessions do not describe every peak allocation.

    Preserve session affinity across the deployment and allow WebSocket upgrades through proxies. Stateful controls need the correct server, and live updates need an uninterrupted transport path.

    Set health thresholds from measured capacity rather than convenient guesses. The unhealthy response and retry guidance give infrastructure a signal when the instance should stop receiving new sessions.

    Monitor the signal that corresponds to each corrected problem. Scenario durations, managed heap size, and thread-pool queues reveal different regressions and should not be treated as interchangeable.

    Write a report that connects the original scenario to its cause, correction, and evidence. Include risks and operational checks so a colleague can verify the improvement after deployment.

    Watch what happens when instance A reaches capacity. New traffic moves toward instance B while established sessions continue, separating admission control from disruption of existing users.

    Compare every scenario against its original budget. Keep the export marked as a risk because an absent target and limited concurrency testing cannot support a passing claim.

    Deliver the capacity calculation, health configuration, deployment notes, and reproducible report together. Operations needs both the chosen limits and the evidence explaining why those limits are appropriate.

  4. ReadingAI Coding Exercise · 20 min

    Build this module's OpsMonitor sample with ChatGPT or Claude from a ready-made prompt that points the model at this module's specification, then review, run and extend what it gives you — including against our own reference build on GitHub.

    Read the lesson guide (PDF)

  5. Knowledge checkModule 7 Knowledge Check · 10 min · Pass mark 80%
  6. Hands-on labLab — Capacity Model and Capstone Report · 45 min

    Objective: Deliver the WisejPerfLab capstone. Derive a capacity model from your own measurements: memory retained per idle session, CPU per common scenario, expected peak concurrency and the headroom you are keeping. Write a HealthCheck.json whose maxSessions, maxMemory and maxCPU come from those numbers and explain each threshold, returning 503 with a retry-after so a load balancer routes new users elsewhere while existing sessions keep running. Write the deployment notes for two instances behind a load balancer with session affinity and WebSocket support, and assemble the final report: scenarios, environment, baseline evidence, root causes, changes, after evidence, remaining risks and the production counters that will show if a regression returns. Deliverables: Capacity model deriving sessions per server from measured per-session memory and per-scenario CPU; HealthCheck.json with maxSessions, maxMemory, maxCPU, return code and retry-after, each threshold justified; Deployment notes covering session affinity, WebSocket support and the polling fallback; Final performance report with baseline, root cause, change and after-evidence for every module fix; Monitoring plan naming the counters, thresholds and alerts that would catch each regression.