locust 2.32.2.dev7__tar.gz → 2.32.2.dev19__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/PKG-INFO +1 -1
  2. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/_version.py +2 -2
  3. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/html.py +3 -1
  4. locust-2.32.2.dev19/locust/util/date.py +23 -0
  5. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/web.py +13 -5
  6. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/webui/dist/report.html +1 -1
  7. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/poetry.lock +48 -1
  8. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/pyproject.toml +2 -1
  9. locust-2.32.2.dev7/locust/util/date.py +0 -5
  10. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/LICENSE +0 -0
  11. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/README.md +0 -0
  12. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/__init__.py +0 -0
  13. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/__main__.py +0 -0
  14. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/argument_parser.py +0 -0
  15. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/clients.py +0 -0
  16. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/contrib/__init__.py +0 -0
  17. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/contrib/fasthttp.py +0 -0
  18. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/contrib/mongodb.py +0 -0
  19. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/contrib/postgres.py +0 -0
  20. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/debug.py +0 -0
  21. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/dispatch.py +0 -0
  22. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/env.py +0 -0
  23. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/event.py +0 -0
  24. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/exception.py +0 -0
  25. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/input_events.py +0 -0
  26. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/log.py +0 -0
  27. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/main.py +0 -0
  28. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/py.typed +0 -0
  29. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/rpc/__init__.py +0 -0
  30. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/rpc/protocol.py +0 -0
  31. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/rpc/zmqrpc.py +0 -0
  32. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/runners.py +0 -0
  33. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/shape.py +0 -0
  34. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/stats.py +0 -0
  35. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/user/__init__.py +0 -0
  36. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/user/inspectuser.py +0 -0
  37. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/user/sequential_taskset.py +0 -0
  38. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/user/task.py +0 -0
  39. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/user/users.py +0 -0
  40. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/user/wait_time.py +0 -0
  41. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/util/__init__.py +0 -0
  42. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/util/cache.py +0 -0
  43. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/util/deprecation.py +0 -0
  44. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/util/directory.py +0 -0
  45. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/util/exception_handler.py +0 -0
  46. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/util/load_locustfile.py +0 -0
  47. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/util/rounding.py +0 -0
  48. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/util/timespan.py +0 -0
  49. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/util/url.py +0 -0
  50. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/webui/dist/assets/favicon-dark.png +0 -0
  51. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/webui/dist/assets/favicon-light.png +0 -0
  52. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/webui/dist/assets/index-CV_-ndKF.js +0 -0
  53. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/webui/dist/auth.html +0 -0
  54. {locust-2.32.2.dev7 → locust-2.32.2.dev19}/locust/webui/dist/index.html +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: locust
3
- Version: 2.32.2.dev7
3
+ Version: 2.32.2.dev19
4
4
  Summary: Developer-friendly load testing framework
5
5
  Home-page: https://locust.io/
6
6
  License: MIT
@@ -14,7 +14,7 @@ __version_tuple__: VERSION_TUPLE
14
14
  version_tuple: VERSION_TUPLE
15
15
 
16
16
 
17
- __version__ = "2.32.2.dev7"
17
+ __version__ = "2.32.2.dev19"
18
18
  version = __version__
19
- __version_tuple__ = (2, 32, 2, "dev7")
19
+ __version_tuple__ = (2, 32, 2, "dev19")
20
20
  version_tuple = __version_tuple__
@@ -12,7 +12,7 @@ from . import stats as stats_module
12
12
  from .runners import STATE_STOPPED, STATE_STOPPING, MasterRunner
13
13
  from .stats import sort_stats, update_stats_history
14
14
  from .user.inspectuser import get_ratio
15
- from .util.date import format_utc_timestamp
15
+ from .util.date import format_duration, format_utc_timestamp
16
16
 
17
17
  PERCENTILES_FOR_HTML_REPORT = [0.50, 0.6, 0.7, 0.8, 0.9, 0.95, 0.99, 1.0]
18
18
  DEFAULT_BUILD_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "webui", "dist")
@@ -36,6 +36,7 @@ def get_html_report(
36
36
  if end_ts := stats.last_request_timestamp:
37
37
  end_time = format_utc_timestamp(end_ts)
38
38
  else:
39
+ end_ts = stats.start_time
39
40
  end_time = start_time
40
41
 
41
42
  host = None
@@ -88,6 +89,7 @@ def get_html_report(
88
89
  ],
89
90
  "start_time": start_time,
90
91
  "end_time": end_time,
92
+ "duration": format_duration(stats.start_time, end_ts),
91
93
  "host": escape(str(host)),
92
94
  "history": history,
93
95
  "show_download_link": show_download_link,
@@ -0,0 +1,23 @@
1
+ from datetime import datetime, timezone
2
+
3
+
4
+ def format_utc_timestamp(unix_timestamp):
5
+ return datetime.fromtimestamp(int(unix_timestamp), timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
6
+
7
+
8
+ def format_safe_timestamp(unix_timestamp):
9
+ return datetime.fromtimestamp(int(unix_timestamp)).strftime("%Y-%m-%d-%Hh%M")
10
+
11
+
12
+ def format_duration(start_time, end_time):
13
+ seconds = int(end_time) - int(start_time)
14
+ days = seconds // 86400
15
+ hours = (seconds % 86400) // 3600
16
+ minutes = (seconds % 3600) // 60
17
+ seconds = seconds % 60
18
+
19
+ time_parts = [(days, "day"), (hours, "hour"), (minutes, "minute"), (seconds, "second")]
20
+
21
+ parts = [f"{value} {label}{'s' if value != 1 else ''}" for value, label in time_parts if value > 0]
22
+
23
+ return " and ".join(filter(None, [", ".join(parts[:-1])] + parts[-1:])) if parts else "0 seconds"
@@ -40,7 +40,7 @@ from .runners import STATE_MISSING, STATE_RUNNING, MasterRunner
40
40
  from .stats import StatsCSV, StatsCSVFileWriter, StatsErrorDict, sort_stats
41
41
  from .user.inspectuser import get_ratio
42
42
  from .util.cache import memoize
43
- from .util.date import format_utc_timestamp
43
+ from .util.date import format_safe_timestamp
44
44
  from .util.timespan import parse_timespan
45
45
 
46
46
  if TYPE_CHECKING:
@@ -347,17 +347,25 @@ class WebUI:
347
347
  )
348
348
  if request.args.get("download"):
349
349
  res = app.make_response(res)
350
- res.headers["Content-Disposition"] = f"attachment;filename=report_{time()}.html"
350
+ host = f"_{self.environment.host}" if self.environment.host else ""
351
+ res.headers["Content-Disposition"] = (
352
+ f"attachment;filename=Locust_{format_safe_timestamp(self.environment.stats.start_time)}_"
353
+ + f"{self.environment.locustfile}{host}.html"
354
+ )
351
355
  return res
352
356
 
353
357
  def _download_csv_suggest_file_name(suggest_filename_prefix: str) -> str:
354
358
  """Generate csv file download attachment filename suggestion.
355
359
 
356
360
  Arguments:
357
- suggest_filename_prefix: Prefix of the filename to suggest for saving the download. Will be appended with timestamp.
361
+ suggest_filename_prefix: Prefix of the filename to suggest for saving the download.
362
+ Will be appended with timestamp.
358
363
  """
359
-
360
- return f"{suggest_filename_prefix}_{time()}.csv"
364
+ host = f"_{self.environment.host}" if self.environment.host else ""
365
+ return (
366
+ f"Locust_{format_safe_timestamp(self.environment.stats.start_time)}_"
367
+ + f"{self.environment.locustfile}{host}_{suggest_filename_prefix}.csv"
368
+ )
361
369
 
362
370
  def _download_csv_response(csv_data: str, filename_prefix: str) -> Response:
363
371
  """Generate csv file download response with 'csv_data'.
@@ -220,7 +220,7 @@ PERFORMANCE OF THIS SOFTWARE.
220
220
  <span style="color:${f};">
221
221
  ${h}:&nbsp${Rbe({chartValueFormatter:i,value:d})}
222
222
  </span>
223
- `,""):"No data",borderWidth:0},xAxis:{type:"time",min:(e.time||[new Date().toISOString()])[0],startValue:(e.time||[])[0],axisLabel:{formatter:Lbe}},grid:{left:60,right:40},yAxis:Ebe({splitAxis:a,yAxisLabels:o}),series:NW({charts:e,lines:r,scatterplot:s}),color:n,toolbox:{right:10,feature:{dataZoom:{title:{zoom:"Zoom Select",back:"Zoom Reset"},yAxisIndex:!1},saveAsImage:{name:t.replace(/\s+/g,"_").toLowerCase()+"_"+new Date().getTime()/1e3,title:"Download as PNG",emphasis:{iconStyle:{textPosition:"left"}}}}}}),Nbe=e=>({symbol:"none",label:{formatter:t=>`Run #${t.dataIndex+1}`,padding:[0,0,8,0]},data:(e.markers||[]).map(t=>({xAxis:t}))}),zbe=e=>t=>{const{batch:r}=t;if(!r)return;const[{start:n,startValue:i,end:a}]=r,o=n>0&&a<=100||i>0;e.setOption({dataZoom:[{type:"slider",show:o}]})},Bbe=aQ;function Fbe({charts:e,title:t,lines:r,colors:n,chartValueFormatter:i,splitAxis:a,yAxisLabels:o,scatterplot:s,shouldReplaceMergeLines:l=!1}){const[u,c]=V.useState(null),f=Bbe(({theme:{isDarkMode:d}})=>d),h=V.useRef(null);return V.useEffect(()=>{if(!h.current)return;const d=bce(h.current);d.setOption(Obe({charts:e,title:t,lines:r,colors:n,chartValueFormatter:i,splitAxis:a,yAxisLabels:o,scatterplot:s})),d.on("datazoom",zbe(d));const p=()=>d.resize();return window.addEventListener("resize",p),d.group="swarmCharts",Cce("swarmCharts"),c(d),()=>{Tce(d),window.removeEventListener("resize",p)}},[h]),V.useEffect(()=>{const d=r.every(({key:p})=>!!e[p]);u&&d&&u.setOption({series:r.map(({key:p,yAxisIndex:v,...g},y)=>({...g,data:e[p],...a?{yAxisIndex:v||y}:{},...y===0?{markLine:Nbe(e)}:{}}))})},[e,u,r]),V.useEffect(()=>{if(u){const{textColor:d,axisColor:p,backgroundColor:v,splitLine:g}=f?Lz.DARK:Lz.LIGHT;u.setOption({backgroundColor:v,textStyle:{color:d},title:{textStyle:{color:d}},legend:{icon:"circle",inactiveColor:d,textStyle:{color:d}},tooltip:{backgroundColor:v,textStyle:{color:d}},xAxis:{axisLine:{lineStyle:{color:p}}},yAxis:{axisLine:{lineStyle:{color:p}},splitLine:{lineStyle:{color:g}}}})}},[u,f]),V.useEffect(()=>{u&&u.setOption({series:NW({charts:e,lines:r,scatterplot:s})},l?{replaceMerge:["series"]}:void 0)},[r]),H.jsx("div",{ref:h,style:{width:"100%",height:"300px"}})}const Vbe=dl.percentilesToChart?dl.percentilesToChart.map(e=>({name:`${e*100}th percentile`,key:`responseTimePercentile${e}`})):[],Gbe=["#ff9f00","#9966CC","#8A2BE2","#8E4585","#E0B0FF","#C8A2C8","#E6E6FA"],$be=[{title:"Total Requests per Second",lines:[{name:"RPS",key:"currentRps"},{name:"Failures/s",key:"currentFailPerSec"}],colors:["#00ca5a","#ff6d6d"]},{title:"Response Times (ms)",lines:Vbe,colors:Gbe},{title:"Number of Users",lines:[{name:"Number of Users",key:"userCount"}],colors:["#0099ff"]}];function zW({charts:e}){return H.jsx("div",{children:$be.map((t,r)=>H.jsx(Fbe,{...t,charts:e},`swarm-chart-${r}`))})}const Hbe=({ui:{charts:e}})=>({charts:e});Kd(Hbe)(zW);function Wbe(e){return(e*100).toFixed(1)+"%"}function EC({classRatio:e}){return H.jsx("ul",{children:Object.entries(e).map(([t,{ratio:r,tasks:n}])=>H.jsxs("li",{children:[`${Wbe(r)} ${t}`,n&&H.jsx(EC,{classRatio:n})]},`nested-ratio-${t}`))})}function BW({ratios:{perClass:e,total:t}}){return!e&&!t?null:H.jsxs("div",{children:[e&&H.jsxs(H.Fragment,{children:[H.jsx("h3",{children:"Ratio Per Class"}),H.jsx(EC,{classRatio:e})]}),t&&H.jsxs(H.Fragment,{children:[H.jsx("h3",{children:"Total Ratio"}),H.jsx(EC,{classRatio:t})]})]})}const Ube=({ui:{ratios:e}})=>({ratios:e});Kd(Ube)(BW);const xS={DARK:"dark",LIGHT:"light"},FW=localStorage.theme===xS.DARK||!("theme"in localStorage)&&window.matchMedia("(prefers-color-scheme: dark)").matches?xS.DARK:xS.LIGHT,jbe={isDarkMode:!1},Ybe=Oq({name:"theme",initialState:jbe,reducers:{setIsDarkMode:(e,{payload:t})=>{e.isDarkMode=t}}}),Xbe=Ybe.reducer,Zbe=e=>ZT({palette:{mode:e,primary:{main:"#15803d"},success:{main:"#00C853"}},components:{MuiCssBaseline:{styleOverrides:{":root":{"--footer-height":"40px"}}}}}),Kbe=Zbe(window.theme||FW),qbe=(window.theme||FW)==="dark",Qbe=[{key:"method",title:"Type"},{key:"name",title:"Name"},{key:"numRequests",title:"# Requests"},{key:"numFailures",title:"# Fails"},{key:"avgResponseTime",title:"Average (ms)",round:2},{key:"minResponseTime",title:"Min (ms)"},{key:"maxResponseTime",title:"Max (ms)"},{key:"avgContentLength",title:"Average size (bytes)",round:2},{key:"totalRps",title:"RPS",round:2},{key:"totalFailPerSec",title:"Failures/s",round:2}],Jbe=Aq({reducer:W3({theme:Xbe}),preloadedState:{theme:{isDarkMode:qbe}}});function eCe({locustfile:e,showDownloadLink:t,startTime:r,endTime:n,charts:i,host:a,exceptionsStatistics:o,requestsStatistics:s,failuresStatistics:l,responseTimeStatistics:u,tasks:c}){return H.jsx(ZQ,{store:Jbe,children:H.jsxs($Y,{theme:Kbe,children:[H.jsx(TZ,{}),H.jsxs(SZ,{maxWidth:"lg",sx:{my:4},children:[H.jsxs(Nn,{sx:{display:"flex",justifyContent:"space-between",alignItems:"flex-end"},children:[H.jsx(Tr,{component:"h1",noWrap:!0,sx:{fontWeight:700},variant:"h3",children:"Locust Test Report"}),t&&H.jsx(G3,{href:`?download=1&theme=${window.theme}`,children:"Download the Report"})]}),H.jsxs(Nn,{sx:{my:2},children:[H.jsxs(Nn,{sx:{display:"flex",columnGap:.5},children:[H.jsx(Tr,{fontWeight:600,children:"During:"}),H.jsxs(Tr,{children:[IC(r)," - ",IC(n)]})]}),H.jsxs(Nn,{sx:{display:"flex",columnGap:.5},children:[H.jsx(Tr,{fontWeight:600,children:"Target Host:"}),H.jsx(Tr,{children:a||"None"})]}),H.jsxs(Nn,{sx:{display:"flex",columnGap:.5},children:[H.jsx(Tr,{fontWeight:600,children:"Script:"}),H.jsx(Tr,{children:e})]})]}),H.jsxs(Nn,{sx:{display:"flex",flexDirection:"column",rowGap:4},children:[H.jsxs(Nn,{children:[H.jsx(Tr,{component:"h2",mb:1,noWrap:!0,variant:"h4",children:"Request Statistics"}),H.jsx(t4,{stats:s,tableStructure:Qbe})]}),!!u.length&&H.jsxs(Nn,{children:[H.jsx(Tr,{component:"h2",mb:1,noWrap:!0,variant:"h4",children:"Response Time Statistics"}),H.jsx(bne,{responseTimes:u})]}),H.jsxs(Nn,{children:[H.jsx(Tr,{component:"h2",mb:1,noWrap:!0,variant:"h4",children:"Failures Statistics"}),H.jsx(QF,{errors:l})]}),!!o.length&&H.jsxs(Nn,{children:[H.jsx(Tr,{component:"h2",mb:1,noWrap:!0,variant:"h4",children:"Exceptions Statistics"}),H.jsx(qF,{exceptions:o})]}),H.jsxs(Nn,{children:[H.jsx(Tr,{component:"h2",mb:1,noWrap:!0,variant:"h4",children:"Charts"}),H.jsx(zW,{charts:i})]}),H.jsxs(Nn,{children:[H.jsx(Tr,{component:"h2",mb:1,noWrap:!0,variant:"h4",children:"Final ratio"}),H.jsx(BW,{ratios:c})]})]})]})]})})}const tCe=SS.createRoot(document.getElementById("root"));tCe.render(H.jsx(p9,{fallbackRender:g9,children:H.jsx(eCe,{...x9})}));
223
+ `,""):"No data",borderWidth:0},xAxis:{type:"time",min:(e.time||[new Date().toISOString()])[0],startValue:(e.time||[])[0],axisLabel:{formatter:Lbe}},grid:{left:60,right:40},yAxis:Ebe({splitAxis:a,yAxisLabels:o}),series:NW({charts:e,lines:r,scatterplot:s}),color:n,toolbox:{right:10,feature:{dataZoom:{title:{zoom:"Zoom Select",back:"Zoom Reset"},yAxisIndex:!1},saveAsImage:{name:t.replace(/\s+/g,"_").toLowerCase()+"_"+new Date().getTime()/1e3,title:"Download as PNG",emphasis:{iconStyle:{textPosition:"left"}}}}}}),Nbe=e=>({symbol:"none",label:{formatter:t=>`Run #${t.dataIndex+1}`,padding:[0,0,8,0]},data:(e.markers||[]).map(t=>({xAxis:t}))}),zbe=e=>t=>{const{batch:r}=t;if(!r)return;const[{start:n,startValue:i,end:a}]=r,o=n>0&&a<=100||i>0;e.setOption({dataZoom:[{type:"slider",show:o}]})},Bbe=aQ;function Fbe({charts:e,title:t,lines:r,colors:n,chartValueFormatter:i,splitAxis:a,yAxisLabels:o,scatterplot:s,shouldReplaceMergeLines:l=!1}){const[u,c]=V.useState(null),f=Bbe(({theme:{isDarkMode:d}})=>d),h=V.useRef(null);return V.useEffect(()=>{if(!h.current)return;const d=bce(h.current);d.setOption(Obe({charts:e,title:t,lines:r,colors:n,chartValueFormatter:i,splitAxis:a,yAxisLabels:o,scatterplot:s})),d.on("datazoom",zbe(d));const p=()=>d.resize();return window.addEventListener("resize",p),d.group="swarmCharts",Cce("swarmCharts"),c(d),()=>{Tce(d),window.removeEventListener("resize",p)}},[h]),V.useEffect(()=>{const d=r.every(({key:p})=>!!e[p]);u&&d&&u.setOption({series:r.map(({key:p,yAxisIndex:v,...g},y)=>({...g,data:e[p],...a?{yAxisIndex:v||y}:{},...y===0?{markLine:Nbe(e)}:{}}))})},[e,u,r]),V.useEffect(()=>{if(u){const{textColor:d,axisColor:p,backgroundColor:v,splitLine:g}=f?Lz.DARK:Lz.LIGHT;u.setOption({backgroundColor:v,textStyle:{color:d},title:{textStyle:{color:d}},legend:{icon:"circle",inactiveColor:d,textStyle:{color:d}},tooltip:{backgroundColor:v,textStyle:{color:d}},xAxis:{axisLine:{lineStyle:{color:p}}},yAxis:{axisLine:{lineStyle:{color:p}},splitLine:{lineStyle:{color:g}}}})}},[u,f]),V.useEffect(()=>{u&&u.setOption({series:NW({charts:e,lines:r,scatterplot:s})},l?{replaceMerge:["series"]}:void 0)},[r]),H.jsx("div",{ref:h,style:{width:"100%",height:"300px"}})}const Vbe=dl.percentilesToChart?dl.percentilesToChart.map(e=>({name:`${e*100}th percentile`,key:`responseTimePercentile${e}`})):[],Gbe=["#ff9f00","#9966CC","#8A2BE2","#8E4585","#E0B0FF","#C8A2C8","#E6E6FA"],$be=[{title:"Total Requests per Second",lines:[{name:"RPS",key:"currentRps"},{name:"Failures/s",key:"currentFailPerSec"}],colors:["#00ca5a","#ff6d6d"]},{title:"Response Times (ms)",lines:Vbe,colors:Gbe},{title:"Number of Users",lines:[{name:"Number of Users",key:"userCount"}],colors:["#0099ff"]}];function zW({charts:e}){return H.jsx("div",{children:$be.map((t,r)=>H.jsx(Fbe,{...t,charts:e},`swarm-chart-${r}`))})}const Hbe=({ui:{charts:e}})=>({charts:e});Kd(Hbe)(zW);function Wbe(e){return(e*100).toFixed(1)+"%"}function EC({classRatio:e}){return H.jsx("ul",{children:Object.entries(e).map(([t,{ratio:r,tasks:n}])=>H.jsxs("li",{children:[`${Wbe(r)} ${t}`,n&&H.jsx(EC,{classRatio:n})]},`nested-ratio-${t}`))})}function BW({ratios:{perClass:e,total:t}}){return!e&&!t?null:H.jsxs("div",{children:[e&&H.jsxs(H.Fragment,{children:[H.jsx("h3",{children:"Ratio Per Class"}),H.jsx(EC,{classRatio:e})]}),t&&H.jsxs(H.Fragment,{children:[H.jsx("h3",{children:"Total Ratio"}),H.jsx(EC,{classRatio:t})]})]})}const Ube=({ui:{ratios:e}})=>({ratios:e});Kd(Ube)(BW);const xS={DARK:"dark",LIGHT:"light"},FW=localStorage.theme===xS.DARK||!("theme"in localStorage)&&window.matchMedia("(prefers-color-scheme: dark)").matches?xS.DARK:xS.LIGHT,jbe={isDarkMode:!1},Ybe=Oq({name:"theme",initialState:jbe,reducers:{setIsDarkMode:(e,{payload:t})=>{e.isDarkMode=t}}}),Xbe=Ybe.reducer,Zbe=e=>ZT({palette:{mode:e,primary:{main:"#15803d"},success:{main:"#00C853"}},components:{MuiCssBaseline:{styleOverrides:{":root":{"--footer-height":"40px"}}}}}),Kbe=Zbe(window.theme||FW),qbe=(window.theme||FW)==="dark",Qbe=[{key:"method",title:"Type"},{key:"name",title:"Name"},{key:"numRequests",title:"# Requests"},{key:"numFailures",title:"# Fails"},{key:"avgResponseTime",title:"Average (ms)",round:2},{key:"minResponseTime",title:"Min (ms)"},{key:"maxResponseTime",title:"Max (ms)"},{key:"avgContentLength",title:"Average size (bytes)",round:2},{key:"totalRps",title:"RPS",round:2},{key:"totalFailPerSec",title:"Failures/s",round:2}],Jbe=Aq({reducer:W3({theme:Xbe}),preloadedState:{theme:{isDarkMode:qbe}}});function eCe({locustfile:e,showDownloadLink:t,startTime:r,endTime:n,duration:i,charts:a,host:o,exceptionsStatistics:s,requestsStatistics:l,failuresStatistics:u,responseTimeStatistics:c,tasks:f}){return H.jsx(ZQ,{store:Jbe,children:H.jsxs($Y,{theme:Kbe,children:[H.jsx(TZ,{}),H.jsxs(SZ,{maxWidth:"lg",sx:{my:4},children:[H.jsxs(Nn,{sx:{display:"flex",justifyContent:"space-between",alignItems:"flex-end"},children:[H.jsx(Tr,{component:"h1",noWrap:!0,sx:{fontWeight:700},variant:"h3",children:"Locust Test Report"}),t&&H.jsx(G3,{href:`?download=1&theme=${window.theme}`,children:"Download the Report"})]}),H.jsxs(Nn,{sx:{my:2},children:[H.jsxs(Nn,{sx:{display:"flex",columnGap:.5},children:[H.jsx(Tr,{fontWeight:600,children:"During:"}),H.jsxs(Tr,{children:[IC(r)," - ",IC(n)," (",i,")"]})]}),H.jsxs(Nn,{sx:{display:"flex",columnGap:.5},children:[H.jsx(Tr,{fontWeight:600,children:"Target Host:"}),H.jsx(Tr,{children:o||"None"})]}),H.jsxs(Nn,{sx:{display:"flex",columnGap:.5},children:[H.jsx(Tr,{fontWeight:600,children:"Script:"}),H.jsx(Tr,{children:e})]})]}),H.jsxs(Nn,{sx:{display:"flex",flexDirection:"column",rowGap:4},children:[H.jsxs(Nn,{children:[H.jsx(Tr,{component:"h2",mb:1,noWrap:!0,variant:"h4",children:"Request Statistics"}),H.jsx(t4,{stats:l,tableStructure:Qbe})]}),!!c.length&&H.jsxs(Nn,{children:[H.jsx(Tr,{component:"h2",mb:1,noWrap:!0,variant:"h4",children:"Response Time Statistics"}),H.jsx(bne,{responseTimes:c})]}),H.jsxs(Nn,{children:[H.jsx(Tr,{component:"h2",mb:1,noWrap:!0,variant:"h4",children:"Failures Statistics"}),H.jsx(QF,{errors:u})]}),!!s.length&&H.jsxs(Nn,{children:[H.jsx(Tr,{component:"h2",mb:1,noWrap:!0,variant:"h4",children:"Exceptions Statistics"}),H.jsx(qF,{exceptions:s})]}),H.jsxs(Nn,{children:[H.jsx(Tr,{component:"h2",mb:1,noWrap:!0,variant:"h4",children:"Charts"}),H.jsx(zW,{charts:a})]}),H.jsxs(Nn,{children:[H.jsx(Tr,{component:"h2",mb:1,noWrap:!0,variant:"h4",children:"Final ratio"}),H.jsx(BW,{ratios:f})]})]})]})]})})}const tCe=SS.createRoot(document.getElementById("root"));tCe.render(H.jsx(p9,{fallbackRender:g9,children:H.jsx(eCe,{...x9})}));
224
224
  </script>
225
225
  </head>
226
226
  {% endraw %}
@@ -513,6 +513,20 @@ files = [
513
513
  {file = "docutils-0.18.1.tar.gz", hash = "sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06"},
514
514
  ]
515
515
 
516
+ [[package]]
517
+ name = "exceptiongroup"
518
+ version = "1.2.2"
519
+ description = "Backport of PEP 654 (exception groups)"
520
+ optional = false
521
+ python-versions = ">=3.7"
522
+ files = [
523
+ {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
524
+ {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
525
+ ]
526
+
527
+ [package.extras]
528
+ test = ["pytest (>=6)"]
529
+
516
530
  [[package]]
517
531
  name = "filelock"
518
532
  version = "3.16.1"
@@ -872,6 +886,17 @@ perf = ["ipython"]
872
886
  test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"]
873
887
  type = ["pytest-mypy"]
874
888
 
889
+ [[package]]
890
+ name = "iniconfig"
891
+ version = "2.0.0"
892
+ description = "brain-dead simple config-ini parsing"
893
+ optional = false
894
+ python-versions = ">=3.7"
895
+ files = [
896
+ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
897
+ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
898
+ ]
899
+
875
900
  [[package]]
876
901
  name = "itsdangerous"
877
902
  version = "2.2.0"
@@ -1632,6 +1657,28 @@ lxml = ">=2.1"
1632
1657
  [package.extras]
1633
1658
  test = ["pytest", "pytest-cov", "requests", "webob", "webtest"]
1634
1659
 
1660
+ [[package]]
1661
+ name = "pytest"
1662
+ version = "8.3.3"
1663
+ description = "pytest: simple powerful testing with Python"
1664
+ optional = false
1665
+ python-versions = ">=3.8"
1666
+ files = [
1667
+ {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"},
1668
+ {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"},
1669
+ ]
1670
+
1671
+ [package.dependencies]
1672
+ colorama = {version = "*", markers = "sys_platform == \"win32\""}
1673
+ exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
1674
+ iniconfig = "*"
1675
+ packaging = "*"
1676
+ pluggy = ">=1.5,<2"
1677
+ tomli = {version = ">=1", markers = "python_version < \"3.11\""}
1678
+
1679
+ [package.extras]
1680
+ dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
1681
+
1635
1682
  [[package]]
1636
1683
  name = "pywin32"
1637
1684
  version = "308"
@@ -2474,4 +2521,4 @@ testing = ["coverage[toml]", "zope.event", "zope.testing"]
2474
2521
  [metadata]
2475
2522
  lock-version = "2.0"
2476
2523
  python-versions = ">=3.9"
2477
- content-hash = "9e58a176725655fe7b74069776ba86492cdc74c7f34b9c2a1788da9fba924e8e"
2524
+ content-hash = "87823fddd6f62da6dd45b8f33d7e7cf6b707ea6c8bb6e06d1f7753d6ec6c6e83"
@@ -5,7 +5,7 @@ build-backend = "poetry_dynamic_versioning.backend"
5
5
  [tool.poetry]
6
6
  name = "locust"
7
7
  description = "Developer-friendly load testing framework"
8
- version = "2.32.2.dev7"
8
+ version = "2.32.2.dev19"
9
9
  license = "MIT"
10
10
  readme = "README.md"
11
11
  authors = ["Jonatan Heyman", "Lars Holmberg"]
@@ -156,6 +156,7 @@ retry = "^0.9.2"
156
156
  ruff = "0.3.7"
157
157
  tox = "^4.16.0"
158
158
  types-requests = "^2.32.0.20240622"
159
+ pytest = "^8.3.3"
159
160
 
160
161
  [tool.poetry.group.docs]
161
162
  optional = true
@@ -1,5 +0,0 @@
1
- from datetime import datetime, timezone
2
-
3
-
4
- def format_utc_timestamp(unix_timestamp):
5
- return datetime.fromtimestamp(unix_timestamp, timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
File without changes
File without changes