Skip to main content

The Wisej.NET Performance Model

This example is the WisejPerfLab support application with a dashboard, a ticket grid and a customer tree over a seeded dataset of 50,000 tickets and 3,200 customer nodes, made measurable by a ScenarioProbe that wraps each named scenario in a Stopwatch scope and writes structured PERF start / PERF end records.

Nothing in this module is optimised. That is the point: this is the before the whole course is measured against, and every later module starts from the version in the previous folder. The three deliverables are in docs/: Scenarios.md, Baseline.md and Budget.md.

Run it

cd "LearnWisej-Samples/Performance and Profiling Course/Module 1/WisejPerfLab"
dotnet run -f net10.0 --urls http://localhost:5801

Measurements are taken in Release, and so are the numbers in docs/Baseline.md:

dotnet run -c Release -f net10.0 --urls http://localhost:5801

The first start creates and seeds WisejPerfLab/App_Data/perflab.db (about 60 MB, half a second) and prints the row counts. Delete that folder to reseed; the seed is deterministic, so the rows come back identical. The app header shows the dataset and the build configuration it is running under — if it says Debug (measure in Release), the numbers on screen are not baseline material.

What to click

ActionWhat you should see
Warm up (discarded run)the three scenarios run once, bracketed in the PERF log by "warm-up run — the three records below are discarded"
Run the three scenarios ×3nine runs, then three median … lines and a status line with all three medians
Dashboard → RefreshKPIs and the chart repaint; the chip says Refresh 476 ms — over the 250 ms budget rows=50,000
Tickets → Search tickets5,000 rows, and the status line admits 5,001 SQL statements
Tickets → Redrawthe same rows rebuilt with no query at all — the allocation-only scenario Module 4 attacks
Tickets → Export CSVthe file name appears after about half a second; the progress bar never moves, because the click handler blocked on .Result and nothing reaches the browser until it returns
Tickets → double-click a rowthe ticket detail form, showing bus subscribers: 1 timer: running — its two retention roots, on screen
Customers → Load the customer tree3,200 nodes and 3,201 SQL statements, built before you open anything
Customers → Expand the first branchone branch opens, and another 3,201 statements are issued to do it
Break the database then any scenarioa red banner, a fault status, an emptied grid — and a PERF end … failed=DatabaseUnavailableException record with the time spent before the failure
Restorethe next run succeeds again
Clear logthe PERF buffer of this session empties

Open a second browser tab to see that each session gets its own PERF log: the buffer lives in Application.Session, not in a static field.

Where things live

WisejPerfLab/
Startup.cs host builder: SQLite, the probe, the services, the Wisej DI bridge, seeding
Program.cs Wisej session entry point (one MainPage per session)
MainPage.cs / .Designer.cs shell: three tabs, the PERF log card, warm-up / ×3 / break / restore
Diagnostics/
ScenarioProbe.cs Measure(scenario, userAction, rows) -> IDisposable scope ← the deliverable
PerfRecord.cs one PERF line
PerfLogBuffer.cs this session's records + MedianElapsed()
SessionPerfLog.cs finds the buffer in Application.Session
Shell/
PerfBudget.cs docs/Budget.md as code: threshold + tool per scenario
IPerfLabShell.cs status line and failure banner
IScenarioPage.cs a screen that owns one measured scenario
Pages/
DashboardPage.cs Dashboard/Refresh — load everything, format per row, rebuild the KPI panel
TicketGridPage.cs Tickets/Search, /Redraw, /Export
CustomerTreePage.cs Customers/LoadTree, /ExpandNode
Forms/TicketDetailForm.cs the detail form, with the leaks Module 4 will find
Services/ DashboardService, TicketSearchService, CustomerTreeService,
ExportService, TicketFormatter, GlobalTicketBus
Data/ PerfLabContext, entities, the deterministic seeder, the outage switch
docs/ Scenarios.md, Baseline.md, Budget.md

Known simplifications

  • The database is SQLite in process, so statements are about six times cheaper than they would be against SQL Server across a socket. The statement counts in this app are real; the wall-clock cost of the N+1 is flattering. docs/Budget.md explains what that did to the thresholds.
  • The chart is Wisej-4-ChartJS. The lesson snippet writes chartLoad.DataSource = snapshot.ChartRows; the real control takes Labels plus DataSets, so that is what RebuildChart does.
  • The PERF records are also shown on screen. In production they would only go to the logging pipeline — ScenarioProbe writes to ILogger first and to the session buffer second, and the console output of dotnet run shows exactly the same lines.