HTTP load-testing tool with a native GUI — an analog of k6 / hey / wrk, built as a real-world showcase for Plumix: one codebase, two UI stacks, a zero-allocation load engine underneath.
Run load tests from your desktop for full analysis — or from your phone to quickly check prod during on-call duty.
- Scenarios — configure target, requests, concurrent users, duration, ramp-up and think time; stored as local JSON.
- Load engine — hundreds of concurrent workers over
HttpClient, results streamed through a boundedChannel<RequestResult>. - Real-time metrics — throughput, latency percentiles (p50/p95/p99 via HDR Histogram) and error rate, charted live while the test runs.
- Report — summary and latency-over-time chart when the run completes.
The interesting part of this repo is the architecture: a single multi-targeted project serves every platform, and the platform picks its UI half at compile time — no #ifs in code.
NetBench/ # the only shared project
├── Features/<F>/ # clean architecture per feature
│ ├── Domain/ # entities + contracts (shared)
│ ├── Data/ # repositories, load engine (shared)
│ └── Presentation/
│ ├── Desktop/ # Avalonia views + MVVM ViewModels (CommunityToolkit)
│ └── Mobile/ # Plumix widgets + Cubit/State (Plumix.Bloc)
├── Desktop/ # app shell for desktop: App, DI (Pure.DI), navigation, custom controls
└── Mobile/ # app shell for mobile: MobileApp (PlumixApplication)
NetBench.csproj targets net10.0;net10.0-android;net10.0-ios. Platform heads are bootstrap-only and pick the nearest target:
| Head | Target | UI stack | State management |
|---|---|---|---|
| Desktop, Browser | net10.0 |
Avalonia controls (XAML) | MVVM, CommunityToolkit.Mvvm |
| Android, iOS | net10.0-android / -ios |
Plumix — Flutter-like widgets in C# | Cubit<TState> + immutable records (Plumix.Bloc) |
Only the Domain and Data layers are shared between platforms; each UI stack brings its own state management. Conditional Compile/AvaloniaXaml items exclude the other platform's half from every compilation.
Load generation is the whole point, so the hot path is allocation-free:
RequestResultis areadonly structpassed byin— at 100k+ requests per run the difference versus a class is decisive.Span<T>/ReadOnlySpan<T>parsing,ArrayPoolbuffers, no LINQ on the hot path.- Results flow through a bounded single-reader
Channel<RequestResult>; aggregation uses lock-free counters (Interlocked) plus an HDR Histogram under a short lock. - The UI only ever sees aggregated snapshots (~250 ms interval), never individual results.
Requires the .NET 10 SDK. The shared project multi-targets mobile frameworks, so android/ios workloads are needed for a full restore — or pass -p:MobileTargets=false to build desktop-only without them.
# Run the desktop app
dotnet run --project NetBench/NetBench.Desktop/NetBench.Desktop.csproj
# Tests
dotnet test NetBench/NetBench.Tests/NetBench.Tests.csproj
# Without mobile workloads installed
dotnet build NetBench/NetBench.Desktop/NetBench.Desktop.csproj -p:MobileTargets=false
# Mobile (Plumix) flavor
dotnet build NetBench/NetBench/NetBench.csproj -f net10.0-android # shared project only
dotnet build NetBench/NetBench.Android/NetBench.Android.csproj # full APK- Desktop — Windows (
Setup.exe), macOS (.app, Apple Silicon and Intel) and Linux (AppImage) on GitHub Releases; installed copies update themselves. - Mobile — App Store and Google Play (coming with 1.0).
Releases are cut by pushing a vX.Y.Z tag: CI builds every platform and uploads to TestFlight, Play internal
testing and a draft GitHub Release with fastlane and Velopack — see store/README.md.
- Plumix — Flutter-inspired UI framework for .NET
- Plumix.Packages — Plumix.Bloc, Plumix.Provider
- plumix.net
MIT. NetBench uses Plumix.Bloc and Plumix.Slang under the MIT option of their dual license, which applies to applications distributed under an OSI-approved license.