[{"data":1,"prerenderedAt":547},["ShallowReactive",2],{"navigation":3,"/docs/benchmarks/testing-from-your-own-machine":92,"/docs/benchmarks/testing-from-your-own-machine-surround":542},[4],{"title":5,"path":6,"stem":7,"children":8,"page":39},"Docs","/docs","docs",[9,25,40,60,76,84],{"title":10,"path":11,"stem":12,"children":13,"icon":24},"Getting Started","/docs/getting-started","docs/1.getting-started/1.index",[14,16,20],{"title":15,"path":11,"stem":12},"Introduction",{"title":17,"path":18,"stem":19},"Quick start","/docs/getting-started/quick-start","docs/1.getting-started/2.quick-start",{"title":21,"path":22,"stem":23},"Running from source","/docs/getting-started/running-from-source","docs/1.getting-started/3.running-from-source","i-lucide-rocket",{"title":26,"icon":27,"path":28,"stem":29,"children":30,"page":39},"Configuration","i-lucide-settings-2","/docs/configuration","docs/2.configuration",[31,35],{"title":32,"path":33,"stem":34},"Adding a real database","/docs/configuration/adding-databases","docs/2.configuration/1.adding-databases",{"title":36,"path":37,"stem":38},"Customizing the image","/docs/configuration/customizing-the-image","docs/2.configuration/2.customizing-the-image",false,{"title":41,"path":42,"stem":43,"children":44,"icon":59},"Image variations","/docs/image-variations","docs/3.image-variations/1.index",[45,47,51,55],{"title":46,"path":42,"stem":43},"Choosing a variation",{"title":48,"path":49,"stem":50},"FPM-NGINX","/docs/image-variations/fpm-nginx","docs/3.image-variations/2.fpm-nginx",{"title":52,"path":53,"stem":54},"FrankenPHP","/docs/image-variations/frankenphp","docs/3.image-variations/3.frankenphp",{"title":56,"path":57,"stem":58},"FPM-Apache","/docs/image-variations/fpm-apache","docs/3.image-variations/4.fpm-apache","i-lucide-layers",{"title":61,"path":62,"stem":63,"children":64,"icon":75},"Benchmarks & Methodology","/docs/benchmarks","docs/4.benchmarks/1.index",[65,67,71],{"title":66,"path":62,"stem":63},"Methodology",{"title":68,"path":69,"stem":70},"Reading your results","/docs/benchmarks/reading-your-results","docs/4.benchmarks/2.reading-your-results",{"title":72,"path":73,"stem":74},"Testing from your own machine","/docs/benchmarks/testing-from-your-own-machine","docs/4.benchmarks/3.testing-from-your-own-machine","i-lucide-flask-conical",{"title":77,"path":78,"stem":79,"children":80,"icon":83},"Community Results","/docs/community-results","docs/5.community-results/1.index",[81],{"title":82,"path":78,"stem":79},"Submitting your results","i-lucide-users",{"title":85,"path":86,"stem":87,"children":88,"icon":91},"FAQ","/docs/faq","docs/6.faq/1.index",[89],{"title":90,"path":86,"stem":87},"Frequently asked questions","i-lucide-circle-help",{"id":93,"title":72,"body":94,"description":535,"extension":536,"links":537,"meta":538,"navigation":209,"path":73,"redirect":537,"seo":539,"sitemap":540,"stem":74,"__hash__":541},"docs/docs/4.benchmarks/3.testing-from-your-own-machine.md",{"type":95,"value":96,"toc":519},"minimark",[97,101,108,114,119,122,156,160,170,227,230,234,237,297,303,307,310,351,358,374,378,381,384,449,460,463,467,473,479,485,496,505,509,515],[98,99,100],"p",{},"The built-in load test runs oha on the server it is testing, alongside the application and the database. That keeps an A and B comparison clean, because the request path is identical between your two runs. It also costs you two things, and both are fixable by driving the load yourself from somewhere else.",[98,102,103,107],{},[104,105,106],"strong",{},"You want the absolute number."," With the load generator on the box, some of the CPU that could have served requests is generating them instead. Move it off and the server has the whole machine for the thing you are measuring.",[98,109,110,113],{},[104,111,112],{},"You want honest tail latency."," The built-in test holds a fixed number of connections open. When the server slows down, a fixed-connection client waits rather than sending more, so the reported p95 and p99 come out better than reality. BenchKit labels its own tail percentiles as indicative for that reason.",[115,116,118],"h2",{"id":117},"get-the-commands","Get the commands",[98,120,121],{},"BenchKit builds the commands for you, already pointed at your server.",[123,124,126,131,134,138,145,149],"steps",{"level":125},"3",[127,128,130],"h3",{"id":129},"open-the-app","Open the app",[98,132,133],{},"Go to the BenchKit start screen.",[127,135,137],{"id":136},"find-the-link-under-the-start-button","Find the link under the start button",[98,139,140,141,144],{},"Press ",[104,142,143],{},"Prefer your own tools? You can run some tests externally too",".",[127,146,148],{"id":147},"copy-a-command","Copy a command",[98,150,151,152,155],{},"The ",[104,153,154],{},"Test from your own machine"," modal lists each endpoint with a ready-to-run command. Every command carries your server's address, so there is nothing to fill in.",[115,157,159],{"id":158},"install-oha","Install oha",[98,161,162,169],{},[163,164,168],"a",{"href":165,"rel":166},"https://github.com/hatoo/oha",[167],"nofollow","oha"," is the same load generator BenchKit uses internally, which is what keeps the two comparable.",[171,172,178],"pre",{"className":173,"code":174,"filename":175,"language":176,"meta":177,"style":177},"language-bash shiki shiki-themes github-dark","# macOS\nbrew install oha\n\n# Cargo, on anything else\ncargo install oha\n","Terminal","bash","",[179,180,181,190,204,211,217],"code",{"__ignoreMap":177},[182,183,186],"span",{"class":184,"line":185},"line",1,[182,187,189],{"class":188},"sAwPA","# macOS\n",[182,191,193,197,201],{"class":184,"line":192},2,[182,194,196],{"class":195},"svObZ","brew",[182,198,200],{"class":199},"sU2Wk"," install",[182,202,203],{"class":199}," oha\n",[182,205,207],{"class":184,"line":206},3,[182,208,210],{"emptyLinePlaceholder":209},true,"\n",[182,212,214],{"class":184,"line":213},4,[182,215,216],{"class":188},"# Cargo, on anything else\n",[182,218,220,223,225],{"class":184,"line":219},5,[182,221,222],{"class":195},"cargo",[182,224,200],{"class":199},[182,226,203],{"class":199},[98,228,229],{},"Install it on the machine driving the load, not on the BenchKit server.",[115,231,233],{"id":232},"the-endpoints","The endpoints",[98,235,236],{},"These four routes are the same targets the built-in test drives. They skip session and CSRF middleware, so any machine that can reach the server can load test them.",[238,239,240,253],"table",{},[241,242,243],"thead",{},[244,245,246,250],"tr",{},[247,248,249],"th",{},"Endpoint",[247,251,252],{},"What it represents",[254,255,256,267,277,287],"tbody",{},[244,257,258,264],{},[259,260,261],"td",{},[179,262,263],{},"/bench/static",[259,265,266],{},"Framework baseline. A static response, no database",[244,268,269,274],{},[259,270,271],{},[179,272,273],{},"/bench/json",[259,275,276],{},"An API response. A 25-item JSON payload built in PHP",[244,278,279,284],{},[259,280,281],{},[179,282,283],{},"/bench/db-read",[259,285,286],{},"A database read. 20 rows queried per request",[244,288,289,294],{},[259,290,291],{},[179,292,293],{},"/bench/io?ms=100",[259,295,296],{},"Simulated I/O. Sleeps for the milliseconds you pass, standing in for one outbound call",[98,298,299,302],{},[179,300,301],{},"ms"," is clamped to the 0–1000 range the settings enforce, so a stray query string cannot tie up a worker.",[115,304,306],{"id":305},"measuring-throughput","Measuring throughput",[98,308,309],{},"This is the closed-loop run: hold 50 connections open for 30 seconds and see how much the server gets through.",[171,311,313],{"className":173,"code":312,"filename":175,"language":176,"meta":177,"style":177},"oha -z 30s -c 50 http://\u003Cyour-server-ip>/bench/db-read\n",[179,314,315],{"__ignoreMap":177},[182,316,317,319,323,326,329,332,335,339,342,345,348],{"class":184,"line":185},[182,318,168],{"class":195},[182,320,322],{"class":321},"sDLfK"," -z",[182,324,325],{"class":199}," 30s",[182,327,328],{"class":321}," -c",[182,330,331],{"class":321}," 50",[182,333,334],{"class":199}," http://",[182,336,338],{"class":337},"snl16","\u003C",[182,340,341],{"class":199},"your-server-i",[182,343,98],{"class":344},"s95oV",[182,346,347],{"class":337},">",[182,349,350],{"class":199},"/bench/db-read\n",[98,352,353,354,357],{},"Swap the path for any endpoint in the table. Raise ",[179,355,356],{},"-c"," to push concurrency higher.",[359,360,361],"warning",{},[98,362,363,364,367,368,371,372,144],{},"Under PHP-FPM a request holds a worker for its whole duration. If your connection count is above ",[179,365,366],{},"pm.max_children",", you are measuring the pool rather than the server. Raise ",[179,369,370],{},"PHP_FPM_PM_MAX_CHILDREN"," to at least your core count first. See ",[163,373,36],{"href":37},[115,375,377],{"id":376},"measuring-tail-latency","Measuring tail latency",[98,379,380],{},"A fixed-connection run reports throughput well and understates p99. Gil Tene named this coordinated omission. To measure latency honestly, drive a constant request rate instead and let oha correct for it.",[98,382,383],{},"Do it in two passes:",[123,385,386,390,393,397,407],{"level":125},[127,387,389],{"id":388},"find-your-maximum-throughput","Find your maximum throughput",[98,391,392],{},"Run the throughput command above and note the requests per second it reports.",[127,394,396],{"id":395},"run-again-at-about-70-of-that","Run again at about 70% of that",[98,398,399,400,403,404,144],{},"Pass the rate with ",[179,401,402],{},"-q"," and add ",[179,405,406],{},"--latency-correction",[171,408,410],{"className":173,"code":409,"filename":175,"language":176,"meta":177,"style":177},"oha -q \u003Creq_per_sec> -z 30s --latency-correction http://\u003Cyour-server-ip>/bench/db-read\n",[179,411,412],{"__ignoreMap":177},[182,413,414,416,419,422,425,428,430,432,434,437,439,441,443,445,447],{"class":184,"line":185},[182,415,168],{"class":195},[182,417,418],{"class":321}," -q",[182,420,421],{"class":337}," \u003C",[182,423,424],{"class":199},"req_per_se",[182,426,427],{"class":344},"c",[182,429,347],{"class":337},[182,431,322],{"class":321},[182,433,325],{"class":199},[182,435,436],{"class":321}," --latency-correction",[182,438,334],{"class":199},[182,440,338],{"class":337},[182,442,341],{"class":199},[182,444,98],{"class":344},[182,446,347],{"class":337},[182,448,350],{"class":199},[450,451,452],"note",{},[98,453,454,456,457,459],{},[179,455,406],{}," does nothing without a target rate. The ",[179,458,402],{}," flag is what makes it work, which is why the two always appear together.",[98,461,462],{},"Staying below your maximum is the point. At saturation every client queues, and the numbers stop describing latency and start describing the backlog.",[115,464,466],{"id":465},"reading-what-you-get-back","Reading what you get back",[98,468,469,472],{},[104,470,471],{},"External numbers are not built-in numbers."," An external run includes network time between the two machines and keeps the load generator off the server. Expect different figures than the Web Server Load Test panel, and do not put the two side by side as though they measured the same thing.",[98,474,475,478],{},[104,476,477],{},"Your network is now part of the measurement."," Drive the load from a machine close to the server when you want to measure the server. Latency from a laptop three regions away is mostly the internet.",[98,480,481,484],{},[104,482,483],{},"Run each endpoint on its own."," The built-in test measures one route at a time for the same reason. Overlapping runs compete and neither result means much.",[359,486,487],{},[98,488,489,491,492,495],{},[179,490,283],{}," answers ",[179,493,494],{},"503"," until the table it queries exists. That table is prepared when the HTTP stage starts, so run a benchmark with the web server load test enabled once before you drive that endpoint from outside. The other three endpoints work immediately.",[450,497,498],{},[98,499,500,501,504],{},"There is a fifth route, ",[179,502,503],{},"/bench/env",", which reports what PHP looks like in the process serving requests rather than in the command line process. It is not a load target. BenchKit calls it to record the environment on your results, and it is worth a look when you want to confirm which settings were actually in effect.",[115,506,508],{"id":507},"keeping-the-comparison-fair","Keeping the comparison fair",[98,510,511,512,514],{},"Everything in ",[163,513,68],{"href":69}," still applies. Change one thing at a time, run each variation on the same hardware, and drive the load from the same client machine every time. A result that moved because you switched laptops is not a result.",[516,517,518],"style",{},"html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}",{"title":177,"searchDepth":192,"depth":192,"links":520},[521,526,527,528,529,533,534],{"id":117,"depth":192,"text":118,"children":522},[523,524,525],{"id":129,"depth":206,"text":130},{"id":136,"depth":206,"text":137},{"id":147,"depth":206,"text":148},{"id":158,"depth":192,"text":159},{"id":232,"depth":192,"text":233},{"id":305,"depth":192,"text":306},{"id":376,"depth":192,"text":377,"children":530},[531,532],{"id":388,"depth":206,"text":389},{"id":395,"depth":206,"text":396},{"id":465,"depth":192,"text":466},{"id":507,"depth":192,"text":508},"Drive the load test from a different machine with oha, and measure tail latency honestly.","md",null,{},{"title":72,"description":535},{"loc":73},"MR2IZFvKl-Qn710o2gDZW-tfy5Gg2gMD5mSqVrCMpmE",[543,545],{"title":68,"path":69,"stem":70,"description":544,"children":-1},"What a BenchKit number tells you, and the known limits that apply to it.",{"title":82,"path":78,"stem":79,"description":546,"children":-1},"How a run becomes a public gallery entry, and what gets published.",1786740096644]