Skip to content

Commit 1a632ed

Browse files
committed
Merge branch 'feat/multi-process'
2 parents f0ebf6a + 9466255 commit 1a632ed

File tree

15 files changed

+230
-80
lines changed

15 files changed

+230
-80
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ VITE_API_BASE_URL=/api # Base API URL for frontend requests (supports reverse p
120120
MULTIPROCESS_THRESHOLD=1000
121121

122122
# Minimum number of concurrent users each child process should handle (prevents excessive processes and resource waste)
123-
MIN_USERS_PER_PROCESS=600
123+
MIN_USERS_PER_PROCESS=500
124124

125125
# ⚠️ IMPORTANT NOTES:
126126
# - When concurrency ≥ 1000, enabling multi-process mode is strongly recommended for performance.

README_CN.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ VITE_API_BASE_URL=/api
119119
# 当并发用户数超过此阈值,系统将自动启用多进程模式(需多核 CPU 支持)
120120
MULTIPROCESS_THRESHOLD: 1000
121121
# 每个子进程至少承载的并发用户数(避免进程过多导致资源浪费)
122-
MIN_USERS_PER_PROCESS: 600
122+
MIN_USERS_PER_PROCESS: 500
123123
# ⚠️ 重要提示:
124124
# - 当并发量 ≥ 1000 时,强烈建议启用多进程以提升性能。
125125
# - 多进程模式依赖多核 CPU 资源,请确保部署环境满足资源要求

backend/service/upload_service.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ async def process_cert_files(
206206
raise HTTPException(status_code=400, detail=str(e))
207207
except Exception as e:
208208
logger.error(f"Error processing certificate files: {e}")
209-
raise HTTPException(status_code=500, detail="Internal server error")
209+
raise HTTPException(status_code=500, detail="Upload failed")
210210

211211

212212
async def process_dataset_files(task_id: str, files: List[UploadFile]):
@@ -275,7 +275,7 @@ async def process_dataset_files(task_id: str, files: List[UploadFile]):
275275
raise HTTPException(status_code=400, detail=str(e))
276276
except Exception as e:
277277
logger.error(f"Error processing dataset files: {e}")
278-
raise HTTPException(status_code=500, detail="Internal server error")
278+
raise HTTPException(status_code=500, detail="Upload failed")
279279

280280

281281
async def upload_file_svc(

docker-compose.dev.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ services:
6464
ENABLE_MULTIPROCESS: auto
6565
LOCUST_CPU_CORES: 4.0
6666
MULTIPROCESS_THRESHOLD: 1000 # Enable multiprocess only for 1000+ users
67-
MIN_USERS_PER_PROCESS: 600 # Each process handles at least 600 users
67+
MIN_USERS_PER_PROCESS: 500 # Each process handles at least 600 users
6868
# Process management and stability
6969
PYTHONUNBUFFERED: 1 # Ensure immediate log output
7070
LOCUST_WORKER_HEARTBEAT_INTERVAL: 3 # Worker heartbeat interval (seconds)

docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ services:
5858
ENABLE_MULTIPROCESS: auto
5959
LOCUST_CPU_CORES: 2.0
6060
MULTIPROCESS_THRESHOLD: 1000 # Enable multiprocess only for 1000+ users
61-
MIN_USERS_PER_PROCESS: 600 # Each process handles at least 600 users
61+
MIN_USERS_PER_PROCESS: 500 # Each process handles at least 600 users
6262
# Process management and stability
6363
PYTHONUNBUFFERED: 1 # Ensure immediate log output
6464
LOCUST_WORKER_HEARTBEAT_INTERVAL: 3 # Worker heartbeat interval (seconds)

frontend/public/locales/en/translation.json

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -409,14 +409,22 @@
409409
"promptFieldPathTooltip": "The key in your request payload that contains the user prompt (needed for performance metrics calculation)",
410410
"streamingResponseConfiguration": "Streaming Response Configuration",
411411
"streamLinePrefix": "Stream Line Prefix",
412-
"streamLinePrefixTooltip": "Text that appears at the beginning of each streaming data line (e.g., \"data:\", \"event:\")",
412+
"streamLinePrefixTooltip": "Text that appears at the beginning of each data line (e.g., \"data:\", \"event:\")",
413413
"dataFormatTooltip": "Format of the streaming data after removing the prefix",
414414
"jsonFormat": "JSON Format",
415415
"plainText": "Plain Text",
416416
"contentFieldPath": "Content Field Path",
417417
"contentFieldPathTooltip": "Dot-notation path to the main content in each JSON chunk (e.g., choices.0.delta.content)",
418418
"reasoningFieldPath": "Reasoning Field Path",
419419
"reasoningFieldPathTooltip": "Dot-notation path to reasoning content in JSON (optional, for models that support reasoning)",
420+
"usageFieldPath": "Token Usage Field",
421+
"usageFieldPathTooltip": "Field path for token usage statistics (for token throughput calculation, if not filled or filled incorrectly, the built-in tokenizer will be used to estimate)",
422+
"promptTokensFieldPath": "Prompt Tokens Field Path",
423+
"promptTokensFieldPathTooltip": "Dot-notation path to prompt tokens count field (e.g., usage.prompt_tokens)",
424+
"completionTokensFieldPath": "Completion Tokens Field Path",
425+
"completionTokensFieldPathTooltip": "Dot-notation path to completion tokens count field (e.g., usage.completion_tokens)",
426+
"totalTokensFieldPath": "Total Tokens Field Path",
427+
"totalTokensFieldPathTooltip": "Dot-notation path to total tokens count field (e.g., usage.total_tokens)",
420428
"streamTerminationConfiguration": "Stream Termination Configuration",
421429
"endLinePrefix": "End Line Prefix",
422430
"endLinePrefixTooltip": "Prefix for lines that contain stream termination signals (usually same as stream prefix)",
@@ -427,7 +435,7 @@
427435
"nonStreamingResponseConfiguration": "Non-Streaming Response Configuration",
428436
"nonStreamingContentFieldPathTooltip": "Dot-notation path to the main content in the response JSON (e.g., choices.0.message.content)",
429437
"nonStreamingReasoningFieldPathTooltip": "Dot-notation path to reasoning content (optional, for models with reasoning capabilities)",
430-
"fieldMappingDescription": "Configure field mappings for both prompt field and response data extraction. This mapping is essential for updating request payloads with test data and parsing streaming/non-streaming responses correctly.",
438+
"fieldMappingDescription": "⚠️ Please accurately configure the mapping between the prompt and the response data field. This mapping directly affects the accuracy of data set replacement, load test execution, and performance metrics (such as response latency and token throughput). If the response does not contain the usage field, the built-in tokenizer will be used to estimate the token count.",
431439
"testIt": "Test It",
432440
"nextStep": "Next Step",
433441
"previousStep": "Previous",

frontend/public/locales/zh/translation.json

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -174,9 +174,9 @@
174174
"apiKeyDescription": "用于身份验证的API密钥",
175175
"aiConfigDescription": "配置AI服务请求信息,用于对性能结果进行AI分析。",
176176
"pleaseEnterHostUrl": "请输入AI服务主机URL",
177-
"enterBaseUrlDescription": "输入AI服务的基础URL(例如,https://api.openai.com",
177+
"enterBaseUrlDescription": "输入AI服务的基础URL,如:https://api.openai.com",
178178
"pleaseEnterModelName": "请输入AI模型名称",
179-
"enterModelDescription": "输入用于分析的模型名称(例如,gpt-3.5-turbo,gpt-4)",
179+
"enterModelDescription": "输入用于分析的模型名称,如:gpt-4o",
180180
"pleaseEnterApiKey": "请输入API密钥",
181181
"enterApiKeyDescription": "输入用于身份验证的API密钥",
182182
"enterApiKeyWithoutBearer": "输入API密钥(不包含Bearer前缀)"
@@ -408,26 +408,34 @@
408408
"promptFieldPath": "提示词字段路径",
409409
"promptFieldPathTooltip": "请求参数中包含用户提示词的键(性能指标计算需要)",
410410
"streamingResponseConfiguration": "流式响应配置",
411-
"streamLinePrefix": "流前缀",
412-
"streamLinePrefixTooltip": "出现在每个流数据行开头的文本(例如,data:、event:",
413-
"dataFormatTooltip": "移除前缀后流数据的格式",
411+
"streamLinePrefix": "数据行流前缀",
412+
"streamLinePrefixTooltip": "出现在每个数据行开头的文本,如:data:、event:",
413+
"dataFormatTooltip": "移除前缀后的数据格式",
414414
"jsonFormat": "JSON格式",
415415
"plainText": "纯文本",
416416
"contentFieldPath": "content字段路径",
417-
"contentFieldPathTooltip": "content字段路径,使用点分割(例如,choices.0.delta.content",
417+
"contentFieldPathTooltip": "content字段路径,使用点分割,如:choices.0.delta.content",
418418
"reasoningFieldPath": "reasoning_content字段路径",
419-
"reasoningFieldPathTooltip": "reasoning_content字段路径,使用点分割(可选,适用于深度思考模型)",
419+
"reasoningFieldPathTooltip": "reasoning_content字段路径,使用点分割,如:choices.0.delta.reasoning_content",
420+
"usageFieldPath": "Token使用统计字段",
421+
"usageFieldPathTooltip": "Token使用统计信息的字段路径(用于token吞吐计算,若未填写或者填写错误则按照内置tokenizer估算)",
422+
"promptTokensFieldPath": "prompt_tokens字段路径",
423+
"promptTokensFieldPathTooltip": "prompt_tokens字段路径,使用点分割,如:usage.prompt_tokens",
424+
"completionTokensFieldPath": "completion_tokens字段路径",
425+
"completionTokensFieldPathTooltip": "completion_tokens字段路径,使用点分割,如:usage.completion_tokens",
426+
"totalTokensFieldPath": "total_tokens字段路径",
427+
"totalTokensFieldPathTooltip": "total_tokens字段路径,使用点分割,如:usage.total_tokens",
420428
"streamTerminationConfiguration": "流终止配置",
421429
"endLinePrefix": "结束行前缀",
422430
"endLinePrefixTooltip": "包含流终止信号的行前缀(通常与流前缀相同)",
423431
"stopSignal": "停止信号",
424-
"stopSignalTooltip": "流式输出结束的标志(例如,[DONE]、STOP、finished",
432+
"stopSignalTooltip": "流式输出结束的标志,如:[DONE]、STOP、finished",
425433
"endFieldPath": "结束字段路径",
426-
"endFieldPathTooltip": "指示完成的字段的JSON路径(可选,例如choices.0.finish_reason",
434+
"endFieldPathTooltip": "指示完成的字段的JSON路径,如: choices.0.finish_reason",
427435
"nonStreamingResponseConfiguration": "非流式响应配置",
428-
"nonStreamingContentFieldPathTooltip": "content字段路径,使用点分割(例如,choices.0.delta.content",
429-
"nonStreamingReasoningFieldPathTooltip": "reasoning_content字段路径,使用点分割(可选,适用于深度思考模型)",
430-
"fieldMappingDescription": "配置提示词字段和响应数据提取的字段映射。此映射对于使用数据集更新请求参数和正确解析流式/非流式响应至关重要",
436+
"nonStreamingContentFieldPathTooltip": "content字段路径,使用点分割,如:choices.0.delta.content",
437+
"nonStreamingReasoningFieldPathTooltip": "reasoning_content字段路径,使用点分割,如:choices.0.delta.reasoning_content",
438+
"fieldMappingDescription": "⚠️ 请准确配置提示词(prompt)与响应数据字段的映射关系。该映射直接影响数据集替换、压测执行及性能指标(如 响应时延、token吞吐量)的统计准确性。若响应中未包含 usage 字段,系统将回退至内置 tokenizer 估算 token 数量",
431439
"testIt": "测试",
432440
"nextStep": "下一步",
433441
"previousStep": "上一步",
@@ -479,7 +487,7 @@
479487
"systemHeader": "系统请求头",
480488
"headerNamePlaceholder": "请求头名称",
481489
"headerValuePlaceholder": "请求头值",
482-
"cookieNamePlaceholder": "Cookie名称(例如session_token)",
490+
"cookieNamePlaceholder": "Cookie名称,如:session_token",
483491
"cookieValuePlaceholder": "Cookie值",
484492
"close": "关闭"
485493
},

frontend/src/components/CreateJobForm.tsx

Lines changed: 79 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,6 @@ const CreateJobFormContent: React.FC<CreateJobFormProps> = ({
8989
if (apiPath === '/chat/completions') {
9090
const isStreamMode = streamMode !== false; // Default to streaming if not specified
9191
return {
92-
prompt: 'messages.0.content',
9392
...(isStreamMode
9493
? CHAT_COMPLETIONS_FIELD_MAPPING.STREAMING
9594
: CHAT_COMPLETIONS_FIELD_MAPPING.NON_STREAMING),
@@ -98,11 +97,13 @@ const CreateJobFormContent: React.FC<CreateJobFormProps> = ({
9897
// For non-chat/completions APIs, return empty values (only show placeholders)
9998
return {
10099
prompt: '',
101-
stream_prefix: '',
100+
stream_prefix: 'data:',
102101
data_format: 'json',
103102
content: '',
104103
reasoning_content: '',
105-
usage: '',
104+
prompt_tokens: 'usage.prompt_tokens',
105+
completion_tokens: 'usage.completion_tokens',
106+
total_tokens: 'usage.total_tokens',
106107
end_prefix: '',
107108
stop_flag: '',
108109
end_field: '',
@@ -2159,8 +2160,79 @@ const CreateJobFormContent: React.FC<CreateJobFormProps> = ({
21592160
</Col>
21602161
</Row>
21612162

2162-
<Row gutter={24} style={{ marginTop: 16 }}>
2163-
<Col span={12}>
2163+
<Row gutter={16} style={{ marginTop: 16 }}>
2164+
<Col span={8}>
2165+
<Form.Item
2166+
name={['field_mapping', 'prompt_tokens']}
2167+
label={
2168+
<span>
2169+
{t(
2170+
'components.createJobForm.promptTokensFieldPath'
2171+
)}
2172+
<Tooltip
2173+
title={t(
2174+
'components.createJobForm.promptTokensFieldPathTooltip'
2175+
)}
2176+
>
2177+
<InfoCircleOutlined
2178+
style={{ marginLeft: 5 }}
2179+
/>
2180+
</Tooltip>
2181+
</span>
2182+
}
2183+
>
2184+
<Input placeholder='usage.prompt_tokens' />
2185+
</Form.Item>
2186+
</Col>
2187+
2188+
<Col span={8}>
2189+
<Form.Item
2190+
name={['field_mapping', 'completion_tokens']}
2191+
label={
2192+
<span>
2193+
{t(
2194+
'components.createJobForm.completionTokensFieldPath'
2195+
)}
2196+
<Tooltip
2197+
title={t(
2198+
'components.createJobForm.completionTokensFieldPathTooltip'
2199+
)}
2200+
>
2201+
<InfoCircleOutlined
2202+
style={{ marginLeft: 5 }}
2203+
/>
2204+
</Tooltip>
2205+
</span>
2206+
}
2207+
>
2208+
<Input placeholder='usage.completion_tokens' />
2209+
</Form.Item>
2210+
</Col>
2211+
<Col span={8}>
2212+
<Form.Item
2213+
name={['field_mapping', 'total_tokens']}
2214+
label={
2215+
<span>
2216+
{t(
2217+
'components.createJobForm.totalTokensFieldPath'
2218+
)}
2219+
<Tooltip
2220+
title={t(
2221+
'components.createJobForm.totalTokensFieldPathTooltip'
2222+
)}
2223+
>
2224+
<InfoCircleOutlined
2225+
style={{ marginLeft: 5 }}
2226+
/>
2227+
</Tooltip>
2228+
</span>
2229+
}
2230+
>
2231+
<Input placeholder='usage.total_tokens' />
2232+
</Form.Item>
2233+
</Col>
2234+
</Row>
2235+
{/* <Col span={12}>
21642236
<Form.Item
21652237
name={['field_mapping', 'usage']}
21662238
label={
@@ -2180,8 +2252,7 @@ const CreateJobFormContent: React.FC<CreateJobFormProps> = ({
21802252
>
21812253
<Input placeholder='usage' />
21822254
</Form.Item>
2183-
</Col>
2184-
</Row>
2255+
</Col> */}
21852256
</>
21862257
)
21872258
);
@@ -2271,7 +2342,7 @@ const CreateJobFormContent: React.FC<CreateJobFormProps> = ({
22712342
},
22722343
]}
22732344
>
2274-
<Input placeholder='[DONE]' />
2345+
<Input placeholder='stop' />
22752346
</Form.Item>
22762347
</Col>
22772348
</Row>

frontend/src/utils/constants.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,21 +27,27 @@ export const DEFAULT_FORM_VALUES = {
2727
// Chat completions field mapping defaults
2828
export const CHAT_COMPLETIONS_FIELD_MAPPING = {
2929
STREAMING: {
30+
prompt: 'messages.0.content',
3031
stream_prefix: 'data:',
3132
data_format: 'json',
3233
content: 'choices.0.delta.content',
3334
reasoning_content: 'choices.0.delta.reasoning_content',
34-
usage: 'usage',
35+
prompt_tokens: 'usage.prompt_tokens',
36+
completion_tokens: 'usage.completion_tokens',
37+
total_tokens: 'usage.total_tokens',
3538
end_prefix: 'data:',
3639
stop_flag: '[DONE]',
3740
end_field: '',
3841
},
3942
NON_STREAMING: {
43+
prompt: 'messages.0.content',
4044
stream_prefix: '',
4145
data_format: 'json',
4246
content: 'choices.0.message.content',
4347
reasoning_content: 'choices.0.message.reasoning_content',
44-
usage: 'usage',
48+
prompt_tokens: 'usage.prompt_tokens',
49+
completion_tokens: 'usage.completion_tokens',
50+
total_tokens: 'usage.total_tokens',
4551
end_prefix: '',
4652
stop_flag: '',
4753
end_field: '',

st_engine/config/multiprocess.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def __init__(self) -> None:
2424
os.environ.get("MULTIPROCESS_THRESHOLD", "1000")
2525
)
2626
self.min_users_per_process = int(
27-
os.environ.get("MIN_USERS_PER_PROCESS", "600")
27+
os.environ.get("MIN_USERS_PER_PROCESS", "500")
2828
)
2929
self.force_single_process = (
3030
os.environ.get("FORCE_SINGLE_PROCESS", "false").lower() == "true"

0 commit comments

Comments
 (0)