Skip to content

Commit 2125744

Browse files
committed
feat: locustio#2955 Allow to exclude some statistics from aggregation
1 parent ab5faf3 commit 2125744

File tree

6 files changed

+47
-4
lines changed

6 files changed

+47
-4
lines changed

locust/argument_parser.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -680,6 +680,15 @@ def setup_parser_arguments(parser):
680680
action="store_true",
681681
help="Prints the final stats in JSON format to stdout. Useful for parsing the results in other programs/scripts. Use together with --headless and --skip-log for an output only with the json data.",
682682
)
683+
stats_group.add_argument(
684+
"--exclude-from-aggregation",
685+
type=str,
686+
metavar="<str>",
687+
default="",
688+
dest="exclude_from_aggregation",
689+
env_var="LOCUST_EXCLUDE_FROM_AGGREGATION",
690+
help='Exclude from "Aggregated" stats matching method or name. Regexp is allowed.',
691+
)
683692

684693
log_group = parser.add_argument_group("Logging options")
685694
log_group.add_argument(

locust/env.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
from operator import methodcaller
4+
from re import Pattern
45
from typing import Callable, TypeVar
56

67
from configargparse import Namespace
@@ -27,6 +28,7 @@ def __init__(
2728
tags: list[str] | None = None,
2829
locustfile: str | None = None,
2930
exclude_tags: list[str] | None = None,
31+
exclude_from_aggregation: str | Pattern[str] | None = None,
3032
events: Events | None = None,
3133
host: str | None = None,
3234
reset_stats=False,
@@ -69,7 +71,8 @@ def __init__(
6971
"""If set, only tasks that are tagged by tags in this list will be executed. Leave this as None to use the one from parsed_options"""
7072
self.exclude_tags = exclude_tags
7173
"""If set, only tasks that aren't tagged by tags in this list will be executed. Leave this as None to use the one from parsed_options"""
72-
self.stats = RequestStats()
74+
self.exclude_from_aggregation = exclude_from_aggregation
75+
self.stats = RequestStats(exclude_from_aggregation=exclude_from_aggregation)
7376
"""Reference to RequestStats instance"""
7477
self.host = host
7578
"""Base URL of the target system"""
@@ -154,7 +157,9 @@ def create_worker_runner(self, master_host: str, master_port: int) -> WorkerRunn
154157
"""
155158
# Create a new RequestStats with use_response_times_cache set to False to save some memory
156159
# and CPU cycles, since the response_times_cache is not needed for Worker nodes
157-
self.stats = RequestStats(use_response_times_cache=False)
160+
self.stats = RequestStats(
161+
use_response_times_cache=False, exclude_from_aggregation=self.exclude_from_aggregation
162+
)
158163
return self._create_runner(
159164
WorkerRunner,
160165
master_host=master_host,

locust/main.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ def create_environment(
8989
available_user_classes=available_user_classes,
9090
available_shape_classes=available_shape_classes,
9191
available_user_tasks=available_user_tasks,
92+
exclude_from_aggregation=options.exclude_from_aggregation,
9293
)
9394

9495

locust/stats.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import json
66
import logging
77
import os
8+
import re
89
import signal
910
import time
1011
from abc import abstractmethod
@@ -13,6 +14,7 @@
1314
from copy import copy
1415
from html import escape
1516
from itertools import chain
17+
from re import Pattern
1618
from types import FrameType
1719
from typing import TYPE_CHECKING, Any, Callable, NoReturn, Protocol, TypedDict, TypeVar, cast
1820

@@ -184,14 +186,17 @@ class RequestStats:
184186
Class that holds the request statistics. Accessible in a User from self.environment.stats
185187
"""
186188

187-
def __init__(self, use_response_times_cache=True):
189+
def __init__(self, use_response_times_cache=True, exclude_from_aggregation: str | Pattern[str] | None = ""):
188190
"""
189191
:param use_response_times_cache: The value of use_response_times_cache will be set for each StatsEntry()
190192
when they are created. Settings it to False saves some memory and CPU
191193
cycles which we can do on Worker nodes where the response_times_cache
192194
is not needed.
195+
:param exclude_from_aggregation: Define which logs method or name should be excluded from "Aggretated"
196+
stats. Default will accept all the logs. Regexp is allowed.
193197
"""
194198
self.use_response_times_cache = use_response_times_cache
199+
self.exclude_from_aggregation = exclude_from_aggregation
195200
self.entries: dict[tuple[str, str], StatsEntry] = EntriesDict(self)
196201
self.errors: dict[str, StatsError] = {}
197202
self.total = StatsEntry(self, "Aggregated", None, use_response_times_cache=self.use_response_times_cache)
@@ -217,8 +222,16 @@ def last_request_timestamp(self):
217222
def start_time(self):
218223
return self.total.start_time
219224

225+
def exclude_from_total(self, method: str, name: str):
226+
if self.exclude_from_aggregation:
227+
found_in_method = re.search(self.exclude_from_aggregation, method)
228+
found_in_name = re.search(self.exclude_from_aggregation, name)
229+
return found_in_method or found_in_name
230+
return False
231+
220232
def log_request(self, method: str, name: str, response_time: int, content_length: int) -> None:
221-
self.total.log(response_time, content_length)
233+
if not self.exclude_from_total(method, name):
234+
self.total.log(response_time, content_length)
222235
self.entries[(name, method)].log(response_time, content_length)
223236

224237
def log_error(self, method: str, name: str, error: Exception | str | None) -> None:

locust/test/test_parser.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ def test_parse_options(self):
109109
"-t",
110110
"5m",
111111
"--reset-stats",
112+
"--exclude-from-aggregation",
113+
"CUSTOM",
112114
"--stop-timeout",
113115
"5",
114116
"MyUserClass",
@@ -120,6 +122,7 @@ def test_parse_options(self):
120122
self.assertEqual("5m", options.run_time)
121123
self.assertTrue(options.reset_stats)
122124
self.assertEqual("5", options.stop_timeout)
125+
self.assertEqual("CUSTOM", options.exclude_from_aggregation)
123126
self.assertEqual(["MyUserClass"], options.user_classes)
124127
# check default arg
125128
self.assertEqual(8089, options.web_port)
@@ -132,6 +135,7 @@ def test_parse_options_from_env(self):
132135
os.environ["LOCUST_RESET_STATS"] = "true"
133136
os.environ["LOCUST_STOP_TIMEOUT"] = "5"
134137
os.environ["LOCUST_USER_CLASSES"] = "MyUserClass"
138+
os.environ["LOCUST_EXCLUDE_FROM_AGGREGATION"] = "CUSTOM"
135139
options = parse_options(args=[])
136140

137141
self.assertEqual("locustfile.py", options.locustfile)
@@ -141,6 +145,7 @@ def test_parse_options_from_env(self):
141145
self.assertTrue(options.reset_stats)
142146
self.assertEqual("5", options.stop_timeout)
143147
self.assertEqual(["MyUserClass"], options.user_classes)
148+
self.assertEqual("CUSTOM", options.exclude_from_aggregation)
144149
# check default arg
145150
self.assertEqual(8089, options.web_port)
146151

locust/test/test_stats.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,16 @@ def test_total_rps(self):
9898
self.assertAlmostEqual(s2.total_rps, 1 / 5.0)
9999
self.assertEqual(self.stats.total.total_rps, 10 / 5.0)
100100

101+
def test_total_exclude_from_aggregation(self):
102+
before_count = self.stats.num_requests
103+
# First without exclusion
104+
self.stats.log_request("CUSTOM", "some_name", 1337, 1337)
105+
self.assertEqual(self.stats.num_requests, before_count + 1)
106+
# Second with exclusion
107+
self.stats.exclude_from_aggregation = r"CUSTOM"
108+
self.stats.log_request("CUSTOM", "some_name", 1337, 1337)
109+
self.assertEqual(self.stats.num_requests, before_count + 1)
110+
101111
def test_rps_less_than_one_second(self):
102112
s = StatsEntry(self.stats, "percentile_test", "GET")
103113
for i in range(10):

0 commit comments

Comments
 (0)