diff --git a/locust/stats.py b/locust/stats.py index 16e6df91b3..52c76767b8 100644 --- a/locust/stats.py +++ b/locust/stats.py @@ -919,6 +919,7 @@ def update_stats_history(runner: Runner) -> None: "current_fail_per_sec": [timestamp, stats.total.current_fail_per_sec or 0], "total_avg_response_time": [timestamp, proper_round(stats.total.avg_response_time, digits=2)], "user_count": [timestamp, runner.user_count or 0], + "time": timestamp, } stats.history.append(r) diff --git a/locust/web.py b/locust/web.py index 072a2e385f..6c4d54a6b8 100644 --- a/locust/web.py +++ b/locust/web.py @@ -618,8 +618,6 @@ def update_template_args(self): else None ) - start_time = format_utc_timestamp(stats.start_time) - self.template_args = { "locustfile": self.environment.locustfile, "state": self.environment.runner.state, @@ -637,7 +635,6 @@ def update_template_args(self): and not (self.userclass_picker_is_active or self.environment.shape_class.use_common_options) ), "stats_history_enabled": options and options.stats_history_enabled, - "start_time": start_time, "tasks": dumps({}), "extra_options": extra_options, "run_time": options and options.run_time, diff --git a/locust/webui/src/components/LineChart/LineChart.tsx b/locust/webui/src/components/LineChart/LineChart.tsx index 04d6fafe51..4454cea644 100644 --- a/locust/webui/src/components/LineChart/LineChart.tsx +++ b/locust/webui/src/components/LineChart/LineChart.tsx @@ -2,7 +2,11 @@ import { useEffect, useState, useRef } from 'react'; import { init, dispose, ECharts, connect } from 'echarts'; import { CHART_THEME } from 'components/LineChart/LineChart.constants'; -import { ILineChart, ILineChartMarkers } from 'components/LineChart/LineChart.types'; +import { + ILineChart, + ILineChartMarkers, + ILineChartTimeAxis, +} from 'components/LineChart/LineChart.types'; import { createMarkLine, createOptions, @@ -11,7 +15,9 @@ import { } from 'components/LineChart/LineChart.utils'; import { useSelector } from 'redux/hooks'; -interface IBaseChartType extends ILineChartMarkers {} +interface IBaseChartType extends ILineChartMarkers, ILineChartTimeAxis { + [key: string]: any; +} export default function LineChart({ charts, diff --git a/locust/webui/src/components/LineChart/LineChart.types.ts b/locust/webui/src/components/LineChart/LineChart.types.ts index c04567181a..ddd73782b7 100644 --- a/locust/webui/src/components/LineChart/LineChart.types.ts +++ b/locust/webui/src/components/LineChart/LineChart.types.ts @@ -25,6 +25,10 @@ export interface ILineChartMarkers { markers?: string[]; } +export interface ILineChartTimeAxis { + time: string[]; +} + export interface ILineChartTooltipFormatterParams { axisValue: string; color: string; diff --git a/locust/webui/src/components/LineChart/LineChart.utils.ts b/locust/webui/src/components/LineChart/LineChart.utils.ts index 2e134e398a..5a7741c3ae 100644 --- a/locust/webui/src/components/LineChart/LineChart.utils.ts +++ b/locust/webui/src/components/LineChart/LineChart.utils.ts @@ -12,7 +12,6 @@ import { ILineChartZoomEvent, ILineChartTooltipFormatterParams, } from 'components/LineChart/LineChart.types'; -import { swarmTemplateArgs } from 'constants/swarm'; import { ICharts } from 'types/ui.types'; import { formatLocaleString } from 'utils/date'; import { padStart } from 'utils/number'; @@ -81,7 +80,7 @@ const renderChartTooltipValue = ({ return Array.isArray(value) ? value[1] : value; }; -export const createOptions = ({ +export const createOptions = >({ charts, title, lines, @@ -126,7 +125,7 @@ export const createOptions = ({ }, xAxis: { type: 'time', - startValue: swarmTemplateArgs.startTime, + startValue: charts.time[0], axisLabel: { formatter: formatTimeAxis, }, diff --git a/locust/webui/src/components/LineChart/tests/LineChart.mocks.ts b/locust/webui/src/components/LineChart/tests/LineChart.mocks.ts index 2f41b0152e..9bee9d3342 100644 --- a/locust/webui/src/components/LineChart/tests/LineChart.mocks.ts +++ b/locust/webui/src/components/LineChart/tests/LineChart.mocks.ts @@ -1,6 +1,6 @@ import { ICharts } from 'types/ui.types'; -export type MockChartType = Pick; +export type MockChartType = Pick; export const mockChartLines = [ { name: 'RPS', key: 'currentRps' as keyof MockChartType }, @@ -22,6 +22,13 @@ export const mockCharts: MockChartType = { ['Tue, 06 Aug 2024 11:33:12 GMT', 0], ['Tue, 06 Aug 2024 11:33:14 GMT', 0], ], + time: [ + 'Tue, 06 Aug 2024 11:33:02 GMT', + 'Tue, 06 Aug 2024 11:33:08 GMT', + 'Tue, 06 Aug 2024 11:33:10 GMT', + 'Tue, 06 Aug 2024 11:33:12 GMT', + 'Tue, 06 Aug 2024 11:33:14 GMT', + ], }; export const mockTimestamps = [ diff --git a/locust/webui/src/components/LineChart/tests/LineChartUtils.test.tsx b/locust/webui/src/components/LineChart/tests/LineChartUtils.test.tsx index a60291de55..269c604d7b 100644 --- a/locust/webui/src/components/LineChart/tests/LineChartUtils.test.tsx +++ b/locust/webui/src/components/LineChart/tests/LineChartUtils.test.tsx @@ -18,7 +18,6 @@ import { mockTimestamps, mockTooltipParams, } from 'components/LineChart/tests/LineChart.mocks'; -import { swarmStateMock } from 'test/mocks/swarmState.mock'; import { formatLocaleString } from 'utils/date'; const removeWhitespace = (string: string) => string.replace(/\s+/g, ''); @@ -60,7 +59,7 @@ describe('createOptions', () => { expect(options.title.text).toBe('Test Chart'); expect(options.xAxis.type).toBe('time'); - expect(options.xAxis.startValue).toBe(swarmStateMock.startTime); + expect(options.xAxis.startValue).toBe(mockCharts.time[0]); expect(options.yAxis).toEqual(defaultYAxis); expect(options.series).toEqual(mockSeriesData); expect(options.color).toEqual(['#fff']); @@ -188,7 +187,7 @@ describe('createOptions', () => { expect(options.title.text).toBe('Test Chart'); expect(options.yAxis).toEqual(defaultYAxis); expect(options.xAxis.type).toBe('time'); - expect(options.xAxis.startValue).toBe(swarmStateMock.startTime); + expect(options.xAxis.startValue).toBe(mockCharts.time[0]); expect(options.series).toEqual(mockScatterplotSeriesData); expect(options.color).toEqual(['#fff']); }); diff --git a/locust/webui/src/hooks/useSwarmUi.ts b/locust/webui/src/hooks/useSwarmUi.ts index a0f37d07ba..1fbfcadd87 100644 --- a/locust/webui/src/hooks/useSwarmUi.ts +++ b/locust/webui/src/hooks/useSwarmUi.ts @@ -70,6 +70,7 @@ export default function useSwarmUi() { currentFailPerSec: [time, totalFailPerSecRounded], totalAvgResponseTime: [time, roundToDecimalPlaces(totalAvgResponseTime, 2)], userCount: [time, userCount], + time, }; setUi({ diff --git a/locust/webui/src/redux/slice/swarm.slice.ts b/locust/webui/src/redux/slice/swarm.slice.ts index 83560b6917..718238c627 100644 --- a/locust/webui/src/redux/slice/swarm.slice.ts +++ b/locust/webui/src/redux/slice/swarm.slice.ts @@ -26,7 +26,6 @@ export interface ISwarmState { runTime?: string | number; showUserclassPicker: boolean; spawnRate: number | null; - startTime: string; state: string; statsHistoryEnabled: boolean; tasks: string; diff --git a/locust/webui/src/redux/slice/tests/ui.slice.test.ts b/locust/webui/src/redux/slice/tests/ui.slice.test.ts index dcc9b86f57..dc87e2837c 100644 --- a/locust/webui/src/redux/slice/tests/ui.slice.test.ts +++ b/locust/webui/src/redux/slice/tests/ui.slice.test.ts @@ -1,7 +1,7 @@ import { describe, expect, test } from 'vitest'; import uiSlice, { IUiState, UiAction, uiActions } from 'redux/slice/ui.slice'; -import { percentilesToChart, swarmStateMock } from 'test/mocks/swarmState.mock'; +import { percentilesToChart } from 'test/mocks/swarmState.mock'; import { ICharts, ISwarmRatios } from 'types/ui.types'; const responseTimePercentileKey1 = @@ -12,6 +12,7 @@ const responseTimePercentileKey2 = const initialState = { totalRps: 0, failRatio: 0, + startTime: '', stats: [], errors: [], exceptions: [], @@ -22,6 +23,7 @@ const initialState = { currentFailPerSec: [], totalAvgResponseTime: [], userCount: [], + time: [], }, ratios: {} as ISwarmRatios, userCount: 0, @@ -88,6 +90,7 @@ describe('uiSlice', () => { ...initialState, charts: { ...initialState.charts, + time: ['10:10:10'], }, }, action, @@ -95,7 +98,7 @@ describe('uiSlice', () => { const charts = nextState.charts as ICharts; - expect(charts.markers).toEqual([swarmStateMock.startTime, '20:20:20']); + expect(charts.markers).toEqual(['10:10:10', '20:20:20']); // Add space between runs expect(charts.currentRps[0]).toEqual({ value: null }); diff --git a/locust/webui/src/redux/slice/ui.slice.ts b/locust/webui/src/redux/slice/ui.slice.ts index f8e1ffaeee..c85c1d60f6 100644 --- a/locust/webui/src/redux/slice/ui.slice.ts +++ b/locust/webui/src/redux/slice/ui.slice.ts @@ -17,6 +17,7 @@ export interface IUiState { extendedStats?: IExtendedStat[]; totalRps: number; failRatio: number; + startTime: string; stats: ISwarmStat[]; errors: ISwarmError[]; workers?: ISwarmWorker[]; @@ -31,6 +32,7 @@ export type UiAction = PayloadAction>; const initialState = { totalRps: 0, failRatio: 0, + startTime: '', stats: [] as ISwarmStat[], errors: [] as ISwarmError[], exceptions: [] as ISwarmException[], @@ -54,6 +56,7 @@ const addSpaceToChartsBetweenTests = (charts: ICharts) => { currentFailPerSec: { value: null }, totalAvgResponseTime: { value: null }, userCount: { value: null }, + time: '', }); }; @@ -73,7 +76,7 @@ const uiSlice = createSlice({ ...addSpaceToChartsBetweenTests(state.charts as ICharts), markers: (state.charts as ICharts).markers ? [...((state.charts as ICharts).markers as string[]), payload] - : [swarmTemplateArgs.startTime, payload], + : [state.charts.time[0], payload], }, }; }, diff --git a/locust/webui/src/test/mocks/statsRequest.mock.ts b/locust/webui/src/test/mocks/statsRequest.mock.ts index 0ad4dfb08e..7ccc3e3ea6 100644 --- a/locust/webui/src/test/mocks/statsRequest.mock.ts +++ b/locust/webui/src/test/mocks/statsRequest.mock.ts @@ -155,6 +155,7 @@ export const statsResponseTransformed = { currentFailPerSec: [[mockDate.toISOString(), 1932.5]], userCount: [[mockDate.toISOString(), 1]], totalAvgResponseTime: [[mockDate.toISOString(), 0.41]], + time: [], } as ICharts, ratios: { perClass: { diff --git a/locust/webui/src/test/mocks/swarmState.mock.ts b/locust/webui/src/test/mocks/swarmState.mock.ts index 9a409d06b0..92caac14a1 100644 --- a/locust/webui/src/test/mocks/swarmState.mock.ts +++ b/locust/webui/src/test/mocks/swarmState.mock.ts @@ -22,7 +22,6 @@ export const swarmStateMock = { showUserclassPicker: false, spawnRate: null, state: 'ready', - startTime: '10:10:10', percentilesToChart: percentilesToChart, statsHistoryEnabled: false, tasks: '{}', @@ -48,5 +47,6 @@ export const swarmReportMock: IReport = { currentFailPerSec: [['', 0]], totalAvgResponseTime: [['', 0]], userCount: [['', 0]], + time: [], }, }; diff --git a/locust/webui/src/types/swarm.types.ts b/locust/webui/src/types/swarm.types.ts index 94eb963936..4f899cce78 100644 --- a/locust/webui/src/types/swarm.types.ts +++ b/locust/webui/src/types/swarm.types.ts @@ -26,6 +26,7 @@ export interface IHistory { [key: `responseTimePercentile${number}`]: [string, number | null]; }; totalAvgResponseTime: [string, number]; + time: string; } export interface IReport { diff --git a/locust/webui/src/types/ui.types.ts b/locust/webui/src/types/ui.types.ts index a7a2f4a720..7311304f73 100644 --- a/locust/webui/src/types/ui.types.ts +++ b/locust/webui/src/types/ui.types.ts @@ -60,6 +60,7 @@ export interface ICharts extends ILineChartMarkers { [key: `responseTimePercentile${number}`]: ([string, number | null] | NullChartValue)[]; totalAvgResponseTime: ([string, number] | NullChartValue)[]; userCount: ([string, number] | NullChartValue)[]; + time: string[]; } export interface IClassRatio {