Skip to content

Commit 69ab6fb

Browse files
committed
fix: compare performan
ce & NoneType in handle_general_exception
1 parent 5471404 commit 69ab6fb

File tree

10 files changed

+501
-184
lines changed

10 files changed

+501
-184
lines changed

backend/__init__.py

Whitespace-only changes.

backend/db/init_db.sql

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
SET NAMES utf8mb4;
2+
SET FOREIGN_KEY_CHECKS = 0;
3+
-- 确保数据库存在并使用该数据库
4+
CREATE DATABASE IF NOT EXISTS lmeterx;
5+
USE lmeterx;
6+
7+
-- ----------------------------
8+
-- Table structure for tasks
9+
-- ----------------------------
10+
DROP TABLE IF EXISTS `tasks`;
11+
CREATE TABLE `tasks` (
12+
`id` varchar(36) COLLATE utf8mb4_unicode_ci NOT NULL,
13+
`name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
14+
`status` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT 'idle',
15+
`target_host` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
16+
`model` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
17+
`system_prompt` longtext COLLATE utf8mb4_unicode_ci,
18+
`user_prompt` longtext COLLATE utf8mb4_unicode_ci,
19+
`stream_mode` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT 'True',
20+
`concurrent_users` int(11) DEFAULT '1',
21+
`spawn_rate` int(11) DEFAULT '0',
22+
`duration` int(11) DEFAULT '60',
23+
`chat_type` int(11) DEFAULT '0',
24+
`log_file` longtext COLLATE utf8mb4_unicode_ci,
25+
`result_file` longtext COLLATE utf8mb4_unicode_ci,
26+
`cert_file` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
27+
`key_file` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
28+
`headers` json DEFAULT NULL,
29+
`error_message` text COLLATE utf8mb4_unicode_ci,
30+
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
31+
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
32+
`api_path` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'API路径',
33+
PRIMARY KEY (`id`),
34+
KEY `idx_status_created` (`status`,`created_at`),
35+
KEY `idx_updated_at` (`updated_at`),
36+
KEY `idx_name` (`name`),
37+
KEY `idx_status` (`status`),
38+
KEY `idx_created_at` (`created_at`)
39+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
40+
41+
-- ----------------------------
42+
-- Table structure for task_results
43+
-- ----------------------------
44+
DROP TABLE IF EXISTS `task_results`;
45+
CREATE TABLE `task_results` (
46+
`id` int(11) NOT NULL AUTO_INCREMENT,
47+
`task_id` varchar(36) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '任务ID',
48+
`metric_type` varchar(36) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '指标类型',
49+
`num_requests` int(11) DEFAULT '0' COMMENT '请求总数量',
50+
`num_failures` int(11) DEFAULT '0' COMMENT '请求失败数量',
51+
`avg_latency` float DEFAULT '0' COMMENT '请求平均响应时间',
52+
`min_latency` float DEFAULT '0' COMMENT '请求最小响应时间',
53+
`max_latency` float DEFAULT '0' COMMENT '请求最大响应时间',
54+
`median_latency` float DEFAULT '0' COMMENT '请求中位响应时间',
55+
`p90_latency` float DEFAULT '0' COMMENT '请求90%响应时间',
56+
`rps` float DEFAULT '0' COMMENT '每秒请求数',
57+
`avg_content_length` float DEFAULT '0' COMMENT '平均输出的字符长度',
58+
`completion_tps` float DEFAULT '0' COMMENT '每秒输出的token数量',
59+
`total_tps` float DEFAULT '0' COMMENT '每秒输入输出的总token数量',
60+
`avg_total_tokens_per_req` float DEFAULT '0' COMMENT '每个请求的平均输入输出的总token数量',
61+
`avg_completion_tokens_per_req` float DEFAULT '0' COMMENT '每个请求的平均输出token数量',
62+
`created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
63+
`updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
64+
PRIMARY KEY (`id`),
65+
KEY `idx_task_id` (`task_id`)
66+
) ENGINE=InnoDB AUTO_INCREMENT=262 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
67+
68+
-- 最后重新启用外键检查
69+
SET FOREIGN_KEY_CHECKS = 1;

backend/model/task.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ class ComparisonMetrics(BaseModel):
249249
model_name: The model name.
250250
concurrent_users: The number of concurrent users.
251251
task_name: The task name.
252-
ttft: Time to first token (min_latency).
252+
ttft: Time to first token (avg_latency in seconds).
253253
total_tps: Total tokens per second.
254254
completion_tps: Completion tokens per second.
255255
avg_total_tpr: Average total tokens per request.

backend/service/task_service.py

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -692,8 +692,20 @@ async def compare_performance_svc(
692692
for task_id in task_ids:
693693
task = tasks[task_id]
694694

695-
# Get TTFT and RPS metrics (from Time_to_first_output_token)
696-
ttft_query = (
695+
# Get TTFT metrics - first try Time_to_first_reasoning_token, then Time_to_first_output_token
696+
ttft_reasoning_query = (
697+
select(TaskResult)
698+
.where(
699+
TaskResult.task_id == task_id,
700+
TaskResult.metric_type == "Time_to_first_reasoning_token",
701+
)
702+
.order_by(TaskResult.created_at.desc())
703+
.limit(1)
704+
)
705+
ttft_reasoning_result = await db.execute(ttft_reasoning_query)
706+
ttft_reasoning_data = ttft_reasoning_result.scalar_one_or_none()
707+
708+
ttft_output_query = (
697709
select(TaskResult)
698710
.where(
699711
TaskResult.task_id == task_id,
@@ -702,8 +714,21 @@ async def compare_performance_svc(
702714
.order_by(TaskResult.created_at.desc())
703715
.limit(1)
704716
)
705-
ttft_result = await db.execute(ttft_query)
706-
ttft_data = ttft_result.scalar_one_or_none()
717+
ttft_output_result = await db.execute(ttft_output_query)
718+
ttft_output_data = ttft_output_result.scalar_one_or_none()
719+
720+
# Get Total_time metrics for RPS
721+
total_time_query = (
722+
select(TaskResult)
723+
.where(
724+
TaskResult.task_id == task_id,
725+
TaskResult.metric_type == "Total_time",
726+
)
727+
.order_by(TaskResult.created_at.desc())
728+
.limit(1)
729+
)
730+
total_time_result = await db.execute(total_time_query)
731+
total_time_data = total_time_result.scalar_one_or_none()
707732

708733
# Get token metrics (from token_metrics)
709734
token_query = (
@@ -719,7 +744,7 @@ async def compare_performance_svc(
719744
token_data = token_result.scalar_one_or_none()
720745

721746
# Check if we have the required data
722-
if not ttft_data and not token_data:
747+
if not ttft_reasoning_data and not ttft_output_data and not token_data:
723748
logger.warning(f"No results found for task {task_id}")
724749
continue
725750

@@ -732,11 +757,20 @@ async def compare_performance_svc(
732757
avg_total_tpr = 0.0
733758
avg_completion_tpr = 0.0
734759

735-
# Extract TTFT and RPS data
736-
if ttft_data:
737-
ttft = ttft_data.min_latency or 0.0 # TTFT as minimum latency
738-
rps = ttft_data.rps or 0.0
739-
avg_response_time = ttft_data.avg_latency or 0.0
760+
# Extract TTFT data - prioritize Time_to_first_reasoning_token, then Time_to_first_output_token
761+
# Use avg_latency and convert from ms to seconds
762+
if ttft_reasoning_data and ttft_reasoning_data.avg_latency:
763+
ttft = ttft_reasoning_data.avg_latency / 1000.0 # Convert ms to seconds
764+
avg_response_time = ttft_reasoning_data.avg_latency
765+
elif ttft_output_data and ttft_output_data.avg_latency:
766+
ttft = ttft_output_data.avg_latency / 1000.0 # Convert ms to seconds
767+
avg_response_time = ttft_output_data.avg_latency
768+
769+
# Extract RPS data - prioritize Total_time, then Time_to_first_output_token
770+
if total_time_data and total_time_data.rps:
771+
rps = total_time_data.rps
772+
elif ttft_output_data and ttft_output_data.rps:
773+
rps = ttft_output_data.rps
740774

741775
# Extract token metrics data
742776
if token_data:
@@ -1041,14 +1075,14 @@ async def test_api_endpoint_svc(request: Request, body: TaskCreateReq):
10411075
"response": None,
10421076
}
10431077
except httpx.TimeoutException as e:
1044-
logger.error(f"Request timeout when testing API endpoint: {e}")
1078+
logger.error(f"Request timeout when testing API endpoint.")
10451079
return {
10461080
"status": "error",
10471081
"error": f"Request timeout: {str(e)}",
10481082
"response": None,
10491083
}
10501084
except httpx.ConnectError as e:
1051-
logger.error(f"Connection error when testing API endpoint: {e}")
1085+
logger.error(f"Connection error when testing API endpoint.")
10521086
return {
10531087
"status": "error",
10541088
"error": f"Connection error: {str(e)}",
@@ -1163,7 +1197,9 @@ async def _handle_streaming_response(response, full_url: str) -> Dict:
11631197
},
11641198
}
11651199
except Exception as stream_error:
1166-
logger.error(f"Error processing stream: {stream_error}")
1200+
logger.error(
1201+
f"Error processing stream: {stream_error}. stream data: {stream_data}"
1202+
)
11671203
return {
11681204
"status": "error",
11691205
"error": f"Streaming data processing error: {str(stream_error)}",

frontend/src/pages/ResultComparison.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -358,9 +358,9 @@ const ResultComparison: React.FC = () => {
358358
title: t('pages.resultComparison.modelName'),
359359
dataIndex: 'model_name',
360360
key: 'model_name',
361-
render: (model: string) => (
362-
<Tag color={getModelColor(model)}>{model}</Tag>
363-
),
361+
// render: (model: string) => (
362+
// <Tag color={getModelColor(model)}>{model}</Tag>
363+
// ),
364364
},
365365
{
366366
title: t('pages.resultComparison.concurrentUsers'),

st_engine/engine/core.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -237,12 +237,6 @@ def parse_field_mapping(field_mapping_str: str) -> FieldMapping:
237237
class CertificateManager:
238238
"""Manages SSL certificate configuration."""
239239

240-
# @staticmethod
241-
# def configure_certificates(
242-
# cert_file: Optional[str], key_file: Optional[str], task_logger
243-
# ) -> Optional[Union[str, Tuple[str, str]]]:
244-
# """Configure client certificate and key."""
245-
# return FilePathUtils.configure_certificates(cert_file, key_file, task_logger)
246240
@staticmethod
247241
def configure_certificates(
248242
cert_file: Optional[str], key_file: Optional[str], task_logger

st_engine/engine/locustfile.py

Lines changed: 39 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ def graceful_signal_handler(signum, frame):
6565
)
6666
return
6767

68-
task_logger.info(f"Received signal {signum}. Initiating graceful shutdown.")
6968
_shutdown_in_progress = True
7069

7170
# Let the default signal handler proceed, but our flag will prevent duplicate User.stop() calls
@@ -391,9 +390,6 @@ def get_next_prompt(self) -> Dict[str, Any]:
391390
self.environment.prompt_queue.put_nowait(prompt_data)
392391
return prompt_data
393392
else:
394-
self.task_logger.warning(
395-
"Prompt queue is empty or not initialized. Using default prompt."
396-
)
397393
return {"id": "default", "prompt": DEFAULT_PROMPT}
398394
except queue.Empty:
399395
self.task_logger.warning("Prompt queue is empty. Using default prompt.")
@@ -419,21 +415,19 @@ def _log_token_counts(
419415
model_name = global_config.model_name or ""
420416
system_prompt = global_config.system_prompt or ""
421417

422-
# Validate inputs
423418
user_prompt = user_prompt or ""
424419
reasoning_content = reasoning_content or ""
425420
model_output = model_output or ""
426421

427422
# Prefer usage_tokens if available and valid
428-
completion_tokens = None
429-
total_tokens = None
430-
431-
if usage_tokens:
432-
completion_tokens = usage_tokens.get("completion_tokens")
433-
total_tokens = usage_tokens.get("total_tokens")
423+
completion_tokens = 0
424+
total_tokens = 0
434425

435426
# Fallback: manual counting if completion_tokens and total_tokens are missing
436-
if completion_tokens is None or total_tokens is None:
427+
if usage_tokens and isinstance(usage_tokens, dict):
428+
completion_tokens = usage_tokens.get("completion_tokens", 0) or 0
429+
total_tokens = usage_tokens.get("total_tokens", 0) or 0
430+
else:
437431
system_tokens = (
438432
count_tokens(system_prompt, model_name) if system_prompt else 0
439433
)
@@ -453,9 +447,17 @@ def _log_token_counts(
453447
total_tokens = system_tokens + user_tokens + completion_tokens
454448

455449
# Ensure integer and log - only if tokens are not None and positive
456-
if completion_tokens is not None and completion_tokens > 0:
450+
if (
451+
completion_tokens
452+
and isinstance(completion_tokens, (int, float))
453+
and completion_tokens > 0
454+
):
457455
global_task_queue["completion_tokens_queue"].put(int(completion_tokens))
458-
if total_tokens is not None and total_tokens > 0:
456+
if (
457+
total_tokens
458+
and isinstance(total_tokens, (int, float))
459+
and total_tokens > 0
460+
):
459461
global_task_queue["all_tokens_queue"].put(int(total_tokens))
460462

461463
except Exception as e:
@@ -465,14 +467,14 @@ def _log_token_counts(
465467
def chat_request(self):
466468
"""Main Locust task that executes a single chat request."""
467469
global_config = get_global_config()
468-
469-
prompt_data = self.get_next_prompt()
470-
470+
# Check if we need dataset mode (avoid unnecessary queue operations)
471+
needs_dataset = bool(
472+
global_config.test_data and global_config.test_data.strip()
473+
)
474+
prompt_data = self.get_next_prompt() if needs_dataset else None
471475
base_request_kwargs, user_prompt = self.request_handler.prepare_request_kwargs(
472476
prompt_data
473477
)
474-
# self.task_logger.info(f"base_request_kwargs: {base_request_kwargs}")
475-
476478
if not base_request_kwargs:
477479
self.task_logger.error(
478480
"Failed to generate request arguments. Skipping task."
@@ -490,7 +492,6 @@ def chat_request(self):
490492
if base_request_kwargs
491493
else "failure"
492494
)
493-
494495
try:
495496
if global_config.stream_mode:
496497
reasoning_content, model_output = (
@@ -505,17 +506,29 @@ def chat_request(self):
505506
)
506507
)
507508
except Exception as e:
508-
self.task_logger.error(
509-
f"Unhandled exception in chat_request: {e}", exc_info=True
510-
)
511-
# Record the failure event for unhandled exceptions
512-
response_time = (time.time() - start_time) * 1000
509+
self.task_logger.error(f"Unhandled exception in chat_request: {e}")
510+
# Record the failure event for unhandled exceptions with enhanced context
511+
try:
512+
response_time = (
513+
(time.time() - start_time) * 1000 if start_time is not None else 0
514+
)
515+
except Exception:
516+
response_time = 0
517+
513518
ErrorHandler.handle_general_exception(
514519
f"Unhandled exception in chat_request: {e}",
515520
self.task_logger,
516521
response=None,
517522
response_time=response_time,
518-
request_name=request_name,
523+
additional_context={
524+
"stream_mode": global_config.stream_mode,
525+
"api_path": global_config.api_path,
526+
"prompt_preview": (
527+
str(user_prompt)[:100] if user_prompt else "No prompt"
528+
),
529+
"task_id": global_config.task_id,
530+
"request_name": request_name,
531+
},
519532
)
520533

521534
if reasoning_content or model_output or usage_tokens:

0 commit comments

Comments
 (0)