|
12 | 12 | import sys |
13 | 13 | import tempfile |
14 | 14 | import time |
15 | | -from typing import Any, Dict, Optional, Tuple |
| 15 | +from dataclasses import field |
| 16 | +from typing import Any, Dict, Optional, Tuple, Union |
16 | 17 |
|
17 | 18 | import urllib3 |
18 | 19 | from gevent import queue |
|
24 | 25 |
|
25 | 26 | from config.base import ( |
26 | 27 | DEFAULT_API_PATH, |
| 28 | + DEFAULT_CONTENT_TYPE, |
27 | 29 | DEFAULT_PROMPT, |
28 | 30 | DEFAULT_TIMEOUT, |
29 | 31 | DEFAULT_WAIT_TIME_MAX, |
@@ -218,6 +220,12 @@ def init_parser(parser): |
218 | 220 | default="", |
219 | 221 | help="Custom test data in JSONL format or file path", |
220 | 222 | ) |
| 223 | + parser.add_argument( |
| 224 | + "--duration", |
| 225 | + type=int, |
| 226 | + default=60, |
| 227 | + help="Test duration in seconds (used as fallback when start_time is unavailable)", |
| 228 | + ) |
221 | 229 |
|
222 | 230 |
|
223 | 231 | @events.init.add_listener |
@@ -256,6 +264,7 @@ def on_locust_init(environment, **kwargs): |
256 | 264 | global_config.key_file = options.key_file |
257 | 265 | global_config.field_mapping = options.field_mapping |
258 | 266 | global_config.test_data = options.test_data |
| 267 | + global_config.duration = int(options.duration) |
259 | 268 |
|
260 | 269 | # Parse and validate configuration |
261 | 270 | global_config.headers = ConfigManager.parse_headers( |
@@ -420,9 +429,35 @@ def on_test_stop(environment, **kwargs): |
420 | 429 | global_config = get_global_config() |
421 | 430 | task_id = global_config.task_id |
422 | 431 | task_logger = GlobalStateManager.get_task_logger(task_id) |
| 432 | + |
| 433 | + execution_time = None |
423 | 434 | start_time = GlobalStateManager.get_start_time() |
424 | 435 | end_time = time.time() |
425 | | - execution_time = max(end_time - start_time, 0.001) # avoid division by zero |
| 436 | + |
| 437 | + try: |
| 438 | + if start_time is not None and end_time is not None: |
| 439 | + execution_time = max(end_time - start_time, 0.001) |
| 440 | + else: |
| 441 | + duration = getattr(global_config, "duration", None) |
| 442 | + if duration is not None and duration > 0: |
| 443 | + execution_time = float(duration) |
| 444 | + else: |
| 445 | + execution_time = 60.0 |
| 446 | + task_logger.error( |
| 447 | + f"Failed to get effective execution time: start_time={start_time}, duration={duration}" |
| 448 | + ) |
| 449 | + |
| 450 | + raise ValueError( |
| 451 | + f"Failed to get effective execution time: start_time={start_time}, duration={duration}" |
| 452 | + ) |
| 453 | + |
| 454 | + except Exception as e: |
| 455 | + task_logger.error( |
| 456 | + "Failed to calculate execution time: %s" % str(e), exc_info=True |
| 457 | + ) |
| 458 | + execution_time = 60.0 |
| 459 | + raise RuntimeError("Failed to calculate execution time: %s" % str(e)) |
| 460 | + |
426 | 461 | from utils.common import calculate_custom_metrics, get_locust_stats |
427 | 462 |
|
428 | 463 | # Check if this is a worker process |
@@ -461,7 +496,8 @@ def on_test_stop(environment, **kwargs): |
461 | 496 |
|
462 | 497 | task_logger.debug( |
463 | 498 | f"Process type: {'Worker' if is_worker else 'Master'}, " |
464 | | - f"Multi-process: {is_multiprocess}, Worker count: {worker_count}" |
| 499 | + f"Multi-process: {is_multiprocess}, Worker count: {worker_count}, " |
| 500 | + f"Execution time: {execution_time}" |
465 | 501 | ) |
466 | 502 |
|
467 | 503 | # === WORKER PROCESS LOGIC === |
|
0 commit comments