yamon 0.1.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.
- yamon/__init__.py +2 -0
- yamon/api/__init__.py +2 -0
- yamon/api/metrics.py +55 -0
- yamon/api/websocket.py +138 -0
- yamon/cli.py +47 -0
- yamon/collectors/__init__.py +2 -0
- yamon/collectors/apple_api.py +775 -0
- yamon/collectors/collector.py +141 -0
- yamon/collectors/ioreport.py +455 -0
- yamon/collectors/smc.py +197 -0
- yamon/history.py +113 -0
- yamon/main.py +82 -0
- yamon/static/assets/index-BS0XLMl6.js +47 -0
- yamon/static/assets/index-C2CEmOwf.css +1 -0
- yamon/static/index.html +14 -0
- yamon/static/vite.svg +1 -0
- yamon-0.1.0.dist-info/METADATA +129 -0
- yamon-0.1.0.dist-info/RECORD +22 -0
- yamon-0.1.0.dist-info/WHEEL +5 -0
- yamon-0.1.0.dist-info/entry_points.txt +2 -0
- yamon-0.1.0.dist-info/licenses/LICENSE +22 -0
- yamon-0.1.0.dist-info/top_level.txt +1 -0
yamon/__init__.py
ADDED
yamon/api/__init__.py
ADDED
yamon/api/metrics.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Metrics API endpoints"""
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter
|
|
4
|
+
try:
|
|
5
|
+
from yamon.collectors.collector import MetricsCollector
|
|
6
|
+
from yamon.history import MetricsHistory
|
|
7
|
+
except ImportError:
|
|
8
|
+
from collectors.collector import MetricsCollector
|
|
9
|
+
from history import MetricsHistory
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
router = APIRouter()
|
|
13
|
+
collector = MetricsCollector()
|
|
14
|
+
history = MetricsHistory(max_size=120)
|
|
15
|
+
|
|
16
|
+
@router.get("/metrics")
|
|
17
|
+
async def get_metrics():
|
|
18
|
+
"""获取当前系统指标"""
|
|
19
|
+
metrics = collector.collect()
|
|
20
|
+
history.add_metrics(metrics)
|
|
21
|
+
return {
|
|
22
|
+
"cpu_percent": metrics.cpu_percent,
|
|
23
|
+
"cpu_per_core": metrics.cpu_per_core,
|
|
24
|
+
"cpu_count": metrics.cpu_count,
|
|
25
|
+
"memory_percent": metrics.memory_percent,
|
|
26
|
+
"memory_total": metrics.memory_total,
|
|
27
|
+
"memory_used": metrics.memory_used,
|
|
28
|
+
"memory_available": metrics.memory_available,
|
|
29
|
+
"network_sent_rate": metrics.network_sent_rate,
|
|
30
|
+
"network_recv_rate": metrics.network_recv_rate,
|
|
31
|
+
"cpu_power": metrics.cpu_power,
|
|
32
|
+
"gpu_power": metrics.gpu_power,
|
|
33
|
+
"ane_power": metrics.ane_power,
|
|
34
|
+
"system_power": metrics.system_power,
|
|
35
|
+
"gpu_usage": metrics.gpu_usage,
|
|
36
|
+
"gpu_freq_mhz": metrics.gpu_freq_mhz,
|
|
37
|
+
"ane_usage": metrics.ane_usage,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@router.get("/history")
|
|
41
|
+
async def get_history():
|
|
42
|
+
"""获取历史数据"""
|
|
43
|
+
return {
|
|
44
|
+
"cpu_percent": history.cpu_percent.get_values(),
|
|
45
|
+
"memory_percent": history.memory_percent.get_values(),
|
|
46
|
+
"network_sent_rate": history.network_sent_rate.get_values(),
|
|
47
|
+
"network_recv_rate": history.network_recv_rate.get_values(),
|
|
48
|
+
"cpu_power": history.cpu_power.get_values(),
|
|
49
|
+
"gpu_power": history.gpu_power.get_values(),
|
|
50
|
+
"ane_power": history.ane_power.get_values(),
|
|
51
|
+
"system_power": history.system_power.get_values(),
|
|
52
|
+
"gpu_usage": history.gpu_usage.get_values(),
|
|
53
|
+
"ane_usage": history.ane_usage.get_values(),
|
|
54
|
+
}
|
|
55
|
+
|
yamon/api/websocket.py
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""WebSocket API for real-time metrics"""
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter, WebSocket, WebSocketDisconnect
|
|
4
|
+
try:
|
|
5
|
+
from yamon.collectors.collector import MetricsCollector, SystemMetrics
|
|
6
|
+
from yamon.history import MetricsHistory
|
|
7
|
+
except ImportError:
|
|
8
|
+
from collectors.collector import MetricsCollector, SystemMetrics
|
|
9
|
+
from history import MetricsHistory
|
|
10
|
+
import asyncio
|
|
11
|
+
import json
|
|
12
|
+
from typing import Optional
|
|
13
|
+
|
|
14
|
+
router = APIRouter()
|
|
15
|
+
collector = MetricsCollector()
|
|
16
|
+
history = MetricsHistory(max_size=120)
|
|
17
|
+
|
|
18
|
+
# 共享的最新指标数据(线程安全)
|
|
19
|
+
_latest_metrics: Optional[SystemMetrics] = None
|
|
20
|
+
_metrics_lock = asyncio.Lock()
|
|
21
|
+
|
|
22
|
+
# 后台收集任务
|
|
23
|
+
_collection_task: Optional[asyncio.Task] = None
|
|
24
|
+
|
|
25
|
+
async def _background_collector():
|
|
26
|
+
"""后台任务:持续收集指标数据"""
|
|
27
|
+
global _latest_metrics
|
|
28
|
+
while True:
|
|
29
|
+
try:
|
|
30
|
+
# 在线程池中运行同步的collect()方法,避免阻塞事件循环
|
|
31
|
+
metrics = await asyncio.to_thread(collector.collect)
|
|
32
|
+
async with _metrics_lock:
|
|
33
|
+
_latest_metrics = metrics
|
|
34
|
+
history.add_metrics(metrics)
|
|
35
|
+
# 收集间隔:1秒(1fps)
|
|
36
|
+
await asyncio.sleep(1.0)
|
|
37
|
+
except Exception as e:
|
|
38
|
+
print(f"Background collector error: {e}")
|
|
39
|
+
await asyncio.sleep(0.5)
|
|
40
|
+
|
|
41
|
+
async def start_background_collector():
|
|
42
|
+
"""启动后台收集任务"""
|
|
43
|
+
global _collection_task
|
|
44
|
+
if _collection_task is None or _collection_task.done():
|
|
45
|
+
_collection_task = asyncio.create_task(_background_collector())
|
|
46
|
+
|
|
47
|
+
@router.websocket("/metrics")
|
|
48
|
+
async def websocket_metrics(websocket: WebSocket):
|
|
49
|
+
"""WebSocket 实时推送系统指标"""
|
|
50
|
+
await websocket.accept()
|
|
51
|
+
|
|
52
|
+
# 确保后台收集任务已启动
|
|
53
|
+
await start_background_collector()
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
while True:
|
|
57
|
+
# 读取最新数据(不等待收集完成)
|
|
58
|
+
async with _metrics_lock:
|
|
59
|
+
metrics = _latest_metrics
|
|
60
|
+
|
|
61
|
+
if metrics is None:
|
|
62
|
+
# 如果还没有数据,等待一下
|
|
63
|
+
await asyncio.sleep(0.1)
|
|
64
|
+
continue
|
|
65
|
+
|
|
66
|
+
# 计算 P 核和 E 核的使用率
|
|
67
|
+
cpu_count = metrics.cpu_count
|
|
68
|
+
cpu_per_core = metrics.cpu_per_core
|
|
69
|
+
|
|
70
|
+
# 根据 CPU 核心数判断 P 核和 E 核数量
|
|
71
|
+
if cpu_count == 8:
|
|
72
|
+
# M1/M2/M3: 4P + 4E
|
|
73
|
+
p_core_count = 4
|
|
74
|
+
e_core_count = 4
|
|
75
|
+
elif cpu_count == 10:
|
|
76
|
+
# M1 Pro/Max: 8P + 2E
|
|
77
|
+
p_core_count = 8
|
|
78
|
+
e_core_count = 2
|
|
79
|
+
elif cpu_count == 12:
|
|
80
|
+
# M2 Pro/Max: 8P + 4E 或 M3 Pro: 6P + 6E
|
|
81
|
+
# 默认假设 8P + 4E
|
|
82
|
+
p_core_count = 8
|
|
83
|
+
e_core_count = 4
|
|
84
|
+
elif cpu_count == 16:
|
|
85
|
+
# M3 Max: 12P + 4E
|
|
86
|
+
p_core_count = 12
|
|
87
|
+
e_core_count = 4
|
|
88
|
+
else:
|
|
89
|
+
# 默认:前一半是 P 核
|
|
90
|
+
p_core_count = cpu_count // 2
|
|
91
|
+
e_core_count = cpu_count - p_core_count
|
|
92
|
+
|
|
93
|
+
# 计算 P 核和 E 核的使用率
|
|
94
|
+
p_cores = cpu_per_core[:p_core_count] if len(cpu_per_core) >= p_core_count else []
|
|
95
|
+
e_cores = cpu_per_core[p_core_count:] if len(cpu_per_core) > p_core_count else []
|
|
96
|
+
|
|
97
|
+
# 计算 P 核和 E 核对总CPU使用率的贡献百分比
|
|
98
|
+
# cpu_percent 是所有核心的平均使用率,即 sum(cpu_per_core) / cpu_count
|
|
99
|
+
# 所以 P核贡献 = sum(p_cores) / cpu_count
|
|
100
|
+
# E核贡献 = sum(e_cores) / cpu_count
|
|
101
|
+
# P核贡献 + E核贡献 = cpu_percent
|
|
102
|
+
if cpu_count > 0:
|
|
103
|
+
cpu_p_percent = (sum(p_cores) / cpu_count) if p_cores else 0.0
|
|
104
|
+
cpu_e_percent = (sum(e_cores) / cpu_count) if e_cores else 0.0
|
|
105
|
+
else:
|
|
106
|
+
cpu_p_percent = 0.0
|
|
107
|
+
cpu_e_percent = 0.0
|
|
108
|
+
|
|
109
|
+
# 发送数据
|
|
110
|
+
await websocket.send_json({
|
|
111
|
+
"cpu_percent": metrics.cpu_percent,
|
|
112
|
+
"cpu_per_core": metrics.cpu_per_core,
|
|
113
|
+
"cpu_count": metrics.cpu_count,
|
|
114
|
+
"cpu_p_percent": cpu_p_percent,
|
|
115
|
+
"cpu_e_percent": cpu_e_percent,
|
|
116
|
+
"memory_percent": metrics.memory_percent,
|
|
117
|
+
"memory_total": metrics.memory_total,
|
|
118
|
+
"memory_used": metrics.memory_used,
|
|
119
|
+
"memory_available": metrics.memory_available,
|
|
120
|
+
"network_sent_rate": metrics.network_sent_rate,
|
|
121
|
+
"network_recv_rate": metrics.network_recv_rate,
|
|
122
|
+
"cpu_power": metrics.cpu_power,
|
|
123
|
+
"gpu_power": metrics.gpu_power,
|
|
124
|
+
"ane_power": metrics.ane_power,
|
|
125
|
+
"system_power": metrics.system_power,
|
|
126
|
+
"gpu_usage": metrics.gpu_usage,
|
|
127
|
+
"gpu_freq_mhz": metrics.gpu_freq_mhz,
|
|
128
|
+
"ane_usage": metrics.ane_usage,
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
# 等待 1 秒(1fps)
|
|
132
|
+
await asyncio.sleep(1.0)
|
|
133
|
+
|
|
134
|
+
except WebSocketDisconnect:
|
|
135
|
+
pass
|
|
136
|
+
except Exception as e:
|
|
137
|
+
print(f"WebSocket error: {e}")
|
|
138
|
+
|
yamon/cli.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Command-line interface for Yamon"""
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import sys
|
|
6
|
+
import uvicorn
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def main():
|
|
11
|
+
"""Main entry point for yamon command"""
|
|
12
|
+
parser = argparse.ArgumentParser(
|
|
13
|
+
description="Yamon - Visually pleasing, deep system monitoring for macOS"
|
|
14
|
+
)
|
|
15
|
+
parser.add_argument(
|
|
16
|
+
"--host",
|
|
17
|
+
default="0.0.0.0",
|
|
18
|
+
help="Host to bind to (default: 0.0.0.0)",
|
|
19
|
+
)
|
|
20
|
+
parser.add_argument(
|
|
21
|
+
"--port",
|
|
22
|
+
type=int,
|
|
23
|
+
default=8000,
|
|
24
|
+
help="Port to bind to (default: 8000)",
|
|
25
|
+
)
|
|
26
|
+
parser.add_argument(
|
|
27
|
+
"--reload",
|
|
28
|
+
action="store_true",
|
|
29
|
+
help="Enable auto-reload (development mode)",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
args = parser.parse_args()
|
|
33
|
+
|
|
34
|
+
# Import app after parsing args
|
|
35
|
+
from yamon.main import app
|
|
36
|
+
|
|
37
|
+
uvicorn.run(
|
|
38
|
+
app,
|
|
39
|
+
host=args.host,
|
|
40
|
+
port=args.port,
|
|
41
|
+
reload=args.reload,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
if __name__ == "__main__":
|
|
46
|
+
main()
|
|
47
|
+
|