Skip to content

Commit

Permalink
Refactor html.py and web.py to pass percentiles_to_chart for modern-ui
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewbaldwin44 committed Dec 7, 2023
1 parent 2a2cbcf commit 4334e80
Show file tree
Hide file tree
Showing 15 changed files with 142 additions and 92 deletions.
3 changes: 1 addition & 2 deletions locust/html.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,7 @@ def get_html_report(
"show_download_link": show_download_link,
"locustfile": escape(str(environment.locustfile)),
"tasks": task_data,
"percentile1": stats_module.PERCENTILES_TO_CHART[0],
"percentile2": stats_module.PERCENTILES_TO_CHART[1],
"percentiles_to_chart": stats_module.PERCENTILES_TO_CHART,
},
theme=theme,
static_js="\n".join(static_js),
Expand Down
7 changes: 7 additions & 0 deletions locust/stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -917,6 +917,12 @@ def stats_history(runner: "Runner") -> None:
if not stats.total.use_response_times_cache:
break
if runner.state != "stopped":
current_response_time_percentiles = {
f"response_time_percentile_{percentile}": stats.total.get_current_response_time_percentile(percentile)
or 0
for percentile in PERCENTILES_TO_CHART
}

r = {
"time": datetime.datetime.now(tz=datetime.timezone.utc).strftime("%H:%M:%S"),
"current_rps": stats.total.current_rps or 0,
Expand All @@ -925,6 +931,7 @@ def stats_history(runner: "Runner") -> None:
or 0,
"response_time_percentile_2": stats.total.get_current_response_time_percentile(PERCENTILES_TO_CHART[1])
or 0,
"current_response_time_percentiles": current_response_time_percentiles,
"user_count": runner.user_count or 0,
}
stats.history.append(r)
Expand Down
42 changes: 30 additions & 12 deletions locust/web.py
Original file line number Diff line number Diff line change
Expand Up @@ -408,16 +408,25 @@ def request_stats() -> Response:
report["total_rps"] = total_stats["current_rps"]
report["total_fail_per_sec"] = total_stats["current_fail_per_sec"]
report["fail_ratio"] = environment.runner.stats.total.fail_ratio
report[
"current_response_time_percentile_1"
] = environment.runner.stats.total.get_current_response_time_percentile(
stats_module.PERCENTILES_TO_CHART[0]
)
report[
"current_response_time_percentile_2"
] = environment.runner.stats.total.get_current_response_time_percentile(
stats_module.PERCENTILES_TO_CHART[1]
)

if self.modern_ui:
report["current_response_time_percentiles"] = {
f"response_time_percentile_{percentile}": environment.runner.stats.total.get_current_response_time_percentile(
percentile
)
for percentile in stats_module.PERCENTILES_TO_CHART
}
else:
report[
"current_response_time_percentile_1"
] = environment.runner.stats.total.get_current_response_time_percentile(
stats_module.PERCENTILES_TO_CHART[0]
)
report[
"current_response_time_percentile_2"
] = environment.runner.stats.total.get_current_response_time_percentile(
stats_module.PERCENTILES_TO_CHART[1]
)

if isinstance(environment.runner, MasterRunner):
workers = []
Expand Down Expand Up @@ -580,6 +589,16 @@ def update_template_args(self):
if self.environment.available_shape_classes:
available_shape_classes += sorted(self.environment.available_shape_classes.keys())

if self.modern_ui:
percentiles = {
"percentiles_to_chart": stats_module.PERCENTILES_TO_CHART,
}
else:
percentiles = {
"percentile1": stats_module.PERCENTILES_TO_CHART[0],
"percentile2": stats_module.PERCENTILES_TO_CHART[1],
}

self.template_args = {
"locustfile": self.environment.locustfile,
"state": self.environment.runner.state,
Expand All @@ -603,8 +622,7 @@ def update_template_args(self):
"show_userclass_picker": self.userclass_picker_is_active,
"available_user_classes": available_user_classes,
"available_shape_classes": available_shape_classes,
"percentile1": stats_module.PERCENTILES_TO_CHART[0],
"percentile2": stats_module.PERCENTILES_TO_CHART[1],
**percentiles,
}

def _update_shape_class(self, shape_class_name):
Expand Down

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion locust/webui/dist/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<meta name="theme-color" content="#000000" />

<title>Locust</title>
<script type="module" crossorigin src="/assets/index-e0dbccfe.js"></script>
<script type="module" crossorigin src="/assets/index-41a98b45.js"></script>
</head>
<body>
<div id="root"></div>
Expand Down
15 changes: 5 additions & 10 deletions locust/webui/src/components/SwarmCharts/SwarmCharts.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { connect } from 'react-redux';

import LineChart, { ILineChartProps } from 'components/LineChart/LineChart';
import { swarmTemplateArgs } from 'constants/swarm';
import { IRootState } from 'redux/store';
import { ICharts } from 'types/ui.types';

Expand All @@ -15,16 +16,10 @@ const availableSwarmCharts: ILineChartProps[] = [
},
{
title: 'Response Times (ms)',
lines: [
{
name: `${window.templateArgs.percentile1 * 100}th percentile`,
key: 'responseTimePercentile1',
},
{
name: `${window.templateArgs.percentile2 * 100}th percentile`,
key: 'responseTimePercentile2',
},
],
lines: swarmTemplateArgs.percentilesToChart.map(percentile => ({
name: `${percentile * 100}th percentile`,
key: `responseTimePercentile${percentile}`,
})),
},
{
title: 'Number of Users',
Expand Down
6 changes: 2 additions & 4 deletions locust/webui/src/hooks/useSwarmUi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,14 @@ export default function useSwarmUi() {
}

const {
currentResponseTimePercentiles,
extendedStats,
stats,
errors,
totalRps,
totalFailPerSec,
failRatio,
workers,
currentResponseTimePercentile1,
currentResponseTimePercentile2,
userCount,
} = statsData;

Expand All @@ -57,10 +56,9 @@ export default function useSwarmUi() {
const totalFailureRatioRounded = roundToDecimalPlaces(failRatio * 100);

const newChartEntry = {
...currentResponseTimePercentiles,
currentRps: totalRpsRounded,
currentFailPerSec: totalFailPerSecRounded,
responseTimePercentile1: currentResponseTimePercentile1,
responseTimePercentile2: currentResponseTimePercentile2,
userCount: userCount,
time,
};
Expand Down
6 changes: 5 additions & 1 deletion locust/webui/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
if ((swarmTemplateArgs as IReportTemplateArgs).isReport) {
const reportProps = {
...(swarmTemplateArgs as IReportTemplateArgs),
charts: swarmTemplateArgs.history.reduce(updateArraysAtProps, {}) as ICharts,
charts: swarmTemplateArgs.history.reduce(
(charts, { currentResponseTimePercentiles, ...history }) =>
updateArraysAtProps(charts, { ...currentResponseTimePercentiles, ...history }),
{} as ICharts,
) as ICharts,
};
root.render(<Report {...reportProps} />);
} else {
Expand Down
9 changes: 4 additions & 5 deletions locust/webui/src/redux/slice/swarm.slice.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';

import { swarmTemplateArgs } from 'constants/swarm';
import { updateStateWithPayload } from 'redux/utils';
import { IExtraOptions, History } from 'types/swarm.types';
import { IExtraOptions, IHistory } from 'types/swarm.types';
import { ITab } from 'types/tab.types';
import { ITableStructure } from 'types/table.types';
import { camelCaseKeys } from 'utils/string';

export interface ISwarmState {
availableShapeClasses: string[];
Expand All @@ -13,15 +13,14 @@ export interface ISwarmState {
extendedTabs?: ITab[];
extendedTables?: { key: string; structure: ITableStructure[] }[];
extendedCsvFiles?: { href: string; title: string }[];
history: History[];
history: IHistory[];
host: string;
isDistributed: boolean;
isShape: boolean | null;
locustfile: string;
numUsers: number | null;
overrideHostWarning: boolean;
percentile1: number;
percentile2: number;
percentilesToChart: number[];
runTime?: number;
showUserclassPicker: boolean;
spawnRate: number | null;
Expand Down
28 changes: 18 additions & 10 deletions locust/webui/src/redux/slice/tests/ui.slice.test.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
import { describe, expect, test } from 'vitest';

import uiSlice, { IUiState, UiAction, uiActions } from 'redux/slice/ui.slice';
import { percentilesToChart } from 'test/mocks/swarmState.mock';
import { ICharts, ISwarmRatios } from 'types/ui.types';

const responseTimePercentileKey1 =
`responseTimePercentile${percentilesToChart[0]}` as `responseTimePercentile${number}`;
const responseTimePercentileKey2 =
`responseTimePercentile${percentilesToChart[1]}` as `responseTimePercentile${number}`;

const initialState = {
totalRps: 0,
failRatio: 0,
stats: [],
errors: [],
exceptions: [],
charts: {
[responseTimePercentileKey1]: [],
[responseTimePercentileKey1]: [],
currentRps: [],
currentFailPerSec: [],
responseTimePercentile1: [],
Expand Down Expand Up @@ -39,8 +47,8 @@ describe('uiSlice', () => {
const action = uiActions.updateCharts({
currentRps: 5,
currentFailPerSec: 1,
responseTimePercentile1: 0.4,
responseTimePercentile2: 0.2,
[responseTimePercentileKey1]: 0.4,
[responseTimePercentileKey2]: 0.2,
userCount: 2,
time: '10:10:10',
});
Expand All @@ -50,8 +58,8 @@ describe('uiSlice', () => {

expect(charts.currentRps[0]).toBe(5);
expect(charts.currentFailPerSec[0]).toBe(1);
expect(charts.responseTimePercentile1[0]).toBe(0.4);
expect(charts.responseTimePercentile2[0]).toBe(0.2);
expect(charts[responseTimePercentileKey1][0]).toBe(0.4);
expect(charts[responseTimePercentileKey2][0]).toBe(0.2);
expect(charts.userCount[0]).toBe(2);
expect(charts.time[0]).toBe('10:10:10');
});
Expand All @@ -60,8 +68,8 @@ describe('uiSlice', () => {
const action = uiActions.updateCharts({
currentRps: 5,
currentFailPerSec: 1,
responseTimePercentile1: 0.4,
responseTimePercentile2: 0.2,
[responseTimePercentileKey1]: 0.4,
[responseTimePercentileKey2]: 0.2,
userCount: 2,
time: '10:10:10',
});
Expand All @@ -73,8 +81,8 @@ describe('uiSlice', () => {

expect(charts.currentRps).toEqual([5, 5]);
expect(charts.currentFailPerSec).toEqual([1, 1]);
expect(charts.responseTimePercentile1).toEqual([0.4, 0.4]);
expect(charts.responseTimePercentile2).toEqual([0.2, 0.2]);
expect(charts[responseTimePercentileKey1]).toEqual([0.4, 0.4]);
expect(charts[responseTimePercentileKey2]).toEqual([0.2, 0.2]);
expect(charts.userCount).toEqual([2, 2]);
expect(charts.time).toEqual(['10:10:10', '10:10:10']);
});
Expand All @@ -99,8 +107,8 @@ describe('uiSlice', () => {
// Add space between runs
expect(charts.currentRps[0]).toEqual({ value: null });
expect(charts.currentFailPerSec[0]).toEqual({ value: null });
expect(charts.responseTimePercentile1[0]).toEqual({ value: null });
expect(charts.responseTimePercentile2[0]).toEqual({ value: null });
expect(charts[responseTimePercentileKey1][0]).toEqual({ value: null });
expect(charts[responseTimePercentileKey2][0]).toEqual({ value: null });
expect(charts.userCount[0]).toEqual({ value: null });
});
});
12 changes: 10 additions & 2 deletions locust/webui/src/redux/slice/ui.slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,20 @@ const initialState = {
userCount: 0,
};

const percentileNullValues = swarmTemplateArgs.percentilesToChart.reduce(
(percentilesNullValue, percentile) => ({
...percentilesNullValue,
[`responseTimePercentile${percentile}`]: { value: null },
}),
{},
);

const addSpaceToChartsBetweenTests = (charts: ICharts) => {
return updateArraysAtProps(charts, {
...percentileNullValues,
currentRps: { value: null },
currentFailPerSec: { value: null },
responseTimePercentile1: { value: null },
responseTimePercentile2: { value: null },
totalAvgResponseTime: { value: null },
userCount: { value: null },
time: '',
});
Expand Down
8 changes: 6 additions & 2 deletions locust/webui/src/test/mocks/statsRequest.mock.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export const statsResponseMock = {
current_response_time_percentile_1: 1,
current_response_time_percentile_2: 1,
current_response_time_percentiles: {
'response_time_percentile_0.5': 2,
'response_time_percentile_0.95': 2,
},
errors: [
{
error: 'ConnectionRefusedError(111, &#x27;Connection refused&#x27;)',
Expand Down Expand Up @@ -142,6 +144,8 @@ export const getStatsResponseTransformed = () => ({
exceptions: exceptionsResponseMock.exceptions,
extendedStats: undefined,
charts: {
'responseTimePercentile0.5': [2],
'responseTimePercentile0.95': [2],
currentRps: [1932.5],
currentFailPerSec: [1932.5],
responseTimePercentile1: [1],
Expand Down
3 changes: 3 additions & 0 deletions locust/webui/src/test/mocks/swarmState.mock.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export const percentilesToChart = [0.5, 0.95];

export const swarmStateMock = {
availableShapeClasses: ['Default'],
availableUserClasses: ['ExampleUser'],
Expand All @@ -14,6 +16,7 @@ export const swarmStateMock = {
showUserclassPicker: false,
spawnRate: null,
state: 'ready',
percentilesToChart: percentilesToChart,
statsHistoryEnabled: false,
tasks: '{}',
userCount: 0,
Expand Down
15 changes: 11 additions & 4 deletions locust/webui/src/types/swarm.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,15 @@ export interface IExtraOptions {
[key: string]: IExtraOptionParameter;
}

export type History = Omit<ICharts, 'markers'>;
export interface IHistory {
currentRps: number;
currentFailPerSec: number;
userCount: number;
time: string;
currentResponseTimePercentiles: {
[key: `responseTimePercentile${number}`]: number | null;
};
}

export interface IReport {
locustfile: string;
Expand All @@ -35,8 +43,7 @@ export interface IReport {
}

export interface IReportTemplateArgs extends IReport {
history: ICharts[];
history: IHistory[];
isReport?: boolean;
percentile1: number;
percentile2: number;
percentilesToChart: number[];
}
8 changes: 4 additions & 4 deletions locust/webui/src/types/ui.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,7 @@ interface NullChartValue {
export interface ICharts {
currentRps: (number | NullChartValue)[];
currentFailPerSec: (number | NullChartValue)[];
responseTimePercentile1: (number | null | NullChartValue)[];
responseTimePercentile2: (number | null | NullChartValue)[];
[key: `responseTimePercentile${number}`]: (number | null | NullChartValue)[];
userCount: (number | NullChartValue)[];
time: string[];
markers?: string[];
Expand Down Expand Up @@ -95,9 +94,10 @@ export interface IStatsResponse {
workers: ISwarmWorker[];
totalRps: number;
totalFailPerSec: number;
currentResponseTimePercentiles: {
[key: `responseTimePercentile${number}`]: number | null;
};
failRatio: number;
currentResponseTimePercentile1: number | null;
currentResponseTimePercentile2: number | null;
userCount: number;
}

Expand Down

0 comments on commit 4334e80

Please sign in to comment.