PageSourceSearch

https://nbomber.com/assets/js/ecc295fa.1fb3dae7.js

js nbomber.com collected 2026-10-02 06:25:14 UTC 17,248 bytes, 1 lines download raw bytes

1"use strict";(globalThis.webpackChunknbomber_docs=globalThis.webpackChunknbomber_docs||[]).push([[6937],{9337(e,t,n){n.r(t),n.d(t,{assets:()=>m,contentTitle:()=>u,default:()=>g,frontMatter:()=>h,metadata:()=>s,toc:()=>p});var s=n(6848),i=n(4848),r=n(8453),o=n(9955),a=n(4071),l=n(7106),c=n(2550),d=n(6950);const h={title:"NBomber v5.8.0 - Scenario Weight, Runtime Thresholds",authors:"antyadev",tags:["nbomber-release","load-testing"]},u=void 0,m={authorsImageUrls:[void 0]},p=[{value:"Scenario Weight",id:"scenario-weight",level:2},{value:"Native extensions for probability distributions",id:"native-extensions-for-probability-distributions",level:2},{value:"Runtime Thresholds",id:"runtime-thresholds",level:2},{value:"Get/Find/Exist extensions for Stats data",id:"getfindexist-extensions-for-stats-data",level:2},{value:"HTML report improvements",id:"html-report-improvements",level:2},{value:"Disable rendering of Metrics Table in Console",id:"disable-rendering-of-metrics-table-in-console",level:2},{value:"LoadConfig improvements",id:"loadconfig-improvements",level:2},{value:"What is next",id:"what-is-next",level:2}];function b(e){const t={a:"a",admonition:"admonition",blockquote:"blockquote",code:"code",em:"em",h2:"h2",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,r.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsxs)(t.p,{children:["In the ",(0,i.jsx)(t.a,{href:"https://github.com/PragmaticFlow/NBomber/releases/tag/v5.8.0",children:"new release"}),", we ship a few big features:"]}),"\n",(0,i.jsxs)(t.ul,{children:["\n",(0,i.jsx)(t.li,{children:"Scenario Weight"}),"\n",(0,i.jsx)(t.li,{children:"Native extensions for probability distributions"}),"\n",(0,i.jsx)(t.li,{children:"Runtime Thresholds"}),"\n"]}),"\n",(0,i.jsx)(t.h2,{id:"scenario-weight",children:"Scenario Weight"}),"\n",(0,i.jsx)("center",{children:(0,i.jsx)("img",{src:a.A,width:"80%",height:"80%"})}),"\n",(0,i.jsxs)(t.p,{children:["Previously, NBomber allowed introducing randomness, but primarily within a single Scenario or Step. There was no native way to introduce randomness across multiple scenarios. In the new version we\u2019ve unlocked a highly requested feature - Scenario ",(0,i.jsx)(t.strong,{children:"Weight"}),". The ",(0,i.jsx)(t.strong,{children:"Weight"})," value allows you to control the relative frequency or probability of Scenario's execution. Scenarios with higher weights will run more frequently during the test, while those with lower weights will execute less often, providing flexible control over load distribution. NBomber uses the weights to determine the probability of each Scenario."]}),"\n",(0,i.jsxs)(t.p,{children:["Example: In this case, the ",(0,i.jsx)(t.code,{children:'"read_scenario"'})," probability is 80%, and ",(0,i.jsx)(t.code,{children:'"write_scenario"'})," is 20%."]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-csharp",children:'var readScenario = Scenario\n    .Create("read_scenario", ...)\n    .WithWeight(80); // sets 80%\n\nvar writeScenario = Scenario\n    .Create("write_scenario", ...)\n    .WithWeight(20); // sets 20%\n\nNBomberRunner\n    .RegisterScenarios(readScenario, writeScenario)\n    .Run();\n'})}),"\n",(0,i.jsx)(t.p,{children:(0,i.jsxs)(t.em,{children:["For more information, please refer to our ",(0,i.jsx)(t.a,{href:"https://nbomber.com/docs/nbomber/dynamic-workloads",children:"documentation"}),"."]})}),"\n",(0,i.jsx)(t.h2,{id:"native-extensions-for-probability-distributions",children:"Native extensions for probability distributions"}),"\n",(0,i.jsx)("center",{children:(0,i.jsx)("img",{src:l.A,width:"70%",height:"70%"})}),"\n",(0,i.jsxs)(t.p,{children:["In our documentation, we referenced several popular probability distributions: Uniform, Zipfian, and Multinomial. To use them, we noted that you need to install the ",(0,i.jsx)(t.strong,{children:"MathNet.Numerics"})," package."]}),"\n",(0,i.jsxs)(t.blockquote,{children:["\n",(0,i.jsxs)(t.p,{children:["To start working with probabilities, you should install ",(0,i.jsx)(t.a,{href:"https://www.nuget.org/packages/MathNet.Numerics",children:"MathNet.Numerics"})," package. It's a free package that ",(0,i.jsx)(t.a,{href:"https://numerics.mathdotnet.com/Probability",children:"contains"})," many useful probabilies."]}),"\n"]}),"\n",(0,i.jsx)(t.p,{children:"In the new version of NBomber, we decided it would be benef
1icial to have these distributions available out of the box. Since they will be included with NBomber, we aimed to ensure they are highly performant compared to MathNet.Numerics. One downside of MathNet.Numerics is that its API caused extra allocations, which we wanted to avoid in our implementation."}),"\n",(0,i.jsxs)(t.ul,{children:["\n",(0,i.jsxs)(t.li,{children:["We added native support for ",(0,i.jsx)(t.strong,{children:"Zipf"})," distribution, which allows us to choose an item according to the Zipfian distribution. For example, when choosing a record, some records will be extremely popular (the head) while most records will be unpopular (the tail). The Zipf distribution is a mathematical concept that describes the frequency of items ranked from most common to least common in a dataset, like word frequencies in a book or city populations. Also, this distribution can be used to describe hot keys/items (skewed key distributions) in database or popular tweets in Tweeter web service."]}),"\n"]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-csharp",children:'var scenario = Scenario.Create("zipfian_distribution", async context =>\n{    \n    var stepNumber = context.Random.Zipf(n: 5, s: 1.3);\n\n    switch (stepNumber)\n    {\n        case 1: // run step 1\n            Step.Run("step_1", ...);\n            break;\n\n        case 2: // run step 2\n            Step.Run("step_2", ...);\n            break;\n        \n        ...\n    }\n});\n'})}),"\n",(0,i.jsxs)(t.ul,{children:["\n",(0,i.jsxs)(t.li,{children:["We added native support for ",(0,i.jsx)(t.strong,{children:"Multinomial"})," distribution, which allows us to specify probabilities for each item. For example, assigning a probability of 0.95 to the Read operation, 0.05 to the Update operation, and 0 to Delete and Insert. This setup creates a read-heavy workload. To simulate Multinomial distribution we can use extension method Choice for Random object."]}),"\n"]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-csharp",children:'// 70% for read, 20% for write, 10% for delete\nvar items = new[] { ("read", 70), ("write", 20), ("delete", 10) };\n\nvar scenario = Scenario.Create("multinomial_distribution", async context =>\n{    \n    var randomItem = context.Random.Choice(items);\n\n    switch (randomItem)\n    {\n        case "read": // 70% for read\n            Step.Run("read", ...);            \n            break;\n\n        case "write": // 20% for write\n            Step.Run("write", ...);\n            break;\n\n        case "delete": // 10% for delete\n            Step.Run("delete", ...);\n            break;\n    }\n});\n'})}),"\n",(0,i.jsx)(t.p,{children:(0,i.jsxs)(t.em,{children:["For more information, please refer to our ",(0,i.jsx)(t.a,{href:"https://nbomber.com/docs/nbomber/dynamic-workloads",children:"documentation"}),"."]})}),"\n",(0,i.jsx)(t.h2,{id:"runtime-thresholds",children:"Runtime Thresholds"}),"\n",(0,i.jsx)(t.p,{children:"We're excited to announce that this feature has finally arr
1ived for NBomber fans. Assertions and thresholds have been supported from day one. Typically, thresholds with assertions are applied after the active load test has finished, once all the available statistics have been provided. Here\u2019s an example:"}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-csharp",children:'var scenario = Scenario.Create("http-scenario", async context => ...);\n\nvar result = NBomberRunner\n    .RegisterScenarios(scenario)\n    .Run();\n\n// the test is finished, and we can now retrieve the final stats \n// to check against our thresholds.\nvar scnStats = result.ScenarioStats.Get("http-scenario");\n\nAssert.True(scnStats.Fail.Request.Percent <= 5);  \n'})}),"\n",(0,i.jsxs)(t.p,{children:["However, there may be cases where you want to define thresholds that are active throughout the entire test run \u2014 we refer to these as ",(0,i.jsx)(t.strong,{children:"Runtime Thresholds"}),". Here is an example:"]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-csharp",children:'var scenario = Scenario.Create("http_scenario", async context =>\n{\n    ...\n})\n.WithThresholds(  \n    Threshold.Create(scenarioStats => scenarioStats.Fail.Request.Percent < 10),\n\n    Threshold.Create("step_1", stepStats => stepStats.Fail.Request.Percent < 10),\n)\n'})}),"\n",(0,i.jsxs)(t.admonition,{type:"info",children:[(0,i.jsxs)(t.p,{children:[(0,i.jsx)(t.strong,{children:"Runtime Thresholds"}),", applied to real-time stats, help prevent the test from continuing execution if specific failure criteria are already met. Additionally, the results of these threshold checks are included in the HTML report, making them highly useful for performance analysis."]}),(0,i.jsxs)(t.ul,{children:["\n",(0,i.jsx)(t.li,{children:"Runtime Thresholds perform checks periodically, by default every 5 seconds."}),"\n",(0,i.jsx)(t.li,{children:"The results of thresholds checks are included in the HTML report."}),"\n",(0,i.jsx)(t.li,{children:"Runtime Thresholds can be defined in JSON Config."}),"\n"]})]}),"\n",(0,i.jsx)(t.p,{children:"The key feature of Runtime Thresholds is that they can be defined in the JSON Config. Generally, Runtime Thresholds offer flexibility in terms of configuration: you can set them to run without causing early termination of the test or delay the start of the threshold check."}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-json",children:'"ThresholdSettings": [\n\n    { "OkRequest": "RPS >= 80" },\n\n    { "FailRequest": "Percent < 10" },\n\n    { "OkLatency": "max < 100" },\n\n    { "StatusCode": ["500", "Percent < 5"] },\n    { "StatusCode": ["400", "Percent < 10"], "AbortWhenErrorCount": 5 }\n    { "StatusCode": ["200", "Percent >= 90"], "StartCheckAfter": "00:00:20" }\n]\n'})}),"\n",(0,i.jsx)(t.p,{children:"After completing a test that includes Runtime Thresholds, the results of the threshold checks will be available in the HTML report. This allows you to easily review and analyze how the system performed against the defined thresholds throughout the test run."}),"\n",(0,i.jsx)("center",{children:(0,i.jsx)("img",{src:o.A})}),"\n",(0,i.jsx)(t.p,{children:(0,i.jsxs)(t.em,{children:["For more information, please refer to our ",(0,i.jsx)(t.a,{href:"https://nbomber.com/docs/nbomber/asserts_and_thresholds",children:"documentation"}),"."]})}),"\n",(0,i.jsx)(t.h2,{id:"getfindexist-extensions-for-stats-data",children:"Get/Find/Exist extensions for Stats data"}),"\n",(0,i.jsxs)(t.p,{children:["The Stats Extensions include the useful helper methods: ",(0,i.jsx)(t.code,{children:"Get, Find, Exists"}),", which are used to work with ",(0,i.jsx)(t.code,{children:"ScenarioStats, StepStats, StatusCodes"}),", and other similar data."]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-csharp",children:'var result = NBomberRunner\n                .RegisterScenarios(scenario1, scenario2)\n                .Run();\n\n// gets scenario stats: \n// it throws exception if "scenario_1" is not found \nvar scnStats1 = result.ScenarioStats.Get("scenario_1");\n\n// finds scenario stats: \n// it returns null if "scenario_2" is not found \nvar scnStats2 = result.ScenarioStats.Find("scenario_2");\n\n// check that step "login" exist in the list\nbool isLoginExist = scnStats1.StepStats.Exists("login");\n\n// gets "login" step stats\n// it throws exception if "login" is not found \nvar loginStats = scnStats1.StepStats.Get("login");\n\n// check that status code "503" exist in the list\nbool isExist = scnStats1.Fail.StatusCodes.Exists("503");\n\n// get stats of status code "503"\n// in case of status not found, the exception will be thrown\nvar statusCode = scnStats1.Fail.StatusCodes.Get("503");\n'})}),"\n",(0,i.jsx)(t.h2,{id:"html-report-improvements",children:"HTML report improvements"}),"\n",(0,i.jsx)(t.p,{children:"We\u2019ve improved the HTML report by updating the logic behind the latency indicators panel. Previously, this panel displayed cumulative results across all scenarios. Based on user feedback, we adjusted 
1it to show data specific to the selected items. For instance, if you select a specific step to analyze, the panel will re-render to display data filtered specifically for that step or scenario."}),"\n",(0,i.jsx)(t.p,{children:"Example: Scenario latency indicators panel."}),"\n",(0,i.jsx)("center",{children:(0,i.jsx)("img",{src:d.A,width:"80%",height:"80%"})}),"\n",(0,i.jsx)(t.p,{children:"Example: Step latency indicators panel."}),"\n",(0,i.jsx)("center",{children:(0,i.jsx)("img",{src:c.A,width:"80%",height:"80%"})}),"\n",(0,i.jsx)(t.h2,{id:"disable-rendering-of-metrics-table-in-console",children:"Disable rendering of Metrics Table in Console"}),"\n",(0,i.jsx)(t.p,{children:"In certain cases, some of you may have noticed that using the console's stdout for debugging can be tricky, as the NBomber Metrics Table may overwrite your printouts. If you want to use the console for debugging purposes, you can now disable the rendering of the Metrics Table in the console."}),"\n",(0,i.jsx)(t.p,{children:"In code:"}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-csharp",children:"NBomberRunner\n    .RegisterScenarios(scenario)\n    .DisplayConsoleMetrics(false)\n"})}),"\n",(0,i.jsx)(t.p,{children:"Also, via CLI args:"}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-bash",children:"dotnet MyLoadTest.dll --display-console-metrics=false\n"})}),"\n",(0,i.jsx)(t.p,{children:"Also, via JSON Config:"}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-json",children:'{\n  "GlobalSettings": {        \n    "DisplayConsoleMetrics": false\n  }\n}\n'})}),"\n",(0,i.jsx)(t.h2,{id:"loadconfig-improvements",children:"LoadConfig improvements"}),"\n",(0,i.jsx)(t.p,{children:"We have a method that loads JSON configuration. Previously, this method only supported loading data from a file or URL. We've extended it to allow loading JSON content as a string."}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-csharp",children:'NBomberRunner\n    .RegisterScenarios(scenario) \n    .LoadConfig("{ YOUR JSON }")\n'})}),"\n",(0,i.jsx)(t.h2,{id:"what-is-next",children:"What is next"}),"\n",(0,i.jsxs)(t.ul,{children:["\n",(0,i.jsxs)(t.li,{children:[(0,i.jsx)(t.a,{href:"https://github.com/PragmaticFlow/NBomber/releases/tag/v5.8.0",children:"Release Notes for v5.8"}),"."]}),"\n",(0,i.jsxs)(t.li,{children:["Please follow our ",(0,i.jsx)(t.a,{href:"https://nbomber.com/docs/getting-started/roadmap",children:"Roadmap"})," page for the upcoming release."]}),"\n"]})]})}function g(e={}){const{wrapper:t}={...(0,r.R)(),...e.components};return t?(0,i.jsx)(t,{...e,children:(0,i.jsx)(b,{...e})}):b(e)}},7106(e,t,n){n.d(t,{A:()=>s});const s=n.p+"assets/images/distribution-744730cc1cdfb121cefa891cf6c580b6.jpg"},6950(e,t,n){n.d(t,{A:()=>s});const s=n.p+"assets/images/latency-indicators-scenario-cd00feb256bedbec8f2d252122c9cb7a.jpg"},2550(e,t,n){n.d(t,{A:()=>s});const s=n.p+"assets/images/latency-indicators-step-48a26bebaadc1b12810757d3f5f79677.jpg"},9955(e,t,n){n.d(t,{A:()=>s});const s=n.p+"assets/images/runtime_thresholds-38e166790219b73da187979c5ba35811.jpg"},4071(e,t,n){n.d(t,{A:()=>s});const s=n.p+"assets/images/scenario_weight-9cb183413ee90597989c12e955cd5ad4.jpg"},8453(e,t,n){n.d(t,{R:()=>o,x:()=>a});var s=n(6540);const i={},r=s.createContext(i);function o(e){const t=s.useContext(r);return s.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function a(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:o(e.components),s.createElement(r.Provider,{value:t},e.children)}},6848(e){e.exports=JSON.parse('{"permalink":"/blog/2024/10/28/nbomber-v5.8.0","source":"@site/blog/2024-10-28-nbomber-v5.8.0.md","title":"NBomber v5.8.0 - Scenario Weight, Runtime Thresholds","description":"In the new release, we ship a few big features:","date":"2024-10-28T00:00:00.000Z","tags":[{"inline":true,"label":"nbomber-release","permalink":"/blog/tags/nbomber-release"},{"inline":true,"label":"load-testing","permalink":"/blog/tags/load-testing"}],"readingTime":6.56,"hasTruncateMarker":true,"authors":[{"name":"Anton Moldovan","title":"NBomber Core Team","url":"https://github.com/AntyaDev","socials":{"github":"https://github.com/AntyaDev"},"imageURL":"https://avatars.githubusercontent.com/u/1080518","key":"antyadev","page":null}],"frontMatter":{"title":"NBomber v5.8.0 - Scenario Weight, Runtime Thresholds","authors":"antyadev","tags":["nbomber-release","load-testing"]},"unlisted":false,"prevItem":{"title":"NBomber v6.0.1","permalink":"/blog/2025/03/17/nbomber-v6.0.1"},"nextItem":{"title":"NBomber v5.7.0 - Timescale, WebBrowser","permalink":"/blog/2024/07/08/nbomber-v5.7.0"}}')}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.