jarvis-ai-assistant 0.1.222__py3-none-any.whl → 0.7.0__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.
- jarvis/__init__.py +1 -1
- jarvis/jarvis_agent/__init__.py +1143 -245
- jarvis/jarvis_agent/agent_manager.py +97 -0
- jarvis/jarvis_agent/builtin_input_handler.py +12 -10
- jarvis/jarvis_agent/config_editor.py +57 -0
- jarvis/jarvis_agent/edit_file_handler.py +392 -99
- jarvis/jarvis_agent/event_bus.py +48 -0
- jarvis/jarvis_agent/events.py +157 -0
- jarvis/jarvis_agent/file_context_handler.py +79 -0
- jarvis/jarvis_agent/file_methodology_manager.py +117 -0
- jarvis/jarvis_agent/jarvis.py +1117 -147
- jarvis/jarvis_agent/main.py +78 -34
- jarvis/jarvis_agent/memory_manager.py +195 -0
- jarvis/jarvis_agent/methodology_share_manager.py +174 -0
- jarvis/jarvis_agent/prompt_manager.py +82 -0
- jarvis/jarvis_agent/prompts.py +46 -9
- jarvis/jarvis_agent/protocols.py +4 -1
- jarvis/jarvis_agent/rewrite_file_handler.py +141 -0
- jarvis/jarvis_agent/run_loop.py +146 -0
- jarvis/jarvis_agent/session_manager.py +9 -9
- jarvis/jarvis_agent/share_manager.py +228 -0
- jarvis/jarvis_agent/shell_input_handler.py +23 -3
- jarvis/jarvis_agent/stdio_redirect.py +295 -0
- jarvis/jarvis_agent/task_analyzer.py +212 -0
- jarvis/jarvis_agent/task_manager.py +154 -0
- jarvis/jarvis_agent/task_planner.py +496 -0
- jarvis/jarvis_agent/tool_executor.py +8 -4
- jarvis/jarvis_agent/tool_share_manager.py +139 -0
- jarvis/jarvis_agent/user_interaction.py +42 -0
- jarvis/jarvis_agent/utils.py +54 -0
- jarvis/jarvis_agent/web_bridge.py +189 -0
- jarvis/jarvis_agent/web_output_sink.py +53 -0
- jarvis/jarvis_agent/web_server.py +751 -0
- jarvis/jarvis_c2rust/__init__.py +26 -0
- jarvis/jarvis_c2rust/cli.py +613 -0
- jarvis/jarvis_c2rust/collector.py +258 -0
- jarvis/jarvis_c2rust/library_replacer.py +1122 -0
- jarvis/jarvis_c2rust/llm_module_agent.py +1300 -0
- jarvis/jarvis_c2rust/optimizer.py +960 -0
- jarvis/jarvis_c2rust/scanner.py +1681 -0
- jarvis/jarvis_c2rust/transpiler.py +2325 -0
- jarvis/jarvis_code_agent/build_validation_config.py +133 -0
- jarvis/jarvis_code_agent/code_agent.py +1605 -178
- jarvis/jarvis_code_agent/code_analyzer/__init__.py +62 -0
- jarvis/jarvis_code_agent/code_analyzer/base_language.py +74 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +44 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +102 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +59 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +125 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +69 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +38 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +44 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +38 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +50 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +93 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +129 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +54 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +154 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator.py +43 -0
- jarvis/jarvis_code_agent/code_analyzer/context_manager.py +363 -0
- jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +18 -0
- jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +132 -0
- jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +330 -0
- jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +781 -0
- jarvis/jarvis_code_agent/code_analyzer/language_registry.py +185 -0
- jarvis/jarvis_code_agent/code_analyzer/language_support.py +89 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +31 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +231 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +183 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +219 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +209 -0
- jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +451 -0
- jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +77 -0
- jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +48 -0
- jarvis/jarvis_code_agent/lint.py +275 -13
- jarvis/jarvis_code_agent/utils.py +142 -0
- jarvis/jarvis_code_analysis/checklists/loader.py +20 -6
- jarvis/jarvis_code_analysis/code_review.py +583 -548
- jarvis/jarvis_data/config_schema.json +339 -28
- jarvis/jarvis_git_squash/main.py +22 -13
- jarvis/jarvis_git_utils/git_commiter.py +171 -55
- jarvis/jarvis_mcp/sse_mcp_client.py +22 -15
- jarvis/jarvis_mcp/stdio_mcp_client.py +4 -4
- jarvis/jarvis_mcp/streamable_mcp_client.py +36 -16
- jarvis/jarvis_memory_organizer/memory_organizer.py +753 -0
- jarvis/jarvis_methodology/main.py +48 -63
- jarvis/jarvis_multi_agent/__init__.py +302 -43
- jarvis/jarvis_multi_agent/main.py +70 -24
- jarvis/jarvis_platform/ai8.py +40 -23
- jarvis/jarvis_platform/base.py +210 -49
- jarvis/jarvis_platform/human.py +11 -1
- jarvis/jarvis_platform/kimi.py +82 -76
- jarvis/jarvis_platform/openai.py +73 -1
- jarvis/jarvis_platform/registry.py +8 -15
- jarvis/jarvis_platform/tongyi.py +115 -101
- jarvis/jarvis_platform/yuanbao.py +89 -63
- jarvis/jarvis_platform_manager/main.py +194 -132
- jarvis/jarvis_platform_manager/service.py +122 -86
- jarvis/jarvis_rag/cli.py +156 -53
- jarvis/jarvis_rag/embedding_manager.py +155 -12
- jarvis/jarvis_rag/llm_interface.py +10 -13
- jarvis/jarvis_rag/query_rewriter.py +63 -12
- jarvis/jarvis_rag/rag_pipeline.py +222 -40
- jarvis/jarvis_rag/reranker.py +26 -3
- jarvis/jarvis_rag/retriever.py +270 -14
- jarvis/jarvis_sec/__init__.py +3605 -0
- jarvis/jarvis_sec/checkers/__init__.py +32 -0
- jarvis/jarvis_sec/checkers/c_checker.py +2680 -0
- jarvis/jarvis_sec/checkers/rust_checker.py +1108 -0
- jarvis/jarvis_sec/cli.py +116 -0
- jarvis/jarvis_sec/report.py +257 -0
- jarvis/jarvis_sec/status.py +264 -0
- jarvis/jarvis_sec/types.py +20 -0
- jarvis/jarvis_sec/workflow.py +219 -0
- jarvis/jarvis_smart_shell/main.py +405 -137
- jarvis/jarvis_stats/__init__.py +13 -0
- jarvis/jarvis_stats/cli.py +387 -0
- jarvis/jarvis_stats/stats.py +711 -0
- jarvis/jarvis_stats/storage.py +612 -0
- jarvis/jarvis_stats/visualizer.py +282 -0
- jarvis/jarvis_tools/ask_user.py +1 -0
- jarvis/jarvis_tools/base.py +18 -2
- jarvis/jarvis_tools/clear_memory.py +239 -0
- jarvis/jarvis_tools/cli/main.py +220 -144
- jarvis/jarvis_tools/execute_script.py +52 -12
- jarvis/jarvis_tools/file_analyzer.py +17 -12
- jarvis/jarvis_tools/generate_new_tool.py +46 -24
- jarvis/jarvis_tools/read_code.py +277 -18
- jarvis/jarvis_tools/read_symbols.py +141 -0
- jarvis/jarvis_tools/read_webpage.py +86 -13
- jarvis/jarvis_tools/registry.py +294 -90
- jarvis/jarvis_tools/retrieve_memory.py +227 -0
- jarvis/jarvis_tools/save_memory.py +194 -0
- jarvis/jarvis_tools/search_web.py +62 -28
- jarvis/jarvis_tools/sub_agent.py +205 -0
- jarvis/jarvis_tools/sub_code_agent.py +217 -0
- jarvis/jarvis_tools/virtual_tty.py +330 -62
- jarvis/jarvis_utils/builtin_replace_map.py +4 -5
- jarvis/jarvis_utils/clipboard.py +90 -0
- jarvis/jarvis_utils/config.py +607 -50
- jarvis/jarvis_utils/embedding.py +3 -0
- jarvis/jarvis_utils/fzf.py +57 -0
- jarvis/jarvis_utils/git_utils.py +251 -29
- jarvis/jarvis_utils/globals.py +174 -17
- jarvis/jarvis_utils/http.py +58 -79
- jarvis/jarvis_utils/input.py +899 -153
- jarvis/jarvis_utils/methodology.py +210 -83
- jarvis/jarvis_utils/output.py +220 -137
- jarvis/jarvis_utils/utils.py +1906 -135
- jarvis_ai_assistant-0.7.0.dist-info/METADATA +465 -0
- jarvis_ai_assistant-0.7.0.dist-info/RECORD +192 -0
- {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/entry_points.txt +8 -2
- jarvis/jarvis_git_details/main.py +0 -265
- jarvis/jarvis_platform/oyi.py +0 -357
- jarvis/jarvis_tools/edit_file.py +0 -255
- jarvis/jarvis_tools/rewrite_file.py +0 -195
- jarvis_ai_assistant-0.1.222.dist-info/METADATA +0 -767
- jarvis_ai_assistant-0.1.222.dist-info/RECORD +0 -110
- /jarvis/{jarvis_git_details → jarvis_memory_organizer}/__init__.py +0 -0
- {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,711 @@
|
|
|
1
|
+
"""
|
|
2
|
+
统计管理模块
|
|
3
|
+
|
|
4
|
+
提供统计数据的增加、查看、分析等功能的主接口
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from datetime import datetime, timedelta
|
|
8
|
+
from typing import Dict, List, Optional, Union, Any
|
|
9
|
+
|
|
10
|
+
from jarvis.jarvis_stats.storage import StatsStorage
|
|
11
|
+
from jarvis.jarvis_stats.visualizer import StatsVisualizer
|
|
12
|
+
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class StatsManager:
|
|
16
|
+
"""统计管理器"""
|
|
17
|
+
|
|
18
|
+
# 类级别的存储和可视化器实例
|
|
19
|
+
_storage: Optional[StatsStorage] = None
|
|
20
|
+
_visualizer: Optional[StatsVisualizer] = None
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def _get_storage(cls) -> StatsStorage:
|
|
24
|
+
"""获取存储实例"""
|
|
25
|
+
if cls._storage is None:
|
|
26
|
+
cls._storage = StatsStorage()
|
|
27
|
+
return cls._storage
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def _get_visualizer(cls) -> StatsVisualizer:
|
|
31
|
+
"""获取可视化器实例"""
|
|
32
|
+
if cls._visualizer is None:
|
|
33
|
+
cls._visualizer = StatsVisualizer()
|
|
34
|
+
return cls._visualizer
|
|
35
|
+
|
|
36
|
+
def __init__(self, storage_dir: Optional[str] = None):
|
|
37
|
+
"""
|
|
38
|
+
初始化统计管理器(保留以兼容旧代码)
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
storage_dir: 存储目录路径
|
|
42
|
+
"""
|
|
43
|
+
# 如果提供了特定的存储目录,则重新初始化存储
|
|
44
|
+
if storage_dir is not None:
|
|
45
|
+
StatsManager._storage = StatsStorage(storage_dir)
|
|
46
|
+
|
|
47
|
+
@staticmethod
|
|
48
|
+
def increment(
|
|
49
|
+
metric_name: str,
|
|
50
|
+
amount: Union[int, float] = 1,
|
|
51
|
+
tags: Optional[Dict[str, str]] = None,
|
|
52
|
+
group: Optional[str] = None,
|
|
53
|
+
unit: str = "count",
|
|
54
|
+
):
|
|
55
|
+
"""
|
|
56
|
+
增加计数型指标
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
metric_name: 指标名称
|
|
60
|
+
amount: 增加的数量,默认为1
|
|
61
|
+
tags: 标签字典
|
|
62
|
+
group: 指标分组,会自动添加到 tags 中
|
|
63
|
+
unit: 计量单位,默认为 "count"
|
|
64
|
+
|
|
65
|
+
Examples:
|
|
66
|
+
>>> StatsManager.increment("page_views")
|
|
67
|
+
>>> StatsManager.increment("downloads", 5)
|
|
68
|
+
>>> StatsManager.increment("response_time", 0.123, unit="seconds")
|
|
69
|
+
>>> StatsManager.increment("execute_script", 1, group="tool")
|
|
70
|
+
"""
|
|
71
|
+
# 如果指定了分组,自动添加到 tags 中
|
|
72
|
+
if group:
|
|
73
|
+
if tags is None:
|
|
74
|
+
tags = {}
|
|
75
|
+
tags["group"] = group
|
|
76
|
+
|
|
77
|
+
storage = StatsManager._get_storage()
|
|
78
|
+
storage.add_metric(
|
|
79
|
+
metric_name=metric_name,
|
|
80
|
+
value=float(amount),
|
|
81
|
+
unit=unit,
|
|
82
|
+
timestamp=datetime.now(),
|
|
83
|
+
tags=tags,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
@staticmethod
|
|
87
|
+
def list_metrics() -> List[str]:
|
|
88
|
+
"""
|
|
89
|
+
列出所有指标
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
指标名称列表
|
|
93
|
+
"""
|
|
94
|
+
storage = StatsManager._get_storage()
|
|
95
|
+
return storage.list_metrics()
|
|
96
|
+
|
|
97
|
+
@staticmethod
|
|
98
|
+
def get_metric_total(metric_name: str) -> float:
|
|
99
|
+
"""
|
|
100
|
+
获取指标的累计总量(快速路径)
|
|
101
|
+
优先从总量缓存读取;若不存在则回溯历史数据计算一次后缓存
|
|
102
|
+
"""
|
|
103
|
+
storage = StatsManager._get_storage()
|
|
104
|
+
try:
|
|
105
|
+
return float(storage.get_metric_total(metric_name))
|
|
106
|
+
except Exception:
|
|
107
|
+
return 0.0
|
|
108
|
+
|
|
109
|
+
@staticmethod
|
|
110
|
+
def get_metric_info(metric_name: str) -> Optional[Dict[str, Any]]:
|
|
111
|
+
"""
|
|
112
|
+
获取指标元信息(包含unit、created_at、last_updated、group等)
|
|
113
|
+
- 若缺少group,会尝试基于历史记录的tags进行推断并写回,然后返回最新信息
|
|
114
|
+
"""
|
|
115
|
+
storage = StatsManager._get_storage()
|
|
116
|
+
info = storage.get_metric_info(metric_name)
|
|
117
|
+
if not info or not info.get("group"):
|
|
118
|
+
try:
|
|
119
|
+
grp = storage.resolve_metric_group(
|
|
120
|
+
metric_name
|
|
121
|
+
) # 触发一次分组解析与回填
|
|
122
|
+
if grp:
|
|
123
|
+
info = storage.get_metric_info(metric_name)
|
|
124
|
+
except Exception:
|
|
125
|
+
pass
|
|
126
|
+
return info
|
|
127
|
+
|
|
128
|
+
@staticmethod
|
|
129
|
+
def resolve_metric_group(metric_name: str) -> Optional[str]:
|
|
130
|
+
"""
|
|
131
|
+
主动解析并写回某个指标的分组信息(调用底层存储的推断逻辑)
|
|
132
|
+
"""
|
|
133
|
+
storage = StatsManager._get_storage()
|
|
134
|
+
try:
|
|
135
|
+
return storage.resolve_metric_group(metric_name)
|
|
136
|
+
except Exception:
|
|
137
|
+
return None
|
|
138
|
+
|
|
139
|
+
@staticmethod
|
|
140
|
+
def show(
|
|
141
|
+
metric_name: Optional[str] = None,
|
|
142
|
+
last_hours: Optional[int] = None,
|
|
143
|
+
last_days: Optional[int] = None,
|
|
144
|
+
start_time: Optional[datetime] = None,
|
|
145
|
+
end_time: Optional[datetime] = None,
|
|
146
|
+
format: str = "table",
|
|
147
|
+
aggregation: str = "hourly",
|
|
148
|
+
tags: Optional[Dict[str, str]] = None,
|
|
149
|
+
):
|
|
150
|
+
"""
|
|
151
|
+
显示统计数据
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
metric_name: 指标名称,如果不指定则显示所有指标摘要
|
|
155
|
+
last_hours: 最近N小时
|
|
156
|
+
last_days: 最近N天
|
|
157
|
+
start_time: 开始时间
|
|
158
|
+
end_time: 结束时间
|
|
159
|
+
format: 显示格式 (table, chart, summary)
|
|
160
|
+
aggregation: 聚合方式 (hourly, daily)
|
|
161
|
+
tags: 过滤标签
|
|
162
|
+
|
|
163
|
+
Examples:
|
|
164
|
+
>>> StatsManager.show() # 显示所有指标摘要
|
|
165
|
+
>>> StatsManager.show("api_calls", last_hours=24) # 显示最近24小时
|
|
166
|
+
>>> StatsManager.show("response_time", last_days=7, format="chart") # 图表显示
|
|
167
|
+
"""
|
|
168
|
+
# 处理时间范围
|
|
169
|
+
# 当未提供时间过滤参数时,默认显示全历史数据
|
|
170
|
+
if last_hours or last_days:
|
|
171
|
+
if end_time is None:
|
|
172
|
+
end_time = datetime.now()
|
|
173
|
+
if start_time is None:
|
|
174
|
+
if last_hours:
|
|
175
|
+
start_time = end_time - timedelta(hours=last_hours)
|
|
176
|
+
elif last_days:
|
|
177
|
+
start_time = end_time - timedelta(days=last_days)
|
|
178
|
+
else:
|
|
179
|
+
pass
|
|
180
|
+
|
|
181
|
+
if metric_name is None:
|
|
182
|
+
# 显示所有指标摘要
|
|
183
|
+
StatsManager._show_metrics_summary(start_time, end_time, tags)
|
|
184
|
+
else:
|
|
185
|
+
# 基于元数据的 created_at / last_updated 推断全历史时间范围
|
|
186
|
+
storage = StatsManager._get_storage()
|
|
187
|
+
if start_time is None or end_time is None:
|
|
188
|
+
info = storage.get_metric_info(metric_name)
|
|
189
|
+
if info:
|
|
190
|
+
try:
|
|
191
|
+
created_at = info.get("created_at")
|
|
192
|
+
last_updated = info.get("last_updated")
|
|
193
|
+
start_dt = start_time or (
|
|
194
|
+
datetime.fromisoformat(created_at) if created_at else datetime.now()
|
|
195
|
+
)
|
|
196
|
+
end_dt = end_time or (
|
|
197
|
+
datetime.fromisoformat(last_updated) if last_updated else datetime.now()
|
|
198
|
+
)
|
|
199
|
+
except Exception:
|
|
200
|
+
start_dt = start_time or (datetime.now() - timedelta(days=7))
|
|
201
|
+
end_dt = end_time or datetime.now()
|
|
202
|
+
else:
|
|
203
|
+
start_dt = start_time or (datetime.now() - timedelta(days=7))
|
|
204
|
+
end_dt = end_time or datetime.now()
|
|
205
|
+
else:
|
|
206
|
+
start_dt = start_time
|
|
207
|
+
end_dt = end_time
|
|
208
|
+
|
|
209
|
+
# 根据格式显示数据
|
|
210
|
+
if format == "chart":
|
|
211
|
+
StatsManager._show_chart(
|
|
212
|
+
metric_name, start_dt, end_dt, aggregation, tags
|
|
213
|
+
)
|
|
214
|
+
elif format == "summary":
|
|
215
|
+
StatsManager._show_summary(
|
|
216
|
+
metric_name, start_dt, end_dt, aggregation, tags
|
|
217
|
+
)
|
|
218
|
+
else:
|
|
219
|
+
StatsManager._show_table(metric_name, start_dt, end_dt, tags)
|
|
220
|
+
|
|
221
|
+
@staticmethod
|
|
222
|
+
def plot(
|
|
223
|
+
metric_name: Optional[str] = None,
|
|
224
|
+
last_hours: Optional[int] = None,
|
|
225
|
+
last_days: Optional[int] = None,
|
|
226
|
+
start_time: Optional[datetime] = None,
|
|
227
|
+
end_time: Optional[datetime] = None,
|
|
228
|
+
aggregation: str = "hourly",
|
|
229
|
+
tags: Optional[Dict[str, str]] = None,
|
|
230
|
+
width: Optional[int] = None,
|
|
231
|
+
height: Optional[int] = None,
|
|
232
|
+
):
|
|
233
|
+
"""
|
|
234
|
+
绘制指标的折线图
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
metric_name: 指标名称(可选,不指定则根据标签过滤所有匹配的指标)
|
|
238
|
+
last_hours: 最近N小时
|
|
239
|
+
last_days: 最近N天
|
|
240
|
+
start_time: 开始时间
|
|
241
|
+
end_time: 结束时间
|
|
242
|
+
aggregation: 聚合方式
|
|
243
|
+
tags: 过滤标签
|
|
244
|
+
width: 图表宽度
|
|
245
|
+
height: 图表高度
|
|
246
|
+
|
|
247
|
+
Examples:
|
|
248
|
+
>>> StatsManager.plot("response_time", last_hours=24)
|
|
249
|
+
>>> StatsManager.plot(tags={"service": "api"}, last_days=7)
|
|
250
|
+
"""
|
|
251
|
+
# 处理时间范围
|
|
252
|
+
# 当未提供时间过滤参数时,默认使用全历史数据
|
|
253
|
+
if last_hours or last_days:
|
|
254
|
+
if end_time is None:
|
|
255
|
+
end_time = datetime.now()
|
|
256
|
+
if start_time is None:
|
|
257
|
+
if last_hours:
|
|
258
|
+
start_time = end_time - timedelta(hours=last_hours)
|
|
259
|
+
elif last_days:
|
|
260
|
+
start_time = end_time - timedelta(days=last_days)
|
|
261
|
+
|
|
262
|
+
# 如果指定了metric_name,显示单个图表
|
|
263
|
+
if metric_name:
|
|
264
|
+
# 基于元数据的 created_at / last_updated 推断全历史时间范围
|
|
265
|
+
storage = StatsManager._get_storage()
|
|
266
|
+
if start_time is None or end_time is None:
|
|
267
|
+
info = storage.get_metric_info(metric_name)
|
|
268
|
+
if info:
|
|
269
|
+
try:
|
|
270
|
+
created_at = info.get("created_at")
|
|
271
|
+
last_updated = info.get("last_updated")
|
|
272
|
+
start_dt = start_time or (
|
|
273
|
+
datetime.fromisoformat(created_at) if created_at else datetime.now()
|
|
274
|
+
)
|
|
275
|
+
end_dt = end_time or (
|
|
276
|
+
datetime.fromisoformat(last_updated) if last_updated else datetime.now()
|
|
277
|
+
)
|
|
278
|
+
except Exception:
|
|
279
|
+
start_dt = start_time or (datetime.now() - timedelta(days=7))
|
|
280
|
+
end_dt = end_time or datetime.now()
|
|
281
|
+
else:
|
|
282
|
+
start_dt = start_time or (datetime.now() - timedelta(days=7))
|
|
283
|
+
end_dt = end_time or datetime.now()
|
|
284
|
+
else:
|
|
285
|
+
start_dt = start_time
|
|
286
|
+
end_dt = end_time
|
|
287
|
+
|
|
288
|
+
StatsManager._show_chart(
|
|
289
|
+
metric_name, start_dt, end_dt, aggregation, tags, width, height
|
|
290
|
+
)
|
|
291
|
+
else:
|
|
292
|
+
# 如果没有指定metric_name,根据标签过滤获取所有匹配的指标
|
|
293
|
+
StatsManager._show_multiple_charts(
|
|
294
|
+
start_time, end_time, aggregation, tags, width, height
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
@staticmethod
|
|
298
|
+
def get_stats(
|
|
299
|
+
metric_name: str,
|
|
300
|
+
last_hours: Optional[int] = None,
|
|
301
|
+
last_days: Optional[int] = None,
|
|
302
|
+
start_time: Optional[datetime] = None,
|
|
303
|
+
end_time: Optional[datetime] = None,
|
|
304
|
+
aggregation: Optional[str] = None,
|
|
305
|
+
tags: Optional[Dict[str, str]] = None,
|
|
306
|
+
) -> Dict[str, Any]:
|
|
307
|
+
"""
|
|
308
|
+
获取统计数据
|
|
309
|
+
|
|
310
|
+
Args:
|
|
311
|
+
metric_name: 指标名称
|
|
312
|
+
last_hours: 最近N小时
|
|
313
|
+
last_days: 最近N天
|
|
314
|
+
start_time: 开始时间
|
|
315
|
+
end_time: 结束时间
|
|
316
|
+
aggregation: 聚合方式,如果指定则返回聚合数据
|
|
317
|
+
tags: 过滤标签
|
|
318
|
+
|
|
319
|
+
Returns:
|
|
320
|
+
统计数据字典
|
|
321
|
+
"""
|
|
322
|
+
# 处理时间范围
|
|
323
|
+
# 当未提供时间过滤参数时,返回全历史数据
|
|
324
|
+
if last_hours or last_days or start_time is not None:
|
|
325
|
+
if end_time is None:
|
|
326
|
+
end_time = datetime.now()
|
|
327
|
+
if start_time is None:
|
|
328
|
+
if last_hours:
|
|
329
|
+
start_time = end_time - timedelta(hours=last_hours)
|
|
330
|
+
elif last_days:
|
|
331
|
+
start_time = end_time - timedelta(days=last_days)
|
|
332
|
+
|
|
333
|
+
storage = StatsManager._get_storage()
|
|
334
|
+
|
|
335
|
+
# 基于元数据推断全历史范围(当未提供时间过滤参数时)
|
|
336
|
+
start_dt = start_time
|
|
337
|
+
end_dt = end_time
|
|
338
|
+
if start_dt is None or end_dt is None:
|
|
339
|
+
info = storage.get_metric_info(metric_name)
|
|
340
|
+
if info:
|
|
341
|
+
try:
|
|
342
|
+
created_at = info.get("created_at")
|
|
343
|
+
last_updated = info.get("last_updated")
|
|
344
|
+
if start_dt is None and created_at:
|
|
345
|
+
start_dt = datetime.fromisoformat(created_at)
|
|
346
|
+
if end_dt is None and last_updated:
|
|
347
|
+
end_dt = datetime.fromisoformat(last_updated)
|
|
348
|
+
except Exception:
|
|
349
|
+
pass
|
|
350
|
+
if start_dt is None:
|
|
351
|
+
start_dt = datetime.now() - timedelta(days=7)
|
|
352
|
+
if end_dt is None:
|
|
353
|
+
end_dt = datetime.now()
|
|
354
|
+
|
|
355
|
+
if aggregation:
|
|
356
|
+
# 返回聚合数据(按推断的全历史时间范围)
|
|
357
|
+
return storage.aggregate_metrics(
|
|
358
|
+
metric_name, start_dt, end_dt, aggregation, tags
|
|
359
|
+
)
|
|
360
|
+
else:
|
|
361
|
+
# 返回原始数据(全历史或指定范围)
|
|
362
|
+
records = storage.get_metrics(metric_name, start_dt, end_dt, tags)
|
|
363
|
+
|
|
364
|
+
return {
|
|
365
|
+
"metric": metric_name,
|
|
366
|
+
"records": records,
|
|
367
|
+
"count": len(records),
|
|
368
|
+
"start_time": start_dt.isoformat(),
|
|
369
|
+
"end_time": end_dt.isoformat(),
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
@staticmethod
|
|
373
|
+
def clean_old_data(days_to_keep: int = 30):
|
|
374
|
+
"""
|
|
375
|
+
清理旧数据
|
|
376
|
+
|
|
377
|
+
Args:
|
|
378
|
+
days_to_keep: 保留最近N天的数据
|
|
379
|
+
"""
|
|
380
|
+
storage = StatsManager._get_storage()
|
|
381
|
+
storage.delete_old_data(days_to_keep)
|
|
382
|
+
|
|
383
|
+
@staticmethod
|
|
384
|
+
def remove_metric(metric_name: str) -> bool:
|
|
385
|
+
"""
|
|
386
|
+
删除指定的指标及其所有数据
|
|
387
|
+
|
|
388
|
+
Args:
|
|
389
|
+
metric_name: 要删除的指标名称
|
|
390
|
+
|
|
391
|
+
Returns:
|
|
392
|
+
True 如果成功删除,False 如果指标不存在
|
|
393
|
+
"""
|
|
394
|
+
storage = StatsManager._get_storage()
|
|
395
|
+
return storage.delete_metric(metric_name)
|
|
396
|
+
|
|
397
|
+
@staticmethod
|
|
398
|
+
def _show_metrics_summary(
|
|
399
|
+
start_time: Optional[datetime] = None,
|
|
400
|
+
end_time: Optional[datetime] = None,
|
|
401
|
+
tags: Optional[Dict[str, str]] = None,
|
|
402
|
+
):
|
|
403
|
+
"""显示所有指标摘要"""
|
|
404
|
+
from rich.console import Console
|
|
405
|
+
from rich.table import Table
|
|
406
|
+
|
|
407
|
+
console = Console()
|
|
408
|
+
storage = StatsManager._get_storage()
|
|
409
|
+
metrics = storage.list_metrics()
|
|
410
|
+
|
|
411
|
+
if not metrics:
|
|
412
|
+
console.print("[yellow]没有找到任何统计指标[/yellow]")
|
|
413
|
+
return
|
|
414
|
+
|
|
415
|
+
# 如果未指定时间范围且两者皆为 None,则表示使用全历史数据
|
|
416
|
+
all_time = False
|
|
417
|
+
if start_time is None and end_time is None:
|
|
418
|
+
all_time = True
|
|
419
|
+
else:
|
|
420
|
+
if end_time is None:
|
|
421
|
+
end_time = datetime.now()
|
|
422
|
+
if start_time is None:
|
|
423
|
+
start_time = end_time - timedelta(days=7)
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
# 计算时间范围列标题
|
|
428
|
+
if start_time is None or end_time is None:
|
|
429
|
+
period_label = "值"
|
|
430
|
+
else:
|
|
431
|
+
delta = end_time - start_time
|
|
432
|
+
if delta.days >= 1:
|
|
433
|
+
period_label = f"{delta.days}天数据点"
|
|
434
|
+
else:
|
|
435
|
+
hours = max(1, int(delta.total_seconds() // 3600))
|
|
436
|
+
period_label = f"{hours}小时数据点"
|
|
437
|
+
|
|
438
|
+
# 创建表格
|
|
439
|
+
table = Table(title="统计指标摘要")
|
|
440
|
+
table.add_column("指标名称", style="cyan")
|
|
441
|
+
table.add_column("单位", style="green")
|
|
442
|
+
table.add_column("最后更新", style="yellow")
|
|
443
|
+
table.add_column(period_label, style="magenta")
|
|
444
|
+
|
|
445
|
+
# 过滤满足标签条件的指标
|
|
446
|
+
displayed_count = 0
|
|
447
|
+
for metric in metrics:
|
|
448
|
+
# 获取该指标的记录(全历史或指定范围)
|
|
449
|
+
if 'all_time' in locals() and all_time:
|
|
450
|
+
_start = None
|
|
451
|
+
_end = None
|
|
452
|
+
try:
|
|
453
|
+
info = storage.get_metric_info(metric)
|
|
454
|
+
if info:
|
|
455
|
+
ca = info.get("created_at")
|
|
456
|
+
lu = info.get("last_updated")
|
|
457
|
+
_start = datetime.fromisoformat(ca) if ca else None
|
|
458
|
+
_end = datetime.fromisoformat(lu) if lu else None
|
|
459
|
+
except Exception:
|
|
460
|
+
_start = None
|
|
461
|
+
_end = None
|
|
462
|
+
records = storage.get_metrics(metric, _start, _end, tags)
|
|
463
|
+
else:
|
|
464
|
+
records = storage.get_metrics(metric, start_time, end_time, tags)
|
|
465
|
+
|
|
466
|
+
# 如果指定了标签过滤,但没有匹配的记录,跳过该指标
|
|
467
|
+
if tags and len(records) == 0:
|
|
468
|
+
continue
|
|
469
|
+
|
|
470
|
+
info = storage.get_metric_info(metric)
|
|
471
|
+
unit = "-"
|
|
472
|
+
last_updated = "-"
|
|
473
|
+
|
|
474
|
+
if info:
|
|
475
|
+
unit = info.get("unit", "-")
|
|
476
|
+
last_updated = info.get("last_updated", "-")
|
|
477
|
+
|
|
478
|
+
# 格式化时间
|
|
479
|
+
if last_updated != "-":
|
|
480
|
+
try:
|
|
481
|
+
dt = datetime.fromisoformat(last_updated)
|
|
482
|
+
last_updated = dt.strftime("%Y-%m-%d %H:%M")
|
|
483
|
+
except Exception:
|
|
484
|
+
pass
|
|
485
|
+
|
|
486
|
+
total_value = sum(r.get("value", 0) for r in records)
|
|
487
|
+
# 智能格式化,如果是整数则不显示小数点
|
|
488
|
+
if total_value == int(total_value):
|
|
489
|
+
value_to_display = str(int(total_value))
|
|
490
|
+
else:
|
|
491
|
+
value_to_display = f"{total_value:.2f}"
|
|
492
|
+
|
|
493
|
+
table.add_row(metric, unit, last_updated, value_to_display)
|
|
494
|
+
displayed_count += 1
|
|
495
|
+
|
|
496
|
+
if displayed_count == 0:
|
|
497
|
+
console.print("[yellow]没有找到符合条件的指标[/yellow]")
|
|
498
|
+
if tags:
|
|
499
|
+
console.print(f"过滤条件: {tags}")
|
|
500
|
+
else:
|
|
501
|
+
console.print(table)
|
|
502
|
+
console.print(f"\n[green]总计: {displayed_count} 个指标[/green]")
|
|
503
|
+
if tags:
|
|
504
|
+
console.print(f"过滤条件: {tags}")
|
|
505
|
+
|
|
506
|
+
@staticmethod
|
|
507
|
+
def _show_table(
|
|
508
|
+
metric_name: str,
|
|
509
|
+
start_time: datetime,
|
|
510
|
+
end_time: datetime,
|
|
511
|
+
tags: Optional[Dict[str, str]],
|
|
512
|
+
):
|
|
513
|
+
"""以表格形式显示数据"""
|
|
514
|
+
storage = StatsManager._get_storage()
|
|
515
|
+
visualizer = StatsManager._get_visualizer()
|
|
516
|
+
records = storage.get_metrics(metric_name, start_time, end_time, tags)
|
|
517
|
+
|
|
518
|
+
# 获取指标信息
|
|
519
|
+
info = storage.get_metric_info(metric_name)
|
|
520
|
+
unit = info.get("unit", "") if info else ""
|
|
521
|
+
|
|
522
|
+
# 使用visualizer显示表格
|
|
523
|
+
visualizer.show_table(
|
|
524
|
+
records=records,
|
|
525
|
+
metric_name=metric_name,
|
|
526
|
+
unit=unit,
|
|
527
|
+
start_time=start_time.strftime("%Y-%m-%d %H:%M"),
|
|
528
|
+
end_time=end_time.strftime("%Y-%m-%d %H:%M"),
|
|
529
|
+
tags_filter=tags,
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
@staticmethod
|
|
533
|
+
def _show_chart(
|
|
534
|
+
metric_name: str,
|
|
535
|
+
start_time: datetime,
|
|
536
|
+
end_time: datetime,
|
|
537
|
+
aggregation: str,
|
|
538
|
+
tags: Optional[Dict[str, str]],
|
|
539
|
+
width: Optional[int] = None,
|
|
540
|
+
height: Optional[int] = None,
|
|
541
|
+
):
|
|
542
|
+
"""显示图表"""
|
|
543
|
+
storage = StatsManager._get_storage()
|
|
544
|
+
visualizer = StatsManager._get_visualizer()
|
|
545
|
+
|
|
546
|
+
# 获取聚合数据
|
|
547
|
+
aggregated = storage.aggregate_metrics(
|
|
548
|
+
metric_name, start_time, end_time, aggregation, tags
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
if not aggregated:
|
|
552
|
+
PrettyOutput.print(
|
|
553
|
+
f"没有找到指标 '{metric_name}' 的数据", OutputType.WARNING
|
|
554
|
+
)
|
|
555
|
+
return
|
|
556
|
+
|
|
557
|
+
# 获取指标信息
|
|
558
|
+
info = storage.get_metric_info(metric_name)
|
|
559
|
+
unit = info.get("unit", "") if info else ""
|
|
560
|
+
|
|
561
|
+
# 准备数据
|
|
562
|
+
first_item = next(iter(aggregated.values()), None)
|
|
563
|
+
is_simple_count = (
|
|
564
|
+
first_item
|
|
565
|
+
and first_item.get("min") == 1
|
|
566
|
+
and first_item.get("max") == 1
|
|
567
|
+
and first_item.get("avg") == 1
|
|
568
|
+
)
|
|
569
|
+
|
|
570
|
+
if unit == "count" or is_simple_count:
|
|
571
|
+
# 对于计数类指标,使用总和更有意义
|
|
572
|
+
data = {k: v["sum"] for k, v in aggregated.items()}
|
|
573
|
+
else:
|
|
574
|
+
# 对于其他指标(如耗时),使用平均值
|
|
575
|
+
data = {k: v["avg"] for k, v in aggregated.items()} # 设置可视化器尺寸
|
|
576
|
+
if width or height:
|
|
577
|
+
visualizer.width = width or visualizer.width
|
|
578
|
+
visualizer.height = height or visualizer.height
|
|
579
|
+
|
|
580
|
+
# 绘制图表
|
|
581
|
+
chart = visualizer.plot_line_chart(
|
|
582
|
+
data=data,
|
|
583
|
+
title=f"{metric_name} - {aggregation}聚合",
|
|
584
|
+
unit=unit,
|
|
585
|
+
show_values=True,
|
|
586
|
+
)
|
|
587
|
+
|
|
588
|
+
PrettyOutput.print(chart, OutputType.CODE, lang="text")
|
|
589
|
+
|
|
590
|
+
# 显示时间范围
|
|
591
|
+
from rich.panel import Panel
|
|
592
|
+
from rich.console import Console
|
|
593
|
+
|
|
594
|
+
console = Console()
|
|
595
|
+
console.print(
|
|
596
|
+
Panel(
|
|
597
|
+
f"[cyan]{start_time.strftime('%Y-%m-%d %H:%M')}[/] ~ [cyan]{end_time.strftime('%Y-%m-%d %H:%M')}[/]",
|
|
598
|
+
title="[bold]时间范围[/bold]",
|
|
599
|
+
expand=False,
|
|
600
|
+
style="dim",
|
|
601
|
+
border_style="green",
|
|
602
|
+
)
|
|
603
|
+
)
|
|
604
|
+
|
|
605
|
+
@staticmethod
|
|
606
|
+
def _show_multiple_charts(
|
|
607
|
+
start_time: Optional[datetime],
|
|
608
|
+
end_time: Optional[datetime],
|
|
609
|
+
aggregation: str,
|
|
610
|
+
tags: Optional[Dict[str, str]],
|
|
611
|
+
width: Optional[int] = None,
|
|
612
|
+
height: Optional[int] = None,
|
|
613
|
+
):
|
|
614
|
+
"""根据标签过滤显示多个指标的图表"""
|
|
615
|
+
from rich.console import Console
|
|
616
|
+
|
|
617
|
+
console = Console()
|
|
618
|
+
storage = StatsManager._get_storage()
|
|
619
|
+
|
|
620
|
+
# 获取所有指标
|
|
621
|
+
all_metrics = StatsManager.list_metrics()
|
|
622
|
+
|
|
623
|
+
# 根据标签过滤指标
|
|
624
|
+
matched_metrics = []
|
|
625
|
+
for metric in all_metrics:
|
|
626
|
+
# 获取该指标在时间范围内的数据
|
|
627
|
+
records = storage.get_metrics(metric, start_time, end_time, tags)
|
|
628
|
+
if records: # 如果有匹配标签的数据
|
|
629
|
+
matched_metrics.append(metric)
|
|
630
|
+
|
|
631
|
+
if not matched_metrics:
|
|
632
|
+
console.print("[yellow]没有找到匹配标签的指标数据[/yellow]")
|
|
633
|
+
return
|
|
634
|
+
|
|
635
|
+
console.print(f"[green]找到 {len(matched_metrics)} 个匹配的指标[/green]")
|
|
636
|
+
|
|
637
|
+
# 为每个匹配的指标绘制图表
|
|
638
|
+
for i, metric in enumerate(matched_metrics):
|
|
639
|
+
if i > 0:
|
|
640
|
+
console.print("\n" + "=" * 80 + "\n") # 分隔符
|
|
641
|
+
|
|
642
|
+
# 计算该指标的有效时间范围(基于元数据推断全历史)
|
|
643
|
+
_start = start_time
|
|
644
|
+
_end = end_time
|
|
645
|
+
if _start is None or _end is None:
|
|
646
|
+
info = storage.get_metric_info(metric)
|
|
647
|
+
if info:
|
|
648
|
+
try:
|
|
649
|
+
ca = info.get("created_at")
|
|
650
|
+
lu = info.get("last_updated")
|
|
651
|
+
if _start is None and ca:
|
|
652
|
+
_start = datetime.fromisoformat(ca)
|
|
653
|
+
if _end is None and lu:
|
|
654
|
+
_end = datetime.fromisoformat(lu)
|
|
655
|
+
except Exception:
|
|
656
|
+
pass
|
|
657
|
+
if _start is None:
|
|
658
|
+
_start = datetime.now() - timedelta(days=7)
|
|
659
|
+
if _end is None:
|
|
660
|
+
_end = datetime.now()
|
|
661
|
+
|
|
662
|
+
StatsManager._show_chart(
|
|
663
|
+
metric, _start, _end, aggregation, tags, width, height
|
|
664
|
+
)
|
|
665
|
+
|
|
666
|
+
@staticmethod
|
|
667
|
+
def _show_summary(
|
|
668
|
+
metric_name: str,
|
|
669
|
+
start_time: datetime,
|
|
670
|
+
end_time: datetime,
|
|
671
|
+
aggregation: str,
|
|
672
|
+
tags: Optional[Dict[str, str]],
|
|
673
|
+
):
|
|
674
|
+
"""显示汇总信息"""
|
|
675
|
+
storage = StatsManager._get_storage()
|
|
676
|
+
visualizer = StatsManager._get_visualizer()
|
|
677
|
+
|
|
678
|
+
# 获取聚合数据
|
|
679
|
+
aggregated = storage.aggregate_metrics(
|
|
680
|
+
metric_name, start_time, end_time, aggregation, tags
|
|
681
|
+
)
|
|
682
|
+
|
|
683
|
+
if not aggregated:
|
|
684
|
+
PrettyOutput.print(
|
|
685
|
+
f"没有找到指标 '{metric_name}' 的数据", OutputType.WARNING
|
|
686
|
+
)
|
|
687
|
+
return
|
|
688
|
+
|
|
689
|
+
# 获取指标信息
|
|
690
|
+
info = storage.get_metric_info(metric_name)
|
|
691
|
+
unit = info.get("unit", "") if info else ""
|
|
692
|
+
|
|
693
|
+
# 显示汇总
|
|
694
|
+
summary = visualizer.show_summary(aggregated, metric_name, unit, tags)
|
|
695
|
+
if summary: # 如果返回了内容才打印(兼容性)
|
|
696
|
+
PrettyOutput.print(summary, OutputType.INFO)
|
|
697
|
+
|
|
698
|
+
# 显示时间范围
|
|
699
|
+
from rich.panel import Panel
|
|
700
|
+
from rich.console import Console
|
|
701
|
+
|
|
702
|
+
console = Console()
|
|
703
|
+
console.print(
|
|
704
|
+
Panel(
|
|
705
|
+
f"[cyan]{start_time.strftime('%Y-%m-%d %H:%M')}[/] ~ [cyan]{end_time.strftime('%Y-%m-%d %H:%M')}[/]",
|
|
706
|
+
title="[bold]时间范围[/bold]",
|
|
707
|
+
expand=False,
|
|
708
|
+
style="dim",
|
|
709
|
+
border_style="green",
|
|
710
|
+
)
|
|
711
|
+
)
|