pyre-cli 1.3.0 → 2.0.0
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.
- package/README.md +278 -17
- package/dist/index.js +1763 -350
- package/package.json +6 -3
- package/parity.md +542 -0
- package/src/formatters/index.ts +17 -0
- package/src/formatters/output.ts +165 -0
- package/src/formatters/render.ts +659 -0
- package/src/formatters/themes.ts +115 -0
- package/src/formatters/types.ts +62 -0
- package/src/history.ts +153 -0
- package/src/index.ts +201 -0
- package/src/live/export.ts +76 -0
- package/src/live/index.ts +16 -0
- package/src/live/input.ts +443 -0
- package/src/live/render.ts +146 -0
- package/src/live/state.ts +103 -0
- package/src/live/types.ts +23 -0
- package/src/monitors/collectors.ts +687 -0
- package/src/monitors/index.ts +14 -0
- package/src/monitors/run.ts +18 -0
- package/src/monitors/smc.ts +107 -0
- package/src/monitors/types.ts +148 -0
- package/src/p2p/client.ts +197 -0
- package/src/p2p/index.ts +3 -0
- package/src/p2p/protocol.ts +71 -0
- package/src/p2p/server.ts +219 -0
- package/src/p2p/types.ts +71 -0
- package/src/pyre_img +0 -0
- package/src/sparkline.ts +34 -0
- package/src/splash.ts +102 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data-export and graph-rendering helpers.
|
|
3
|
+
*
|
|
4
|
+
* Provides JSON, CSV, and TSV serialisation of a
|
|
5
|
+
* {@link StatsData} snapshot, as well as sparkline-graph
|
|
6
|
+
* rendering for the live dashboard.
|
|
7
|
+
*/
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
import { sparkline } from '../sparkline.js';
|
|
10
|
+
import type { History } from '../history.js';
|
|
11
|
+
import type { StatsData } from './types.js';
|
|
12
|
+
import { THEMES, type ThemeName } from './themes.js';
|
|
13
|
+
import { formatBytes, celsiusToFahrenheit, formatTemp } from './render.js';
|
|
14
|
+
import { panel } from './render.js';
|
|
15
|
+
|
|
16
|
+
export function formatJson(data: StatsData): string {
|
|
17
|
+
return JSON.stringify(data, null, 2);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function formatCsv(data: StatsData): string {
|
|
21
|
+
const rows: string[][] = [
|
|
22
|
+
['property', 'key', 'value'],
|
|
23
|
+
['cpu', 'brand', data.cpu.brand],
|
|
24
|
+
['cpu', 'usage', String(data.cpu.usage)],
|
|
25
|
+
['cpu', 'loadAvg_1m', String(data.cpu.loadAvg[0])],
|
|
26
|
+
['cpu', 'loadAvg_5m', String(data.cpu.loadAvg[1])],
|
|
27
|
+
['cpu', 'loadAvg_15m', String(data.cpu.loadAvg[2])],
|
|
28
|
+
['memory', 'total', String(data.memory.total)],
|
|
29
|
+
['memory', 'used', String(data.memory.used)],
|
|
30
|
+
['memory', 'usagePercent', String(data.memory.usagePercent)],
|
|
31
|
+
['memory', 'swapUsed', String(data.memory.swapUsed)],
|
|
32
|
+
['memory', 'swapTotal', String(data.memory.swapTotal)],
|
|
33
|
+
['thermal', 'state', data.thermal.state],
|
|
34
|
+
['thermal', 'cpuTempC', data.thermal.temperatures?.cpu_die ? String(data.thermal.temperatures.cpu_die) : ''],
|
|
35
|
+
[
|
|
36
|
+
'thermal',
|
|
37
|
+
'cpuTempF',
|
|
38
|
+
data.thermal.temperatures?.cpu_die != null ? String(celsiusToFahrenheit(data.thermal.temperatures.cpu_die)) : '',
|
|
39
|
+
],
|
|
40
|
+
['network', 'rxBytes', String(data.network.rxBytes)],
|
|
41
|
+
['network', 'txBytes', String(data.network.txBytes)],
|
|
42
|
+
['network', 'rxPackets', String(data.network.rxPackets)],
|
|
43
|
+
['network', 'txPackets', String(data.network.txPackets)],
|
|
44
|
+
['network', 'connections', data.network.connections ? String(data.network.connections) : ''],
|
|
45
|
+
['battery', 'level', data.battery ? String(data.battery.level) : ''],
|
|
46
|
+
['battery', 'powerSource', data.battery ? data.battery.powerSource : ''],
|
|
47
|
+
['battery', 'condition', data.battery?.condition ?? ''],
|
|
48
|
+
['battery', 'maxCapacityPercent', data.battery?.maxCapacityPercent !== undefined ? String(data.battery.maxCapacityPercent) : ''],
|
|
49
|
+
['battery', 'cycles', data.battery?.cycles !== undefined ? String(data.battery.cycles) : ''],
|
|
50
|
+
['battery', 'estimatedTimeToEmpty', data.battery?.estimatedTimeToEmpty ?? ''],
|
|
51
|
+
['battery', 'dischargeRatePerHour', data.battery?.dischargeRatePerHour !== undefined ? String(data.battery.dischargeRatePerHour) : ''],
|
|
52
|
+
['battery', 'powerWatts', data.battery?.powerWatts !== undefined ? String(data.battery.powerWatts) : ''],
|
|
53
|
+
['power', 'cpuWatts', data.power?.cpuWatts !== undefined ? String(data.power.cpuWatts) : ''],
|
|
54
|
+
['power', 'gpuWatts', data.power?.gpuWatts !== undefined ? String(data.power.gpuWatts) : ''],
|
|
55
|
+
['power', 'combinedWatts', data.power?.combinedWatts !== undefined ? String(data.power.combinedWatts) : ''],
|
|
56
|
+
['system', 'hostname', data.header.hostname],
|
|
57
|
+
['system', 'uptime', data.header.uptime],
|
|
58
|
+
['system', 'timestamp', data.timestamp],
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
if (data.packets) {
|
|
62
|
+
rows.push(['packets', 'totalPackets', String(data.packets.totalPackets)]);
|
|
63
|
+
rows.push(['packets', 'rxPackets', String(data.packets.rxPackets)]);
|
|
64
|
+
rows.push(['packets', 'txPackets', String(data.packets.txPackets)]);
|
|
65
|
+
rows.push(['packets', 'connections', String(data.packets.connections)]);
|
|
66
|
+
for (const iface of data.packets.interfaces || []) {
|
|
67
|
+
rows.push(['packets_iface', iface.iface, `${iface.rxPackets},${iface.txPackets},${iface.rxBytes},${iface.txBytes}`]);
|
|
68
|
+
}
|
|
69
|
+
for (const proc of data.packets.allProcesses || []) {
|
|
70
|
+
rows.push(['packets_proc', `pid=${proc.pid}`, `${proc.command},${proc.rxBytes},${proc.txBytes}`]);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (data.gpu) {
|
|
75
|
+
rows.push(['gpu', 'model', data.gpu.model]);
|
|
76
|
+
rows.push(['gpu', 'memory', String(data.gpu.memory)]);
|
|
77
|
+
rows.push(['gpu', 'utilization', String(data.gpu.utilization)]);
|
|
78
|
+
if (data.gpu.temperature) rows.push(['gpu', 'tempC', String(data.gpu.temperature)]);
|
|
79
|
+
rows.push(['gpu', 'processes', String(data.gpu.processes)]);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
for (const p of data.processes) {
|
|
83
|
+
rows.push(['process', `pid=${p.pid}`, `${p.pid}`]);
|
|
84
|
+
rows.push(['process', `ppid=${p.pid}`, String(p.ppid)]);
|
|
85
|
+
rows.push(['process', `user=${p.pid}`, p.user]);
|
|
86
|
+
rows.push(['process', `cpu=${p.pid}`, String(p.cpu)]);
|
|
87
|
+
rows.push(['process', `mem=${p.pid}`, String(p.mem)]);
|
|
88
|
+
rows.push(['process', `state=${p.pid}`, p.state]);
|
|
89
|
+
rows.push(['process', `threads=${p.pid}`, String(p.threads)]);
|
|
90
|
+
rows.push(['process', `runtime=${p.pid}`, String(p.runtime)]);
|
|
91
|
+
rows.push(['process', `command=${p.pid}`, p.command]);
|
|
92
|
+
}
|
|
93
|
+
return rows.map(r => r.map(item => `"${String(item).replace(/"/g, '""')}"`).join(',')).join('\n');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function formatTsv(data: StatsData): string {
|
|
97
|
+
return formatCsv(data).split('\n').map(r => r.split(',').join('\t')).join('\n');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function formatGraphs(history: History, width = 80, themeName: ThemeName = 'default', graphMode: 'spark' | 'bar' = 'spark'): string {
|
|
101
|
+
const theme = THEMES[themeName] || THEMES.default;
|
|
102
|
+
const contentWidth = width - 4;
|
|
103
|
+
const sampleCount = history.cpuUsage.length;
|
|
104
|
+
const sparkWidth = Math.max(10, contentWidth - 28);
|
|
105
|
+
|
|
106
|
+
const lines: string[] = [];
|
|
107
|
+
lines.push(graphRow('CPU %', history.cpuUsage, { min: 0, max: 100 }, v => `${v.toFixed(0)}%`, sparkWidth, graphMode));
|
|
108
|
+
lines.push(graphRow('Mem %', history.memUsage, { min: 0, max: 100 }, v => `${v.toFixed(0)}%`, sparkWidth, graphMode));
|
|
109
|
+
lines.push(
|
|
110
|
+
graphRow('Temp', history.temp, {}, formatTemp, sparkWidth, 'no sensor access (needs sudo powermetrics)', graphMode)
|
|
111
|
+
);
|
|
112
|
+
lines.push(graphRow('Net RX/s', history.netRxRate, {}, v => `${formatBytes(v)}/s`, sparkWidth, graphMode));
|
|
113
|
+
lines.push(graphRow('Net TX/s', history.netTxRate, {}, v => `${formatBytes(v)}/s`, sparkWidth, graphMode));
|
|
114
|
+
if (history.powerWatts.length) {
|
|
115
|
+
lines.push(graphRow('Power W', history.powerWatts, {}, v => `${v.toFixed(1)} W`, sparkWidth, graphMode));
|
|
116
|
+
}
|
|
117
|
+
if (history.rxPacketRate.length) {
|
|
118
|
+
lines.push(graphRow('RX pkt/s', history.rxPacketRate, {}, v => `${v.toFixed(0)} pkt/s`, sparkWidth, graphMode));
|
|
119
|
+
}
|
|
120
|
+
if (history.txPacketRate.length) {
|
|
121
|
+
lines.push(graphRow('TX pkt/s', history.txPacketRate, {}, v => `${v.toFixed(0)} pkt/s`, sparkWidth, graphMode));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const title = `Graphs · mode:${graphMode} · last ${sampleCount} sample${sampleCount === 1 ? '' : 's'}`;
|
|
125
|
+
return panel(title, lines, width, theme.graphs, theme.border).join('\n');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function graphRow(
|
|
129
|
+
label: string,
|
|
130
|
+
values: number[],
|
|
131
|
+
bounds: { min?: number; max?: number },
|
|
132
|
+
fmt: (v: number) => string,
|
|
133
|
+
sparkWidth = 40,
|
|
134
|
+
emptyMessage = 'collecting data...',
|
|
135
|
+
mode: 'spark' | 'bar' = 'spark'
|
|
136
|
+
): string {
|
|
137
|
+
if (!values.length) {
|
|
138
|
+
return `${chalk.dim(label.padEnd(10))} ${chalk.dim(emptyMessage)}`;
|
|
139
|
+
}
|
|
140
|
+
const visible = values.slice(-sparkWidth);
|
|
141
|
+
const spark = mode === 'spark' ? sparkline(visible, bounds) : barGraph(visible, bounds, sparkWidth);
|
|
142
|
+
const current = values[values.length - 1];
|
|
143
|
+
|
|
144
|
+
let color = chalk.cyan;
|
|
145
|
+
if (bounds.min !== undefined && bounds.max !== undefined) {
|
|
146
|
+
const pct = ((current - bounds.min) / (bounds.max - bounds.min || 1)) * 100;
|
|
147
|
+
color = pct > 90 ? chalk.red : pct > 70 ? chalk.yellow : chalk.green;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return `${chalk.dim(label.padEnd(10))}${color(spark)} ${chalk.bold(fmt(current))}`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function barGraph(values: number[], bounds: { min?: number; max?: number }, width: number): string {
|
|
154
|
+
const min = bounds.min ?? Math.min(...values);
|
|
155
|
+
const max = bounds.max ?? Math.max(...values);
|
|
156
|
+
const range = max - min || 1;
|
|
157
|
+
return values.map(v => {
|
|
158
|
+
const pct = (v - min) / range;
|
|
159
|
+
if (pct > 0.8) return '█';
|
|
160
|
+
if (pct > 0.6) return '▇';
|
|
161
|
+
if (pct > 0.4) return '▆';
|
|
162
|
+
if (pct > 0.2) return '▄';
|
|
163
|
+
return ' ';
|
|
164
|
+
}).join('');
|
|
165
|
+
}
|