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.