infomankit 0.3.23__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. infoman/__init__.py +1 -0
  2. infoman/cli/README.md +378 -0
  3. infoman/cli/__init__.py +7 -0
  4. infoman/cli/commands/__init__.py +3 -0
  5. infoman/cli/commands/init.py +312 -0
  6. infoman/cli/scaffold.py +634 -0
  7. infoman/cli/templates/Makefile.template +132 -0
  8. infoman/cli/templates/app/__init__.py.template +3 -0
  9. infoman/cli/templates/app/app.py.template +4 -0
  10. infoman/cli/templates/app/models_base.py.template +18 -0
  11. infoman/cli/templates/app/models_entity_init.py.template +11 -0
  12. infoman/cli/templates/app/models_schemas_init.py.template +11 -0
  13. infoman/cli/templates/app/repository_init.py.template +11 -0
  14. infoman/cli/templates/app/routers_init.py.template +15 -0
  15. infoman/cli/templates/app/services_init.py.template +11 -0
  16. infoman/cli/templates/app/static_index.html.template +39 -0
  17. infoman/cli/templates/app/static_main.js.template +31 -0
  18. infoman/cli/templates/app/static_style.css.template +111 -0
  19. infoman/cli/templates/app/utils_init.py.template +11 -0
  20. infoman/cli/templates/config/.env.dev.template +43 -0
  21. infoman/cli/templates/config/.env.prod.template +43 -0
  22. infoman/cli/templates/config/README.md.template +28 -0
  23. infoman/cli/templates/docker/.dockerignore.template +60 -0
  24. infoman/cli/templates/docker/Dockerfile.template +47 -0
  25. infoman/cli/templates/docker/README.md.template +240 -0
  26. infoman/cli/templates/docker/docker-compose.yml.template +81 -0
  27. infoman/cli/templates/docker/mysql_custom.cnf.template +42 -0
  28. infoman/cli/templates/docker/mysql_init.sql.template +15 -0
  29. infoman/cli/templates/project/.env.example.template +1 -0
  30. infoman/cli/templates/project/.gitignore.template +60 -0
  31. infoman/cli/templates/project/Makefile.template +38 -0
  32. infoman/cli/templates/project/README.md.template +137 -0
  33. infoman/cli/templates/project/deploy.sh.template +97 -0
  34. infoman/cli/templates/project/main.py.template +10 -0
  35. infoman/cli/templates/project/manage.sh.template +97 -0
  36. infoman/cli/templates/project/pyproject.toml.template +47 -0
  37. infoman/cli/templates/project/service.sh.template +203 -0
  38. infoman/config/__init__.py +25 -0
  39. infoman/config/base.py +67 -0
  40. infoman/config/db_cache.py +237 -0
  41. infoman/config/db_relation.py +181 -0
  42. infoman/config/db_vector.py +39 -0
  43. infoman/config/jwt.py +16 -0
  44. infoman/config/llm.py +16 -0
  45. infoman/config/log.py +627 -0
  46. infoman/config/mq.py +26 -0
  47. infoman/config/settings.py +65 -0
  48. infoman/llm/__init__.py +0 -0
  49. infoman/llm/llm.py +297 -0
  50. infoman/logger/__init__.py +57 -0
  51. infoman/logger/context.py +191 -0
  52. infoman/logger/core.py +358 -0
  53. infoman/logger/filters.py +157 -0
  54. infoman/logger/formatters.py +138 -0
  55. infoman/logger/handlers.py +276 -0
  56. infoman/logger/metrics.py +160 -0
  57. infoman/performance/README.md +583 -0
  58. infoman/performance/__init__.py +19 -0
  59. infoman/performance/cli.py +215 -0
  60. infoman/performance/config.py +166 -0
  61. infoman/performance/reporter.py +519 -0
  62. infoman/performance/runner.py +303 -0
  63. infoman/performance/standards.py +222 -0
  64. infoman/service/__init__.py +8 -0
  65. infoman/service/app.py +67 -0
  66. infoman/service/core/__init__.py +0 -0
  67. infoman/service/core/auth.py +105 -0
  68. infoman/service/core/lifespan.py +132 -0
  69. infoman/service/core/monitor.py +57 -0
  70. infoman/service/core/response.py +37 -0
  71. infoman/service/exception/__init__.py +7 -0
  72. infoman/service/exception/error.py +274 -0
  73. infoman/service/exception/exception.py +25 -0
  74. infoman/service/exception/handler.py +238 -0
  75. infoman/service/infrastructure/__init__.py +8 -0
  76. infoman/service/infrastructure/base.py +212 -0
  77. infoman/service/infrastructure/db_cache/__init__.py +8 -0
  78. infoman/service/infrastructure/db_cache/manager.py +194 -0
  79. infoman/service/infrastructure/db_relation/__init__.py +41 -0
  80. infoman/service/infrastructure/db_relation/manager.py +300 -0
  81. infoman/service/infrastructure/db_relation/manager_pro.py +408 -0
  82. infoman/service/infrastructure/db_relation/mysql.py +52 -0
  83. infoman/service/infrastructure/db_relation/pgsql.py +54 -0
  84. infoman/service/infrastructure/db_relation/sqllite.py +25 -0
  85. infoman/service/infrastructure/db_vector/__init__.py +40 -0
  86. infoman/service/infrastructure/db_vector/manager.py +201 -0
  87. infoman/service/infrastructure/db_vector/qdrant.py +322 -0
  88. infoman/service/infrastructure/mq/__init__.py +15 -0
  89. infoman/service/infrastructure/mq/manager.py +178 -0
  90. infoman/service/infrastructure/mq/nats/__init__.py +0 -0
  91. infoman/service/infrastructure/mq/nats/nats_client.py +57 -0
  92. infoman/service/infrastructure/mq/nats/nats_event_router.py +25 -0
  93. infoman/service/launch.py +284 -0
  94. infoman/service/middleware/__init__.py +7 -0
  95. infoman/service/middleware/base.py +41 -0
  96. infoman/service/middleware/logging.py +51 -0
  97. infoman/service/middleware/rate_limit.py +301 -0
  98. infoman/service/middleware/request_id.py +21 -0
  99. infoman/service/middleware/white_list.py +24 -0
  100. infoman/service/models/__init__.py +8 -0
  101. infoman/service/models/base.py +441 -0
  102. infoman/service/models/type/embed.py +70 -0
  103. infoman/service/routers/__init__.py +18 -0
  104. infoman/service/routers/health_router.py +311 -0
  105. infoman/service/routers/monitor_router.py +44 -0
  106. infoman/service/utils/__init__.py +8 -0
  107. infoman/service/utils/cache/__init__.py +0 -0
  108. infoman/service/utils/cache/cache.py +192 -0
  109. infoman/service/utils/module_loader.py +10 -0
  110. infoman/service/utils/parse.py +10 -0
  111. infoman/service/utils/resolver/__init__.py +8 -0
  112. infoman/service/utils/resolver/base.py +47 -0
  113. infoman/service/utils/resolver/resp.py +102 -0
  114. infoman/service/vector/__init__.py +20 -0
  115. infoman/service/vector/base.py +56 -0
  116. infoman/service/vector/qdrant.py +125 -0
  117. infoman/service/vector/service.py +67 -0
  118. infoman/utils/__init__.py +2 -0
  119. infoman/utils/decorators/__init__.py +8 -0
  120. infoman/utils/decorators/cache.py +137 -0
  121. infoman/utils/decorators/retry.py +99 -0
  122. infoman/utils/decorators/safe_execute.py +99 -0
  123. infoman/utils/decorators/timing.py +99 -0
  124. infoman/utils/encryption/__init__.py +8 -0
  125. infoman/utils/encryption/aes.py +66 -0
  126. infoman/utils/encryption/ecc.py +108 -0
  127. infoman/utils/encryption/rsa.py +112 -0
  128. infoman/utils/file/__init__.py +0 -0
  129. infoman/utils/file/handler.py +22 -0
  130. infoman/utils/hash/__init__.py +0 -0
  131. infoman/utils/hash/hash.py +61 -0
  132. infoman/utils/http/__init__.py +8 -0
  133. infoman/utils/http/client.py +62 -0
  134. infoman/utils/http/info.py +94 -0
  135. infoman/utils/http/result.py +19 -0
  136. infoman/utils/notification/__init__.py +8 -0
  137. infoman/utils/notification/feishu.py +35 -0
  138. infoman/utils/text/__init__.py +8 -0
  139. infoman/utils/text/extractor.py +111 -0
  140. infomankit-0.3.23.dist-info/METADATA +632 -0
  141. infomankit-0.3.23.dist-info/RECORD +143 -0
  142. infomankit-0.3.23.dist-info/WHEEL +4 -0
  143. infomankit-0.3.23.dist-info/entry_points.txt +5 -0
@@ -0,0 +1,519 @@
1
+ """
2
+ HTML 报告生成器
3
+
4
+ 生成精美的性能测试报告
5
+ """
6
+
7
+ from typing import Dict
8
+ from datetime import datetime
9
+ from pathlib import Path
10
+ from jinja2 import Template
11
+
12
+ from .runner import AggregatedResult
13
+ from .config import TestConfig
14
+ from .standards import PerformanceStandards, StandardLevel
15
+
16
+
17
+ HTML_TEMPLATE = """
18
+ <!DOCTYPE html>
19
+ <html lang="zh-CN">
20
+ <head>
21
+ <meta charset="UTF-8">
22
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
23
+ <title>{{ config.report_title }}</title>
24
+ <style>
25
+ * {
26
+ margin: 0;
27
+ padding: 0;
28
+ box-sizing: border-box;
29
+ }
30
+
31
+ body {
32
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB',
33
+ 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif;
34
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
35
+ padding: 20px;
36
+ color: #333;
37
+ }
38
+
39
+ .container {
40
+ max-width: 1400px;
41
+ margin: 0 auto;
42
+ }
43
+
44
+ .header {
45
+ background: white;
46
+ border-radius: 16px;
47
+ padding: 40px;
48
+ margin-bottom: 30px;
49
+ box-shadow: 0 10px 40px rgba(0,0,0,0.1);
50
+ }
51
+
52
+ .header h1 {
53
+ font-size: 36px;
54
+ margin-bottom: 10px;
55
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
56
+ -webkit-background-clip: text;
57
+ -webkit-text-fill-color: transparent;
58
+ }
59
+
60
+ .header .meta {
61
+ color: #666;
62
+ font-size: 14px;
63
+ }
64
+
65
+ .summary {
66
+ display: grid;
67
+ grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
68
+ gap: 20px;
69
+ margin-bottom: 30px;
70
+ }
71
+
72
+ .summary-card {
73
+ background: white;
74
+ border-radius: 12px;
75
+ padding: 24px;
76
+ box-shadow: 0 4px 20px rgba(0,0,0,0.08);
77
+ transition: transform 0.2s;
78
+ }
79
+
80
+ .summary-card:hover {
81
+ transform: translateY(-4px);
82
+ box-shadow: 0 8px 30px rgba(0,0,0,0.12);
83
+ }
84
+
85
+ .summary-card .label {
86
+ font-size: 14px;
87
+ color: #666;
88
+ margin-bottom: 8px;
89
+ }
90
+
91
+ .summary-card .value {
92
+ font-size: 32px;
93
+ font-weight: bold;
94
+ color: #333;
95
+ }
96
+
97
+ .summary-card .unit {
98
+ font-size: 14px;
99
+ color: #999;
100
+ margin-left: 4px;
101
+ }
102
+
103
+ .summary-card.excellent .value { color: #10b981; }
104
+ .summary-card.good .value { color: #3b82f6; }
105
+ .summary-card.warning .value { color: #f59e0b; }
106
+ .summary-card.danger .value { color: #ef4444; }
107
+
108
+ .results {
109
+ background: white;
110
+ border-radius: 16px;
111
+ padding: 40px;
112
+ box-shadow: 0 10px 40px rgba(0,0,0,0.1);
113
+ }
114
+
115
+ .results h2 {
116
+ font-size: 24px;
117
+ margin-bottom: 30px;
118
+ color: #333;
119
+ }
120
+
121
+ .test-case {
122
+ border: 1px solid #e5e7eb;
123
+ border-radius: 12px;
124
+ margin-bottom: 30px;
125
+ overflow: hidden;
126
+ }
127
+
128
+ .test-case-header {
129
+ background: #f9fafb;
130
+ padding: 20px;
131
+ border-bottom: 1px solid #e5e7eb;
132
+ display: flex;
133
+ justify-content: space-between;
134
+ align-items: center;
135
+ }
136
+
137
+ .test-case-title {
138
+ font-size: 18px;
139
+ font-weight: 600;
140
+ color: #333;
141
+ }
142
+
143
+ .test-case-url {
144
+ font-size: 12px;
145
+ color: #666;
146
+ font-family: 'Monaco', 'Courier New', monospace;
147
+ margin-top: 4px;
148
+ }
149
+
150
+ .level-badge {
151
+ padding: 6px 12px;
152
+ border-radius: 6px;
153
+ font-size: 12px;
154
+ font-weight: 600;
155
+ text-transform: uppercase;
156
+ }
157
+
158
+ .level-excellent { background: #d1fae5; color: #065f46; }
159
+ .level-good { background: #dbeafe; color: #1e40af; }
160
+ .level-acceptable { background: #fef3c7; color: #92400e; }
161
+ .level-poor { background: #fee2e2; color: #991b1b; }
162
+ .level-critical { background: #991b1b; color: white; }
163
+
164
+ .test-case-body {
165
+ padding: 20px;
166
+ }
167
+
168
+ .metrics-grid {
169
+ display: grid;
170
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
171
+ gap: 20px;
172
+ margin-bottom: 20px;
173
+ }
174
+
175
+ .metric {
176
+ text-align: center;
177
+ padding: 16px;
178
+ background: #f9fafb;
179
+ border-radius: 8px;
180
+ }
181
+
182
+ .metric-label {
183
+ font-size: 12px;
184
+ color: #666;
185
+ margin-bottom: 8px;
186
+ }
187
+
188
+ .metric-value {
189
+ font-size: 24px;
190
+ font-weight: bold;
191
+ color: #333;
192
+ }
193
+
194
+ .metric-unit {
195
+ font-size: 12px;
196
+ color: #999;
197
+ }
198
+
199
+ .percentiles {
200
+ display: flex;
201
+ justify-content: space-around;
202
+ margin: 20px 0;
203
+ padding: 20px;
204
+ background: #f0f9ff;
205
+ border-radius: 8px;
206
+ }
207
+
208
+ .percentile {
209
+ text-align: center;
210
+ }
211
+
212
+ .percentile-label {
213
+ font-size: 12px;
214
+ color: #0369a1;
215
+ margin-bottom: 4px;
216
+ }
217
+
218
+ .percentile-value {
219
+ font-size: 20px;
220
+ font-weight: bold;
221
+ color: #0c4a6e;
222
+ }
223
+
224
+ .errors {
225
+ margin-top: 20px;
226
+ padding: 16px;
227
+ background: #fef2f2;
228
+ border-radius: 8px;
229
+ border-left: 4px solid #ef4444;
230
+ }
231
+
232
+ .errors h4 {
233
+ color: #991b1b;
234
+ margin-bottom: 12px;
235
+ font-size: 14px;
236
+ }
237
+
238
+ .error-list {
239
+ list-style: none;
240
+ }
241
+
242
+ .error-list li {
243
+ padding: 8px 0;
244
+ color: #7f1d1d;
245
+ font-size: 13px;
246
+ border-bottom: 1px solid #fecaca;
247
+ }
248
+
249
+ .error-list li:last-child {
250
+ border-bottom: none;
251
+ }
252
+
253
+ .recommendation {
254
+ margin-top: 16px;
255
+ padding: 12px 16px;
256
+ background: #f0fdf4;
257
+ border-radius: 8px;
258
+ border-left: 4px solid #10b981;
259
+ font-size: 14px;
260
+ color: #065f46;
261
+ }
262
+
263
+ .recommendation.warning {
264
+ background: #fffbeb;
265
+ border-left-color: #f59e0b;
266
+ color: #92400e;
267
+ }
268
+
269
+ .recommendation.danger {
270
+ background: #fef2f2;
271
+ border-left-color: #ef4444;
272
+ color: #991b1b;
273
+ }
274
+
275
+ .footer {
276
+ text-align: center;
277
+ margin-top: 40px;
278
+ padding: 20px;
279
+ color: white;
280
+ font-size: 14px;
281
+ }
282
+
283
+ .footer a {
284
+ color: white;
285
+ text-decoration: none;
286
+ border-bottom: 1px solid rgba(255,255,255,0.5);
287
+ }
288
+
289
+ @media print {
290
+ body {
291
+ background: white;
292
+ padding: 0;
293
+ }
294
+
295
+ .summary-card,
296
+ .test-case {
297
+ break-inside: avoid;
298
+ }
299
+ }
300
+ </style>
301
+ </head>
302
+ <body>
303
+ <div class="container">
304
+ <!-- Header -->
305
+ <div class="header">
306
+ <h1>{{ config.report_title }}</h1>
307
+ <div class="meta">
308
+ <span>📅 {{ report_time }}</span>
309
+ <span style="margin-left: 20px;">🎯 项目: {{ config.project_name }}</span>
310
+ <span style="margin-left: 20px;">🔗 {{ config.base_url }}</span>
311
+ </div>
312
+ </div>
313
+
314
+ <!-- Summary Cards -->
315
+ <div class="summary">
316
+ <div class="summary-card">
317
+ <div class="label">总请求数</div>
318
+ <div class="value">{{ summary.total_requests }}</div>
319
+ </div>
320
+ <div class="summary-card {{ 'excellent' if summary.success_rate >= 99 else 'good' if summary.success_rate >= 95 else 'warning' if summary.success_rate >= 90 else 'danger' }}">
321
+ <div class="label">成功率</div>
322
+ <div class="value">{{ "%.2f"|format(summary.success_rate) }}<span class="unit">%</span></div>
323
+ </div>
324
+ <div class="summary-card {{ 'excellent' if summary.avg_response_time < 100 else 'good' if summary.avg_response_time < 200 else 'warning' if summary.avg_response_time < 500 else 'danger' }}">
325
+ <div class="label">平均响应时间</div>
326
+ <div class="value">{{ "%.0f"|format(summary.avg_response_time) }}<span class="unit">ms</span></div>
327
+ </div>
328
+ <div class="summary-card">
329
+ <div class="label">吞吐量</div>
330
+ <div class="value">{{ "%.1f"|format(summary.throughput) }}<span class="unit">req/s</span></div>
331
+ </div>
332
+ <div class="summary-card">
333
+ <div class="label">并发用户</div>
334
+ <div class="value">{{ config.concurrent_users }}</div>
335
+ </div>
336
+ <div class="summary-card">
337
+ <div class="label">测试时长</div>
338
+ <div class="value">{{ config.duration }}<span class="unit">秒</span></div>
339
+ </div>
340
+ </div>
341
+
342
+ <!-- Detailed Results -->
343
+ <div class="results">
344
+ <h2>📊 详细测试结果</h2>
345
+
346
+ {% for name, result in results.items() %}
347
+ <div class="test-case">
348
+ <div class="test-case-header">
349
+ <div>
350
+ <div class="test-case-title">
351
+ {{ result.method }} {{ result.test_case_name }}
352
+ </div>
353
+ <div class="test-case-url">{{ result.url }}</div>
354
+ </div>
355
+ <span class="level-badge level-{{ result.overall_level }}">
356
+ {{ get_level_label(result.overall_level) }}
357
+ </span>
358
+ </div>
359
+
360
+ <div class="test-case-body">
361
+ <!-- 核心指标 -->
362
+ <div class="metrics-grid">
363
+ <div class="metric">
364
+ <div class="metric-label">总请求数</div>
365
+ <div class="metric-value">{{ result.total_requests }}</div>
366
+ </div>
367
+ <div class="metric">
368
+ <div class="metric-label">成功 / 失败</div>
369
+ <div class="metric-value">
370
+ <span style="color: #10b981;">{{ result.successful_requests }}</span>
371
+ /
372
+ <span style="color: #ef4444;">{{ result.failed_requests }}</span>
373
+ </div>
374
+ </div>
375
+ <div class="metric">
376
+ <div class="metric-label">成功率</div>
377
+ <div class="metric-value">{{ "%.2f"|format(result.success_rate) }}<span class="metric-unit">%</span></div>
378
+ </div>
379
+ <div class="metric">
380
+ <div class="metric-label">吞吐量</div>
381
+ <div class="metric-value">{{ "%.1f"|format(result.throughput) }}<span class="metric-unit">req/s</span></div>
382
+ </div>
383
+ </div>
384
+
385
+ <!-- 响应时间百分位 -->
386
+ <div class="percentiles">
387
+ <div class="percentile">
388
+ <div class="percentile-label">最小</div>
389
+ <div class="percentile-value">{{ "%.0f"|format(result.min_response_time) }}ms</div>
390
+ </div>
391
+ <div class="percentile">
392
+ <div class="percentile-label">P50</div>
393
+ <div class="percentile-value">{{ "%.0f"|format(result.p50_response_time) }}ms</div>
394
+ </div>
395
+ <div class="percentile">
396
+ <div class="percentile-label">平均</div>
397
+ <div class="percentile-value">{{ "%.0f"|format(result.avg_response_time) }}ms</div>
398
+ </div>
399
+ <div class="percentile">
400
+ <div class="percentile-label">P95</div>
401
+ <div class="percentile-value">{{ "%.0f"|format(result.p95_response_time) }}ms</div>
402
+ </div>
403
+ <div class="percentile">
404
+ <div class="percentile-label">P99</div>
405
+ <div class="percentile-value">{{ "%.0f"|format(result.p99_response_time) }}ms</div>
406
+ </div>
407
+ <div class="percentile">
408
+ <div class="percentile-label">最大</div>
409
+ <div class="percentile-value">{{ "%.0f"|format(result.max_response_time) }}ms</div>
410
+ </div>
411
+ </div>
412
+
413
+ <!-- 错误信息 -->
414
+ {% if result.error_messages %}
415
+ <div class="errors">
416
+ <h4>⚠️ 错误信息 ({{ result.error_messages|length }})</h4>
417
+ <ul class="error-list">
418
+ {% for error in result.error_messages[:5] %}
419
+ <li>{{ error }}</li>
420
+ {% endfor %}
421
+ </ul>
422
+ </div>
423
+ {% endif %}
424
+
425
+ <!-- 优化建议 -->
426
+ <div class="recommendation {{ 'warning' if result.overall_level in ['acceptable', 'poor'] else 'danger' if result.overall_level == 'critical' else '' }}">
427
+ 💡 {{ get_recommendation(result.overall_level) }}
428
+ </div>
429
+ </div>
430
+ </div>
431
+ {% endfor %}
432
+ </div>
433
+
434
+ <!-- Footer -->
435
+ <div class="footer">
436
+ <p>Generated by <a href="https://github.com/infoman-lib/infoman-pykit" target="_blank">Infomankit Performance Test Tool</a></p>
437
+ <p style="margin-top: 8px; font-size: 12px;">v0.3.15 | © 2026 Infoman Contributors</p>
438
+ </div>
439
+ </div>
440
+ </body>
441
+ </html>
442
+ """
443
+
444
+
445
+ class HTMLReporter:
446
+ """HTML 报告生成器"""
447
+
448
+ def __init__(self, config: TestConfig):
449
+ self.config = config
450
+
451
+ def generate(
452
+ self,
453
+ results: Dict[str, AggregatedResult],
454
+ output_path: str = None
455
+ ) -> str:
456
+ """
457
+ 生成 HTML 报告
458
+
459
+ Args:
460
+ results: 聚合结果
461
+ output_path: 输出路径 (默认使用配置中的路径)
462
+
463
+ Returns:
464
+ 输出文件路径
465
+ """
466
+ if output_path is None:
467
+ output_path = self.config.report_output
468
+
469
+ # 计算汇总数据
470
+ summary = self._calculate_summary(results)
471
+
472
+ # 渲染模板
473
+ template = Template(HTML_TEMPLATE)
474
+ html_content = template.render(
475
+ config=self.config,
476
+ results=results,
477
+ summary=summary,
478
+ report_time=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
479
+ get_level_label=PerformanceStandards.get_level_label,
480
+ get_recommendation=PerformanceStandards.get_recommendation,
481
+ )
482
+
483
+ # 保存文件
484
+ output_file = Path(output_path)
485
+ output_file.parent.mkdir(parents=True, exist_ok=True)
486
+ output_file.write_text(html_content, encoding="utf-8")
487
+
488
+ return str(output_file.absolute())
489
+
490
+ def _calculate_summary(
491
+ self,
492
+ results: Dict[str, AggregatedResult]
493
+ ) -> Dict:
494
+ """计算汇总数据"""
495
+ if not results:
496
+ return {
497
+ "total_requests": 0,
498
+ "success_rate": 0,
499
+ "avg_response_time": 0,
500
+ "throughput": 0,
501
+ }
502
+
503
+ total_requests = sum(r.total_requests for r in results.values())
504
+ successful_requests = sum(r.successful_requests for r in results.values())
505
+ success_rate = (successful_requests / total_requests * 100) if total_requests > 0 else 0
506
+
507
+ # 加权平均响应时间
508
+ total_rt = sum(r.avg_response_time * r.total_requests for r in results.values())
509
+ avg_response_time = total_rt / total_requests if total_requests > 0 else 0
510
+
511
+ # 总吞吐量
512
+ throughput = sum(r.throughput for r in results.values())
513
+
514
+ return {
515
+ "total_requests": total_requests,
516
+ "success_rate": success_rate,
517
+ "avg_response_time": avg_response_time,
518
+ "throughput": throughput,
519
+ }