Can your drive actually keep up with what you are recording?
Windows measures your disk constantly and then refuses to give you the one number that matters. Task Manager draws a pretty graph that has already scrolled away by the time your recording drops a frame. Every consumer benchmark quotes you a peak that your drive can hold for about a second.
diskrate answers the only question a streamer or recorder actually has:
During the worst single second of a sustained write, how much data got through?
That number β not the average, not the peak β is what decides whether OBS drops frames.
Single PowerShell file. No install, no dependencies, nothing to download. Read-only apart from one temporary file it deletes itself.
diskrate βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Can this drive keep up with what you are recording?
Drive ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Path : C:\Users\hayyz\diskrate-test
Filesystem : NTFS Fixed SSD over NVMe
Device : SAMSUNG MZVLB1T0HBLR-00000
Free space : 754.62 GiB of 919.62 GiB (18% used)
Room to record : 12.0 hours at 150 Mbps
Measuring ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Writing 1.00 GiB x 1 pass(es) in 1.0 MiB chunks, OS write cache defeated.
Drive is quick (882 MB/s) - extending the test to 3.00 GiB so a full 1000 ms window fits.
Pass 1 868.5 MB/s avg ββββββββββββββββββββββ
Final flush cost 1.4 ms across 1 pass(es) - near zero means the
bytes really did reach the device during the timed run, not afterwards.
Sustained write speed βββββββββββββββββββββββββββββββββββββββββββββββ
Average : 868.5 MB/s (6,948 Mbps)
Worst 1s window : 803.2 MB/s (6,426 Mbps) <- what decides dropped frames
Longest stall : 57.1 ms = 3.4 frames at 60 fps
Typical write : 1.13 ms (median), 99th pct 2.00 ms
Can this drive hold it? βββββββββββββββββββββββββββββββββββββββββββββ
1080p60 stream (Twitch/YouTube) 8 Mbps needs 1.0 MB/s PASS
1080p60 record (OBS quality) 50 Mbps needs 6.3 MB/s PASS
1440p60 record 100 Mbps needs 12.5 MB/s PASS
4K60 record (hardware encode) 150 Mbps needs 18.8 MB/s PASS
4K60 record (high bitrate) 400 Mbps needs 50.0 MB/s PASS
1080p60 lossless capture 1,500 Mbps needs 187.5 MB/s PASS
Verdict ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
No problems found. This drive is fine for recording.
That is a real run on a real machine, pasted unedited.
# Windows blocks unsigned .ps1 by default. This bypasses it for ONE process
# only - it does not change your machine's execution policy.
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\diskrate.ps1
# Test the drive you actually record to
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\diskrate.ps1 -Path "D:\Recordings"
# Just the facts about the drive - writes nothing at all
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\diskrate.ps1 -InfoNo administrator rights required. Everything diskrate reads is available to a standard user.
A normal write benchmark measures your RAM, not your disk.
When a program writes a file, Windows returns success as soon as the bytes land in the system file cache. The drive may not see them for another second. Time that loop and you get a number that has nothing to do with your hardware.
diskrate opens its test file with FileOptions.WriteThrough, so every write has to reach the device before it returns. Run -Compare and it shows you both, measured back to back on your drive:
What a naive benchmark would have told you ββββββββββββββββββββββββββ
Buffered write loop : 1,802.2 MB/s flush afterwards took 613 ms
True (write-through): 738.6 MB/s flush afterwards took 4 ms
A buffered benchmark overstates this drive by 2.44x - it is timing RAM, not the disk.
The flush cost is the proof. With write-through, the final flush costs 4 ms because there is nothing left to write. The buffered run spends 613 ms afterwards quietly draining a gigabyte out of RAM β time the "1,802 MB/s" figure conveniently never counted.
On this machine a 1 GiB buffered loop has reported as high as 2,640 MB/s against a device that genuinely sustains around 750 MB/s. A 3.5x lie, and a completely plausible-looking number.
An average hides exactly the event you care about. A drive that writes at 900 MB/s for nine seconds and 0 MB/s for one still averages 810 MB/s β and drops a solid second of your recording.
diskrate timestamps every chunk, then finds the worst one-second interval in the whole run:
- Worst 1s window β the least data completed in any one second. Every PASS/TIGHT/FAIL verdict is judged against this, not the average.
- Longest stall β the biggest gap between two consecutive chunk completions, converted to dropped frames at your fps.
- Typical write / 99th percentile β how consistent the drive is when nothing is going wrong.
Interval semantics matter here and are worth stating: a window runs (anchor, anchor + WindowMs], anchored at the start of the run or immediately after any completion. Anchoring on a completion and counting it would make a totally frozen second report as "one chunk" instead of zero β precisely the case the number exists to expose.
If the run is shorter than one window, the worst second is reported as - and null, never 0. Unknown must never masquerade as catastrophic.
Auto-extend. A quick NVMe finishes 1 GiB in under a second, which would leave the headline number empty on exactly the drives most people record to. If pass 1 runs too fast for a full window and you did not pin -SizeMB yourself, diskrate grows the test and runs it again (capped at 8 GiB, and never more than a third of free space).
| Switch | Default | What it does |
|---|---|---|
-Path <dir> |
current directory | Which drive to test. Point it at your recording folder. |
-SizeMB <n> |
1024 |
Data per pass, in MiB. Setting this disables auto-extend. |
-Passes <n> |
1 |
Run the benchmark n times and merge the timelines. |
-ChunkKB <n> |
1024 |
Write size, in KiB. Smaller values stress the drive harder. |
-WindowMs <n> |
1000 |
Length of the "worst window". |
-Bitrate <n> |
150 |
Your recording bitrate in Mbps, used for headroom maths. |
-Fps <n> |
60 |
Frame rate, used to convert stalls into dropped frames. |
-Compare |
off | Also run a buffered pass to show the cache-defeat difference. |
-Info |
off | Drive facts only. Writes absolutely nothing. |
-FromJson <file> |
Re-analyse a saved -Json result. No disk access at all. |
|
-Json |
off | Machine-readable output on stdout, nothing else. |
| -Quiet | off | Suppress all console output; use the exit code. |
Exit codes: 0 clean, 1 risks found, 2 error.
-Json emits the full per-chunk completion timeline under passes[].marks, not just the summary. Feed that file straight back in with -FromJson and you get byte-identical numbers β every figure the tool reports is reproducible from the data it publishes.
That is not a coincidence. Timestamps are quantised once, at capture, to 0.0001 ms (finer than the Stopwatch resolution), so analysis and publication see exactly the same values. Without it a median could land on a rounding boundary and come back a thousandth different on replay.
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\diskrate.ps1 -Json > run.json
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\diskrate.ps1 -FromJson run.jsonA report with no usable timeline is refused with a one-line message and exit 2. It is never answered with blank numbers and ok: true.
diskrate only speaks up when something is genuinely wrong for recording:
- FAT32 β hard 4 GiB file size cap. It tells you how many minutes that is at your bitrate (at 150 Mbps: 3.8 minutes, then your recording stops).
- Network or removable drive β throughput depends on things this test cannot see.
- Over 90% full β SSDs slow down sharply with no spare blocks.
- Under 30 minutes of headroom at your bitrate.
- A write stall of 100 ms or more (FAIL at 500 ms), reported in ms and in frames.
- Wildly uneven speed between the best and worst second.
A healthy drive gets a clean bill of health and exit code 0. This was tested deliberately: the suite includes a good-drive fixture that must produce zero risks, because a tool that cries wolf gets ignored.
Yes, and here is exactly what it does:
- It creates one file,
diskrate-<guid>.tmp, in the folder you point it at, and deletes it in afinallyblock β even on Ctrl+C or an error. - It touches nothing else. No registry writes, no services, no processes, no settings.
-Infoand-FromJsonwrite nothing whatsoever.- It needs no undo, because it changes no state. That is verified, not asserted: the test suite hashes every file in the target folder before and after a full benchmark and requires byte-identical results, and greps the source for
Set-ItemProperty,New-ItemProperty,Remove-ItemProperty,Stop-Process,Stop-ServiceandSetValue(β all zero.
It will write a few GiB to your drive and read nothing back. On an SSD that is a rounding error against its endurance rating.
Deliberate and consistent, because this is where benchmarks get sloppy:
-SizeMBand-ChunkKBare binary β MiB and KiB, 1024-based.- Throughput is reported in decimal MB/s β 1,000,000 bytes, the same convention CrystalDiskMark uses.
That makes Mbps = MB/s x 8 exact, so it lines up with how video bitrates are quoted, and it makes every expected value in the test suite computable by hand.
Two suites ship with the tool. Run them yourself.
PASSED: 134
FAILED: 0
Every expected number is hand-computed, and each fixture plants a different distinctive value in each field so a tool that mixes two fields up cannot pass:
- 30 chunks on an exact 100 ms cadence: average exactly 10.00 MB/s, worst second exactly 10.00, median stall exactly 100.
- One planted 1000 ms freeze: average exactly 7.00, worst second exactly 1.00 MB/s anchored at t=900, median stall unchanged at 100.
- A 1200 ms freeze (longer than the window): worst second is a measured 0.00, and asserted to be non-null.
- A run shorter than one window: worst second is null, not 0, while the average is still computed.
- Recording maths by hand: FAT32's 4 GiB cap at 150 Mbps = 3.81775 min, at 8 Mbps = 71.58279 min; 1 TB at 400 Mbps = 333.3333 min.
- Verdict boundaries tested at exactly 2.00x and 1.20x of 18.75 MB/s.
- A planted FAT32 / removable / nearly-full volume raises a FAIL quoting 3.8 min and exits 1; a healthy fixture raises zero risks and exits 0.
- Four error paths each print a single clean line, emit valid JSON, exit 2, and never a stack trace.
- A real 64 MiB write asserts exactly 67,108,864 bytes on disk and that the reported rate equals bytes/elapsed.
- A live benchmark is written out with
-Jsonand read back with-FromJson; average, worst second, max stall, median, 99th percentile, duration, flush cost and device model must all come back exactly equal. Repeated five times on different data, identical every time. -Quietverified silent on the success, failure and error paths.
PASSED: 57
FAILED: 0
Synthetic fixtures are clean and predictable, which is exactly why they miss things. These run against real hardware and cross-check against independent Windows APIs.
Drive facts confirmed by three different APIs (Get-Volume, Win32_LogicalDisk, Get-PhysicalDisk):
[PASS] total size matches Get-Volume -> 987439820800
[PASS] total size matches Win32_LogicalDisk -> 987439820800
[PASS] free space agrees with Get-Volume within 64 MiB -> 53248 bytes apart
[PASS] media type matches Get-PhysicalDisk -> SSD
[PASS] bus type matches Get-PhysicalDisk -> NVMe
[PASS] model matches Get-PhysicalDisk -> SAMSUNG MZVLB1T0HBLR-00000
Windows independently confirms the bytes. Win32_PerfRawData_PerfDisk_LogicalDisk.DiskWriteBytesPersec is, despite the name, a raw cumulative byte total. Read it before and after and you get an exact second opinion from the kernel:
[PASS] Windows saw the same bytes -> 1.0062x (270,102,528 observed vs 268,435,456 written)
[PASS] write-through leaves nothing to flush -> 3.504 ms
An independent reference implementation β written with a deliberately naive pipeline-and-slice technique that shares no code with the tool's forward-only two-pointer sweep β agrees field for field on a real captured timeline:
[PASS] chunks agree -> 256
[PASS] bytes agree -> 268435456
[PASS] average MB/s agrees-> 972.28
[PASS] max stall agrees -> 8.889
[PASS] median stall agrees-> 0.992
Known ground truth planted inside real data. A 1500 ms stall is spliced into a genuine 256-chunk timeline captured from this NVMe. The test asserts element-wise that exactly one real interval changed, that the tool recovers the planted value exactly, that it survives end to end through the published entry point alongside real device facts, and that the surrounding hundreds of real intervals do not leak into the answer:
[PASS] exactly one real interval was replaced -> 1
[PASS] planted 1500 ms stall recovered exactly -> 1500
[PASS] worst second is a measured 0.00 MB/s -> 0
[PASS] real device model carried through -> SAMSUNG MZVLB1T0HBLR-00000
[PASS] stall reported as 90 frames at 60fps
-> Longest write stall was 1,500 ms - 90.0 frames at 60 fps.
The number tracks real work β it is not a constant. Three sizes, three distinct durations, and time scaling with data:
[PASS] duration grows with size -> 74.975 / 144.525 / 300.822 ms
[PASS] quadrupling size ~quadruples time -> 4.01x
[PASS] rates are non-zero and not identical -> 895.08 / 928.68 / 892.34 MB/s
Cache defeat, measured on real hardware:
[PASS] buffered loop looks faster than the device
-> buffered=2380.41 writethrough=899.59 MB/s
[PASS] buffered leaves a real flush bill
-> buffered flush=141.87ms writethrough flush=1.345ms
[PASS] both wrote the same bytes -> 268435456
Read-only behaviour proved, not claimed:
[PASS] -Info leaves the folder byte-identical
[PASS] benchmark leaves no temp file behind -> 0
[PASS] folder identical after a full benchmark
Worth recording, because all three looked fine and produced plausible numbers.
1. A $script: variable silently destroyed a parameter. The script initialised $script:Quiet = $false for its own output helpers. But parameters live at script scope, so that assignment overwrote the user's -Quiet switch every single time β -Quiet simply never worked. -Json masked it, because -Json assigns $true afterwards. Renamed to $script:Silent. Section L of the self-test is now a permanent regression guard.
2. The worst-second window could never report zero. The window was anchored on a chunk completion and counted it, so a second in which the drive did absolutely nothing still came back as "one chunk". The bug only showed up when a planted stall was made longer than the window β a 750 ms stall cannot collapse a 1000 ms window, because no window fits inside it. Fixed the interval to (anchor, anchor + WindowMs], and the independent reference implementation was updated and re-agreed.
3. -FromJson could not actually read -Json. The documented round trip was broken from the start: -Json never emitted the marks timeline, so replaying the tool's own output produced ok: true with every number blank β a confidently empty answer, which is worse than an error. Only a smoke test from a fresh clone, exercising the modes the way a user would chain them, caught it. Fixed by publishing the timeline, refusing reports that have none, and asserting exact round-trip equality in the suite.
- Windows with PowerShell 5.1 or later (ships with Windows 10 and 11)
- No admin rights, no installs, no external tools
- obs-4k60-recorder β the OBS settings guide for 4K60 recording
- framecheck β tells you a recording dropped frames;
diskratetells you whether the drive was why - diskscout β find what is eating your disk space
- gamemode β free up RAM and CPU before you record
MIT β see LICENSE.