pyre-cli 10.3.0 → 10.5.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 +41 -0
- package/dist/{alertChannels-TPSQIJ7S.js → alertChannels-MNX4XH6J.js} +1 -0
- package/dist/{anomaliesCmd-25OFKYPK.js → anomaliesCmd-HHTY5LYL.js} +2 -0
- package/dist/benchGitCmd-HIXUC5EK.js +87 -0
- package/dist/{benchmarkCmd-A3ETK6VY.js → benchmarkCmd-B7DLP2VC.js} +2 -0
- package/dist/{blender-FGH3ASEQ.js → blender-6ZX5SPYC.js} +3 -1
- package/dist/{brewHealth-P2GW5Y5C.js → brewHealth-VFW33QB3.js} +1 -0
- package/dist/budgetCmd-TGO3RYLN.js +11 -0
- package/dist/{buildCmd-CDPZ2ZFA.js → buildCmd-E7KUCFYG.js} +3 -1
- package/dist/{checkCmd-WQAHAUWO.js → checkCmd-QLYTP77Y.js} +5 -3
- package/dist/chunk-3RG5ZIWI.js +10 -0
- package/dist/{chunk-VIACN2RU.js → chunk-FYCBGUTW.js} +41 -19
- package/dist/chunk-JH7P6KLN.js +143 -0
- package/dist/{chunk-QM5L43EK.js → chunk-LDJB5V4N.js} +5 -1
- package/dist/chunk-NMGQVRCA.js +111 -0
- package/dist/chunk-OCL7P7N7.js +936 -0
- package/dist/{chunk-23YWCI3O.js → chunk-QEH4LR6P.js} +28 -686
- package/dist/{chunk-KVUMOA6C.js → chunk-RKPGPVTO.js} +77 -4
- package/dist/chunk-UF3XQ3R3.js +3800 -0
- package/dist/{collectors-JDN5Y7EV.js → collectors-V7SCIIDH.js} +5 -3
- package/dist/{completions-ZVTNE52B.js → completions-FTPXDSR2.js} +44 -3
- package/dist/{config-J4YMADEF.js → config-5WW5M63G.js} +2 -1
- package/dist/containersCmd-FSMYOBG2.js +84 -0
- package/dist/{doctor-LHFW4UJP.js → doctor-O4DIAMHK.js} +3 -2
- package/dist/{explainCmd-MF6PIMLW.js → explainCmd-7PBPA7O5.js} +1 -0
- package/dist/{extensions-XJGHG7X3.js → extensions-QQEV7U3D.js} +1 -0
- package/dist/{firebaseSync-ON3RZ6KX.js → firebaseSync-LRN67UTV.js} +2 -0
- package/dist/flightRecorder-GJIAQOXC.js +162 -0
- package/dist/index.js +736 -4125
- package/dist/inspectCmd-5FXJBJTK.js +137 -0
- package/dist/menu-AJFGDLBP.js +1233 -0
- package/dist/monitors/platform/windows-ui.ps1 +31 -29
- package/dist/{netUsageCmd-67IBZZYN.js → netUsageCmd-JP6YKBPF.js} +7 -5
- package/dist/optimizeCmd-WZKLFYZU.js +1110 -0
- package/dist/{pipeCmd-2N4JPKYE.js → pipeCmd-CPK6FVKB.js} +5 -3
- package/dist/playbookCmd-FRROIEXR.js +145 -0
- package/dist/{profileProcCmd-N4H6MCBT.js → profileProcCmd-5Y3733E7.js} +64 -13
- package/dist/{prometheus-5BL5PVLC.js → prometheus-CYNUR3QT.js} +1 -0
- package/dist/{prometheusCmd-LEUM5WCZ.js → prometheusCmd-G2CSEZRQ.js} +5 -3
- package/dist/{replayCmd-Z3PYXY7V.js → replayCmd-ODB5SPOQ.js} +25 -6
- package/dist/{run-HOGQDDTA.js → run-UJQYT2TO.js} +1 -0
- package/dist/{serveCmd-L7LVJOZN.js → serveCmd-KDPWLVQI.js} +5 -3
- package/dist/{service-7GIX3YNC.js → service-LIYYR3UI.js} +1 -0
- package/dist/{smartCmd-ODWKSFNG.js → smartCmd-2BJJ37GV.js} +130 -5
- package/dist/statuslineCmd-EUKYRR2G.js +75 -0
- package/dist/{stressCmd-MFXMFXYX.js → stressCmd-GKHGY26X.js} +2 -0
- package/dist/{syncCmd-42IRF633.js → syncCmd-MIBFKX24.js} +16 -5
- package/dist/topoCmd-RTXQJ6XA.js +189 -0
- package/dist/{updateCheck-52X6LJG5.js → updateCheck-LJHEMG3G.js} +2 -0
- package/dist/{watchdogCmd-6GR4UN6B.js → watchdogCmd-XM6OPTKM.js} +41 -6
- package/dist/{wizard-JQMQWFAW.js → wizard-PRDOJROQ.js} +2 -1
- package/package.json +2 -2
- package/src/benchGitCmd.ts +110 -0
- package/src/budgetCmd.ts +199 -0
- package/src/completions.ts +42 -3
- package/src/containersCmd.ts +106 -0
- package/src/doctor.ts +86 -6
- package/src/easy_features.test.ts +83 -0
- package/src/flightRecorder.ts +214 -0
- package/src/index.ts +181 -8
- package/src/inspectCmd.ts +165 -0
- package/src/live/input.ts +70 -3
- package/src/live/render.ts +1 -1
- package/src/live/state.ts +2 -1
- package/src/menu/commands.ts +754 -0
- package/src/menu/index.ts +358 -0
- package/src/menu/render.ts +272 -0
- package/src/menu/types.ts +63 -0
- package/src/menu.test.ts +110 -0
- package/src/monitors/blender.ts +49 -19
- package/src/monitors/buildTracker.ts +72 -54
- package/src/monitors/platform/windows-ui.ps1 +31 -29
- package/src/monitors/platform/windows.ts +636 -280
- package/src/optimizeCmd.ts +1248 -0
- package/src/platform.test.ts +146 -0
- package/src/playbookCmd.ts +156 -0
- package/src/power_features.test.ts +251 -0
- package/src/profileProcCmd.ts +68 -21
- package/src/replayCmd.ts +15 -6
- package/src/smartCmd.ts +149 -10
- package/src/state/config.ts +13 -0
- package/src/statuslineCmd.ts +87 -0
- package/src/syncCmd.ts +12 -0
- package/src/topoCmd.ts +83 -50
- package/src/watchdogCmd.ts +40 -3
- package/src/xbar.ts +195 -17
- package/dist/chunk-M73HCQD2.js +0 -95
- package/dist/topoCmd-DZ3YS5IL.js +0 -148
package/README.md
CHANGED
|
@@ -107,6 +107,7 @@ Launch with `pyre live` for a responsive, full-terminal dashboard featuring:
|
|
|
107
107
|
| **Fleet & SSH Aggregation** | Monitor remote nodes over SSH (`pyre ssh <host>`) or aggregate hundreds of servers into an auto-tiled overview (`pyre fleet`). |
|
|
108
108
|
| **Socket & Packet Forensics** | Track socket connection states (`ESTABLISHED`, `LISTENING`, `TIME_WAIT`), top remote hosts, and per-process network bandwidth. |
|
|
109
109
|
| **AI Anomaly Explanation** | Explain performance spikes and thermal throttles in plain English using local Ollama models or OpenAI (`pyre explain`). |
|
|
110
|
+
| **macOS System Optimizer** | Reclaim memory and disk space: quit idle/forgotten apps, flush DNS, purge inactive RAM, clean developer caches, empty trash, and audit broken startup agents (`pyre optimize`). |
|
|
110
111
|
| **Multi-Format Export** | Export instant snapshots to JSON, CSV, TSV, HTML, or Markdown reports, plus an importable Grafana dashboard template. |
|
|
111
112
|
|
|
112
113
|
---
|
|
@@ -239,6 +240,7 @@ pyre alert test # Send a test notification to all configured ale
|
|
|
239
240
|
pyre profile-proc <pid> # CPU call-tree profiler and hot function sampler
|
|
240
241
|
pyre sync <push|pull|status> # Synchronize config profiles across machines
|
|
241
242
|
pyre xbar # Generate an xbar / SwiftBar menu bar plugin script
|
|
243
|
+
pyre optimize [subcommand] # macOS optimizer: quit idle apps, memory, caches, trash, startup, dns
|
|
242
244
|
pyre update # Check for pyre-cli updates
|
|
243
245
|
```
|
|
244
246
|
|
|
@@ -264,6 +266,45 @@ Security and networking capabilities:
|
|
|
264
266
|
|
|
265
267
|
---
|
|
266
268
|
|
|
269
|
+
## macOS System Optimizer (`pyre optimize`)
|
|
270
|
+
|
|
271
|
+
Active memory, disk, and application lifecycle optimization for macOS:
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
# 1. System health & optimization overview report (safe, dry-run by default)
|
|
275
|
+
pyre optimize
|
|
276
|
+
|
|
277
|
+
# 2. Find and gracefully quit apps open but unused recently
|
|
278
|
+
pyre optimize apps # Interactive prompt with memory & CPU summary
|
|
279
|
+
pyre optimize apps --idle 30m --keep 3 # Custom idle duration & recent apps to keep
|
|
280
|
+
pyre optimize apps --dry-run # Preview without quitting
|
|
281
|
+
|
|
282
|
+
# 3. Background app activity tracker (records precise frontmost app usage)
|
|
283
|
+
pyre optimize track # Run tracker in current terminal
|
|
284
|
+
pyre optimize track --install # Install lightweight background launchd agent
|
|
285
|
+
pyre optimize track --auto-quit 2h # Daemon auto-quits idle apps & sends desktop alert
|
|
286
|
+
pyre optimize track --uninstall # Remove background tracker
|
|
287
|
+
|
|
288
|
+
# 4. Memory pressure & inactive purge
|
|
289
|
+
pyre optimize memory # Shows pressure, swap, compressed RAM, top consumers
|
|
290
|
+
pyre optimize memory -y # Flushes inactive file cache via `sudo purge`
|
|
291
|
+
|
|
292
|
+
# 5. Developer & system caches
|
|
293
|
+
pyre optimize caches # Measure Xcode DerivedData, Homebrew, npm/yarn/pnpm
|
|
294
|
+
pyre optimize caches --clean # Clean selected caches (safely skips running apps)
|
|
295
|
+
|
|
296
|
+
# 6. Trash & startup maintenance
|
|
297
|
+
pyre optimize trash --empty # Measure and empty macOS Trash
|
|
298
|
+
pyre optimize startup # Audit Login Items & LaunchAgents, flag broken leftovers
|
|
299
|
+
pyre optimize dns # Flush macOS DNS cache via dscacheutil & mDNSResponder
|
|
300
|
+
|
|
301
|
+
# 7. Protect whitelist
|
|
302
|
+
pyre optimize protect "Spotify" # Whitelist app so it is never automatically quit
|
|
303
|
+
pyre optimize unprotect "Spotify" # Remove app from custom whitelist
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
267
308
|
## Requirements
|
|
268
309
|
|
|
269
310
|
| Platform | Minimum Requirement | Notes |
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatBytes,
|
|
3
|
+
runWithBudget
|
|
4
|
+
} from "./chunk-JH7P6KLN.js";
|
|
5
|
+
import "./chunk-3RG5ZIWI.js";
|
|
6
|
+
|
|
7
|
+
// src/benchGitCmd.ts
|
|
8
|
+
import { execSync } from "child_process";
|
|
9
|
+
import chalk from "chalk";
|
|
10
|
+
async function runBenchGitCommand(branchA, branchB, commandStr) {
|
|
11
|
+
try {
|
|
12
|
+
const status = execSync("git status --porcelain", { encoding: "utf-8" }).trim();
|
|
13
|
+
if (status) {
|
|
14
|
+
console.error(chalk.red("\n \u2716 Working directory contains uncommitted changes."));
|
|
15
|
+
console.log(chalk.yellow(" Please commit or stash your changes before running `pyre bench-git`.\n"));
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
} catch (err) {
|
|
19
|
+
console.error(chalk.red(`
|
|
20
|
+
\u2716 Not a git repository: ${err.message}`));
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const startingBranch = execSync("git rev-parse --abbrev-ref HEAD", { encoding: "utf-8" }).trim();
|
|
24
|
+
console.log(chalk.bold(`
|
|
25
|
+
\u{1F525} pyre bench-git \u2014 Cross-Branch Performance Differ
|
|
26
|
+
`));
|
|
27
|
+
console.log(chalk.dim(` Command: ${commandStr}`));
|
|
28
|
+
console.log(chalk.dim(` Baseline Branch: ${branchA}`));
|
|
29
|
+
console.log(chalk.dim(` Target Branch: ${branchB}`));
|
|
30
|
+
console.log(chalk.dim(` Current Branch: ${startingBranch}
|
|
31
|
+
`));
|
|
32
|
+
const tokens = commandStr.split(/\s+/).filter(Boolean);
|
|
33
|
+
try {
|
|
34
|
+
console.log(chalk.bold.cyan(`\u25B6 [1/2] Checking out '${branchA}' and benchmarking...`));
|
|
35
|
+
execSync(`git checkout ${branchA}`, { stdio: "pipe" });
|
|
36
|
+
const resA = await runWithBudget(tokens, {});
|
|
37
|
+
console.log(chalk.bold.cyan(`
|
|
38
|
+
\u25B6 [2/2] Checking out '${branchB}' and benchmarking...`));
|
|
39
|
+
execSync(`git checkout ${branchB}`, { stdio: "pipe" });
|
|
40
|
+
const resB = await runWithBudget(tokens, {});
|
|
41
|
+
execSync(`git checkout ${startingBranch}`, { stdio: "pipe" });
|
|
42
|
+
console.log(chalk.dim(`
|
|
43
|
+
Restored workspace to '${startingBranch}'.`));
|
|
44
|
+
printGitComparisonTable(branchA, branchB, resA, resB);
|
|
45
|
+
} catch (err) {
|
|
46
|
+
try {
|
|
47
|
+
execSync(`git checkout ${startingBranch}`, { stdio: "pipe" });
|
|
48
|
+
} catch {
|
|
49
|
+
}
|
|
50
|
+
console.error(chalk.red(`
|
|
51
|
+
\u2716 Benchmark failed: ${err.message}`));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function printGitComparisonTable(branchA, branchB, resA, resB) {
|
|
55
|
+
const diffPct = (valA, valB, invertGood = false) => {
|
|
56
|
+
if (valA === 0) return "0%";
|
|
57
|
+
const delta = (valB - valA) / valA * 100;
|
|
58
|
+
const sign = delta > 0 ? "+" : "";
|
|
59
|
+
const formatted = `${sign}${delta.toFixed(1)}%`;
|
|
60
|
+
const isGood = invertGood ? delta > 0 : delta < 0;
|
|
61
|
+
const color = delta === 0 ? chalk.dim : isGood ? chalk.green : chalk.red;
|
|
62
|
+
return color(formatted);
|
|
63
|
+
};
|
|
64
|
+
console.log(chalk.bold("\n \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
65
|
+
console.log(chalk.bold(" \u{1F525} Cross-Branch Performance Comparison Results"));
|
|
66
|
+
console.log(chalk.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
67
|
+
const colBranchA = branchA.padEnd(16);
|
|
68
|
+
const colBranchB = branchB.padEnd(16);
|
|
69
|
+
console.log(` ${"Metric".padEnd(18)} ${colBranchA} ${colBranchB} Delta`);
|
|
70
|
+
console.log(chalk.dim(" \u2500".repeat(30)));
|
|
71
|
+
const durA = `${resA.durationSec.toFixed(2)}s`.padEnd(16);
|
|
72
|
+
const durB = `${resB.durationSec.toFixed(2)}s`.padEnd(16);
|
|
73
|
+
console.log(` ${"Duration".padEnd(18)} ${durA} ${durB} ${diffPct(resA.durationSec, resB.durationSec)}`);
|
|
74
|
+
const ramA = formatBytes(resA.peakMemoryBytes).padEnd(16);
|
|
75
|
+
const ramB = formatBytes(resB.peakMemoryBytes).padEnd(16);
|
|
76
|
+
console.log(` ${"Peak Memory".padEnd(18)} ${ramA} ${ramB} ${diffPct(resA.peakMemoryBytes, resB.peakMemoryBytes)}`);
|
|
77
|
+
const cpuA = `${resA.peakCpuPct.toFixed(1)}%`.padEnd(16);
|
|
78
|
+
const cpuB = `${resB.peakCpuPct.toFixed(1)}%`.padEnd(16);
|
|
79
|
+
console.log(` ${"Peak CPU".padEnd(18)} ${cpuA} ${cpuB} ${diffPct(resA.peakCpuPct, resB.peakCpuPct)}`);
|
|
80
|
+
const codeA = `${resA.exitCode}`.padEnd(16);
|
|
81
|
+
const codeB = `${resB.exitCode}`.padEnd(16);
|
|
82
|
+
console.log(` ${"Exit Code".padEnd(18)} ${codeA} ${codeB}`);
|
|
83
|
+
console.log(chalk.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
|
|
84
|
+
}
|
|
85
|
+
export {
|
|
86
|
+
runBenchGitCommand
|
|
87
|
+
};
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import "./chunk-IM3IQMMT.js";
|
|
2
2
|
import {
|
|
3
3
|
collectAll
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
6
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-QEH4LR6P.js";
|
|
5
|
+
import "./chunk-NMGQVRCA.js";
|
|
6
|
+
import "./chunk-FYCBGUTW.js";
|
|
7
|
+
import "./chunk-OCL7P7N7.js";
|
|
7
8
|
import "./chunk-MZO5QKUR.js";
|
|
9
|
+
import "./chunk-3RG5ZIWI.js";
|
|
8
10
|
|
|
9
11
|
// src/checkCmd.ts
|
|
10
12
|
import chalk from "chalk";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
__require
|
|
10
|
+
};
|
|
@@ -1,14 +1,20 @@
|
|
|
1
|
+
import {
|
|
2
|
+
collectWindowsProcesses
|
|
3
|
+
} from "./chunk-OCL7P7N7.js";
|
|
1
4
|
import {
|
|
2
5
|
run
|
|
3
6
|
} from "./chunk-MZO5QKUR.js";
|
|
4
7
|
|
|
5
8
|
// src/monitors/blender.ts
|
|
6
|
-
|
|
9
|
+
import fs from "fs";
|
|
10
|
+
import os from "os";
|
|
11
|
+
import path from "path";
|
|
12
|
+
var RENDER_LOG_DIR = path.join(os.tmpdir(), "blender_render_logs");
|
|
7
13
|
function blendFileFromArgs(args) {
|
|
8
14
|
const nonFlag = args.filter((a) => !a.startsWith("-"));
|
|
9
15
|
for (const a of nonFlag) {
|
|
10
16
|
if (a.endsWith(".blend") || a.includes(".blend")) {
|
|
11
|
-
return a.split(
|
|
17
|
+
return a.split(/[/\\]/).pop() || a;
|
|
12
18
|
}
|
|
13
19
|
}
|
|
14
20
|
return "unknown.blend";
|
|
@@ -24,15 +30,17 @@ function outputPathFromArgs(args) {
|
|
|
24
30
|
const oIdx = args.indexOf("-o");
|
|
25
31
|
if (oIdx !== -1 && args[oIdx + 1]) {
|
|
26
32
|
let p = args[oIdx + 1];
|
|
27
|
-
if (p.endsWith("/")) p = p.slice(0, -1);
|
|
28
|
-
|
|
33
|
+
if (p.endsWith("/") || p.endsWith("\\")) p = p.slice(0, -1);
|
|
34
|
+
const parts = p.split(/[/\\]/);
|
|
35
|
+
return parts.slice(0, -1).join("/") || os.tmpdir();
|
|
29
36
|
}
|
|
30
37
|
const fFlag = args.find((a) => a.startsWith("-o="));
|
|
31
38
|
if (fFlag) {
|
|
32
39
|
const p = fFlag.slice(3);
|
|
33
|
-
|
|
40
|
+
const parts = p.split(/[/\\]/);
|
|
41
|
+
return parts.slice(0, -1).join("/") || os.tmpdir();
|
|
34
42
|
}
|
|
35
|
-
return
|
|
43
|
+
return os.tmpdir();
|
|
36
44
|
}
|
|
37
45
|
function frameRangeFromArgs(args) {
|
|
38
46
|
const fIdx = args.indexOf("-f");
|
|
@@ -54,11 +62,12 @@ function frameRangeFromArgs(args) {
|
|
|
54
62
|
async function parseRenderLog(blendName) {
|
|
55
63
|
const baseName = blendName.replace(/\.blend$/i, "");
|
|
56
64
|
try {
|
|
57
|
-
|
|
65
|
+
if (!fs.existsSync(RENDER_LOG_DIR)) return null;
|
|
66
|
+
const entries = fs.readdirSync(RENDER_LOG_DIR).filter((f) => f.startsWith(`${baseName}_`)).map((f) => ({ name: f, time: fs.statSync(path.join(RENDER_LOG_DIR, f)).mtimeMs })).sort((a, b) => b.time - a.time).slice(0, 5);
|
|
58
67
|
for (const entry of entries) {
|
|
59
|
-
const logPath =
|
|
68
|
+
const logPath = path.join(RENDER_LOG_DIR, entry.name);
|
|
60
69
|
try {
|
|
61
|
-
const content =
|
|
70
|
+
const content = fs.readFileSync(logPath, "utf-8");
|
|
62
71
|
if (!content) continue;
|
|
63
72
|
const lines = content.split("\n");
|
|
64
73
|
const rendered = lines.filter((l) => /Fra:(\d+)/i.test(l)).length;
|
|
@@ -80,18 +89,31 @@ async function parseRenderLog(blendName) {
|
|
|
80
89
|
async function collectBlenderRenders() {
|
|
81
90
|
const renders = [];
|
|
82
91
|
try {
|
|
83
|
-
const
|
|
84
|
-
if (
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
+
const procList = [];
|
|
93
|
+
if (process.platform === "win32") {
|
|
94
|
+
const procs = await collectWindowsProcesses();
|
|
95
|
+
for (const p of procs) {
|
|
96
|
+
if (/blender/i.test(p.command)) {
|
|
97
|
+
procList.push({ pid: p.pid, cpuPct: p.cpu, command: p.command });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
} else {
|
|
101
|
+
const psRaw = await run("ps -eo pid,pcpu,pmem,state,command 2>/dev/null | grep -iE '[b]lender'", "", 2e3);
|
|
102
|
+
if (psRaw) {
|
|
103
|
+
const lines = psRaw.split("\n");
|
|
104
|
+
for (const line of lines) {
|
|
105
|
+
const parts = line.trim().match(/\s*(\d+)\s+([\d.]+)\s+([\d.]+)\s+(\S+)\s+(.+)/);
|
|
106
|
+
if (parts) {
|
|
107
|
+
procList.push({ pid: parseInt(parts[1], 10), cpuPct: parseFloat(parts[2]), command: parts[5] });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
for (const p of procList) {
|
|
113
|
+
const { pid, cpuPct, command } = p;
|
|
92
114
|
const args = command.split(/\s+/);
|
|
93
115
|
const isRender = /-b|--background|-f\s/.test(command) || /render/i.test(command);
|
|
94
|
-
if (!isRender && !command.includes("blender")) continue;
|
|
116
|
+
if (!isRender && !command.toLowerCase().includes("blender")) continue;
|
|
95
117
|
const blendFile = blendFileFromArgs(args);
|
|
96
118
|
const engine = engineFromArgs(args);
|
|
97
119
|
const outputPath = outputPathFromArgs(args);
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__require
|
|
3
|
+
} from "./chunk-3RG5ZIWI.js";
|
|
4
|
+
|
|
5
|
+
// src/budgetCmd.ts
|
|
6
|
+
import fs from "fs";
|
|
7
|
+
import { spawn } from "child_process";
|
|
8
|
+
import chalk from "chalk";
|
|
9
|
+
function parseBytesString(s) {
|
|
10
|
+
const trimmed = s.trim().toUpperCase();
|
|
11
|
+
const num = parseFloat(trimmed);
|
|
12
|
+
if (isNaN(num)) return 0;
|
|
13
|
+
if (trimmed.endsWith("G") || trimmed.endsWith("GB")) return num * 1024 * 1024 * 1024;
|
|
14
|
+
if (trimmed.endsWith("M") || trimmed.endsWith("MB")) return num * 1024 * 1024;
|
|
15
|
+
if (trimmed.endsWith("K") || trimmed.endsWith("KB")) return num * 1024;
|
|
16
|
+
return num;
|
|
17
|
+
}
|
|
18
|
+
function formatBytes(b) {
|
|
19
|
+
if (b >= 1024 * 1024 * 1024) return `${(b / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
|
20
|
+
if (b >= 1024 * 1024) return `${(b / (1024 * 1024)).toFixed(2)} MB`;
|
|
21
|
+
if (b >= 1024) return `${(b / 1024).toFixed(1)} KB`;
|
|
22
|
+
return `${b} B`;
|
|
23
|
+
}
|
|
24
|
+
async function runWithBudget(commandArgs, budgets, opts) {
|
|
25
|
+
if (commandArgs.length === 0) {
|
|
26
|
+
throw new Error("No command specified. Usage: pyre run [budget flags] -- <command>");
|
|
27
|
+
}
|
|
28
|
+
const cmdStr = commandArgs.join(" ");
|
|
29
|
+
const [bin, ...args] = commandArgs;
|
|
30
|
+
const startTime = Date.now();
|
|
31
|
+
const cpuSamples = [];
|
|
32
|
+
let peakMemBytes = 0;
|
|
33
|
+
let peakCpu = 0;
|
|
34
|
+
const child = spawn(bin, args, {
|
|
35
|
+
stdio: "inherit",
|
|
36
|
+
env: process.env
|
|
37
|
+
});
|
|
38
|
+
const pid = child.pid;
|
|
39
|
+
let sampleInterval = null;
|
|
40
|
+
if (pid) {
|
|
41
|
+
sampleInterval = setInterval(() => {
|
|
42
|
+
try {
|
|
43
|
+
const { execSync } = __require("child_process");
|
|
44
|
+
const output = execSync(`ps -o %cpu,rss -p ${pid} 2>/dev/null`, { encoding: "utf-8" });
|
|
45
|
+
const lines = output.trim().split("\n");
|
|
46
|
+
if (lines.length > 1) {
|
|
47
|
+
const parts = lines[1].trim().split(/\s+/);
|
|
48
|
+
const cpu = parseFloat(parts[0]) || 0;
|
|
49
|
+
const rssKb = parseInt(parts[1], 10) || 0;
|
|
50
|
+
const memBytes = rssKb * 1024;
|
|
51
|
+
cpuSamples.push(cpu);
|
|
52
|
+
if (cpu > peakCpu) peakCpu = cpu;
|
|
53
|
+
if (memBytes > peakMemBytes) peakMemBytes = memBytes;
|
|
54
|
+
}
|
|
55
|
+
} catch {
|
|
56
|
+
}
|
|
57
|
+
}, 100);
|
|
58
|
+
}
|
|
59
|
+
const exitCode = await new Promise((resolve) => {
|
|
60
|
+
child.on("close", (code) => {
|
|
61
|
+
if (sampleInterval) clearInterval(sampleInterval);
|
|
62
|
+
resolve(code ?? 0);
|
|
63
|
+
});
|
|
64
|
+
child.on("error", () => {
|
|
65
|
+
if (sampleInterval) clearInterval(sampleInterval);
|
|
66
|
+
resolve(1);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
const durationSec = (Date.now() - startTime) / 1e3;
|
|
70
|
+
const avgCpu = cpuSamples.length > 0 ? cpuSamples.reduce((a, b) => a + b, 0) / cpuSamples.length : peakCpu;
|
|
71
|
+
const violations = [];
|
|
72
|
+
if (budgets.maxTimeSec !== void 0 && durationSec > budgets.maxTimeSec) {
|
|
73
|
+
violations.push(`Runtime exceeded budget: ${durationSec.toFixed(2)}s > ${budgets.maxTimeSec}s limit`);
|
|
74
|
+
}
|
|
75
|
+
if (budgets.maxMemoryBytes !== void 0 && peakMemBytes > budgets.maxMemoryBytes) {
|
|
76
|
+
violations.push(`Memory exceeded budget: ${formatBytes(peakMemBytes)} > ${formatBytes(budgets.maxMemoryBytes)} limit`);
|
|
77
|
+
}
|
|
78
|
+
if (budgets.maxCpuPct !== void 0 && peakCpu > budgets.maxCpuPct) {
|
|
79
|
+
violations.push(`CPU exceeded budget: ${peakCpu.toFixed(1)}% > ${budgets.maxCpuPct}% limit`);
|
|
80
|
+
}
|
|
81
|
+
const result = {
|
|
82
|
+
command: cmdStr,
|
|
83
|
+
exitCode,
|
|
84
|
+
durationSec,
|
|
85
|
+
peakCpuPct: peakCpu,
|
|
86
|
+
avgCpuPct: avgCpu,
|
|
87
|
+
peakMemoryBytes: peakMemBytes,
|
|
88
|
+
violations
|
|
89
|
+
};
|
|
90
|
+
printBudgetReport(result, budgets, opts);
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
function printBudgetReport(result, budgets, opts) {
|
|
94
|
+
const isViolated = result.violations.length > 0;
|
|
95
|
+
const isFailed = result.exitCode !== 0 || isViolated;
|
|
96
|
+
console.log(chalk.bold("\n \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
97
|
+
console.log(chalk.bold(` \u{1F525} pyre run \u2014 Performance Budget Report`));
|
|
98
|
+
console.log(chalk.dim(` Command: ${result.command}`));
|
|
99
|
+
console.log(chalk.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
100
|
+
const statusTime = budgets.maxTimeSec === void 0 ? chalk.dim("unconstrained") : result.durationSec <= budgets.maxTimeSec ? chalk.green(`\u2714 Pass (<= ${budgets.maxTimeSec}s)`) : chalk.red(`\u2716 FAIL (> ${budgets.maxTimeSec}s)`);
|
|
101
|
+
const statusMem = budgets.maxMemoryBytes === void 0 ? chalk.dim("unconstrained") : result.peakMemoryBytes <= budgets.maxMemoryBytes ? chalk.green(`\u2714 Pass (<= ${formatBytes(budgets.maxMemoryBytes)})`) : chalk.red(`\u2716 FAIL (> ${formatBytes(budgets.maxMemoryBytes)})`);
|
|
102
|
+
const statusCpu = budgets.maxCpuPct === void 0 ? chalk.dim("unconstrained") : result.peakCpuPct <= budgets.maxCpuPct ? chalk.green(`\u2714 Pass (<= ${budgets.maxCpuPct}%)`) : chalk.red(`\u2716 FAIL (> ${budgets.maxCpuPct}%)`);
|
|
103
|
+
console.log(` Duration: ${result.durationSec.toFixed(2)}s`.padEnd(28) + statusTime);
|
|
104
|
+
console.log(` Peak Memory: ${formatBytes(result.peakMemoryBytes)}`.padEnd(28) + statusMem);
|
|
105
|
+
console.log(` Peak CPU: ${result.peakCpuPct.toFixed(1)}% (avg ${result.avgCpuPct.toFixed(1)}%)`.padEnd(28) + statusCpu);
|
|
106
|
+
console.log(` Exit Code: ${result.exitCode === 0 ? chalk.green("0") : chalk.red(result.exitCode)}`);
|
|
107
|
+
if (isViolated) {
|
|
108
|
+
console.log(chalk.bold.red("\n \u2716 Performance Budget Violations:"));
|
|
109
|
+
result.violations.forEach((v) => console.log(chalk.red(` \u2022 ${v}`)));
|
|
110
|
+
}
|
|
111
|
+
console.log(chalk.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
|
|
112
|
+
if (opts?.outFile || opts?.markdown) {
|
|
113
|
+
const md = generateMarkdownBudgetReport(result, budgets);
|
|
114
|
+
if (opts.outFile) {
|
|
115
|
+
fs.writeFileSync(opts.outFile, md, "utf-8");
|
|
116
|
+
console.log(chalk.dim(` Saved report to ${opts.outFile}
|
|
117
|
+
`));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function generateMarkdownBudgetReport(result, budgets) {
|
|
122
|
+
return [
|
|
123
|
+
`# Pyre Performance Budget Report`,
|
|
124
|
+
``,
|
|
125
|
+
`**Command**: \`${result.command}\` `,
|
|
126
|
+
`**Status**: ${result.violations.length === 0 && result.exitCode === 0 ? "\u2705 PASSED" : "\u274C FAILED"} `,
|
|
127
|
+
`**Exit Code**: \`${result.exitCode}\` `,
|
|
128
|
+
``,
|
|
129
|
+
`| Metric | Measured | Limit | Status |`,
|
|
130
|
+
`| :--- | :--- | :--- | :--- |`,
|
|
131
|
+
`| Duration | ${result.durationSec.toFixed(2)}s | ${budgets.maxTimeSec ? budgets.maxTimeSec + "s" : "N/A"} | ${budgets.maxTimeSec && result.durationSec > budgets.maxTimeSec ? "\u274C FAIL" : "\u2705 PASS"} |`,
|
|
132
|
+
`| Peak Memory | ${formatBytes(result.peakMemoryBytes)} | ${budgets.maxMemoryBytes ? formatBytes(budgets.maxMemoryBytes) : "N/A"} | ${budgets.maxMemoryBytes && result.peakMemoryBytes > budgets.maxMemoryBytes ? "\u274C FAIL" : "\u2705 PASS"} |`,
|
|
133
|
+
`| Peak CPU | ${result.peakCpuPct.toFixed(1)}% | ${budgets.maxCpuPct ? budgets.maxCpuPct + "%" : "N/A"} | ${budgets.maxCpuPct && result.peakCpuPct > budgets.maxCpuPct ? "\u274C FAIL" : "\u2705 PASS"} |`,
|
|
134
|
+
``,
|
|
135
|
+
...result.violations.length > 0 ? [`### Violations`, ...result.violations.map((v) => `- ${v}`)] : []
|
|
136
|
+
].join("\n");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export {
|
|
140
|
+
parseBytesString,
|
|
141
|
+
formatBytes,
|
|
142
|
+
runWithBudget
|
|
143
|
+
};
|
|
@@ -64,7 +64,11 @@ var DEFAULT_CONFIG = {
|
|
|
64
64
|
historyDuration: 60,
|
|
65
65
|
tableDensity: "standard",
|
|
66
66
|
defaultFilter: "all",
|
|
67
|
-
defaultSort: "cpu"
|
|
67
|
+
defaultSort: "cpu",
|
|
68
|
+
optimizeProtectedApps: [],
|
|
69
|
+
optimizeIdleThreshold: "2h",
|
|
70
|
+
optimizeKeepRecent: 5,
|
|
71
|
+
defaultAction: "live"
|
|
68
72
|
};
|
|
69
73
|
function getConfigDir() {
|
|
70
74
|
return process.env.PYRE_CONFIG_DIR || path.join(os.homedir(), ".config", "pyre");
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import {
|
|
2
|
+
collectWindowsProcesses
|
|
3
|
+
} from "./chunk-OCL7P7N7.js";
|
|
4
|
+
import {
|
|
5
|
+
run
|
|
6
|
+
} from "./chunk-MZO5QKUR.js";
|
|
7
|
+
|
|
8
|
+
// src/monitors/buildTracker.ts
|
|
9
|
+
async function collectBuilds() {
|
|
10
|
+
try {
|
|
11
|
+
const builds = /* @__PURE__ */ new Map();
|
|
12
|
+
const buildToolPatterns = [
|
|
13
|
+
{ name: "msbuild", match: /\b(msbuild|devenv)\b/i },
|
|
14
|
+
{ name: "xcodebuild", match: /xcodebuild/i },
|
|
15
|
+
{ name: "cargo", match: /\bcargo\s+(build|check|test|run)?\b/i },
|
|
16
|
+
{ name: "swift", match: /\bswift\s+(build|test)\b/i },
|
|
17
|
+
{ name: "make", match: /\b(make|nmake)\b/i },
|
|
18
|
+
{ name: "ninja", match: /\bninja\b/i },
|
|
19
|
+
{ name: "cmake", match: /\bcmake\b/i },
|
|
20
|
+
{ name: "bazel", match: /\bbazel\b/i },
|
|
21
|
+
{ name: "go", match: /\bgo\s+(build|test|run)\b/i }
|
|
22
|
+
];
|
|
23
|
+
const compilerMatch = /(clang|swift-frontend|rustc|go tool compile|gcc|g\+\+|ghc|cl\.exe|csc\.exe|link\.exe)/i;
|
|
24
|
+
const procList = [];
|
|
25
|
+
if (process.platform === "win32") {
|
|
26
|
+
const winProcs = await collectWindowsProcesses();
|
|
27
|
+
for (const p of winProcs) {
|
|
28
|
+
procList.push({
|
|
29
|
+
pid: p.pid,
|
|
30
|
+
cpu: p.cpu,
|
|
31
|
+
mem: p.mem,
|
|
32
|
+
elapsedSec: p.runtime || 0,
|
|
33
|
+
cmd: p.command
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
} else {
|
|
37
|
+
const raw = await run("ps -eo pid,ppid,pcpu,pmem,etime,command -r 2>/dev/null", "");
|
|
38
|
+
if (!raw) return [];
|
|
39
|
+
const lines = raw.split("\n").slice(1);
|
|
40
|
+
for (const line of lines) {
|
|
41
|
+
if (!line.trim()) continue;
|
|
42
|
+
const parts = line.trim().split(/\s+/);
|
|
43
|
+
if (parts.length < 6) continue;
|
|
44
|
+
const pid = parseInt(parts[0], 10);
|
|
45
|
+
const cpu = parseFloat(parts[2]) || 0;
|
|
46
|
+
const mem = parseFloat(parts[3]) || 0;
|
|
47
|
+
const etimeStr = parts[4];
|
|
48
|
+
const cmd = parts.slice(5).join(" ");
|
|
49
|
+
let elapsedSec = 0;
|
|
50
|
+
const etimeParts = etimeStr.split(":").map(Number);
|
|
51
|
+
if (etimeParts.length === 2) elapsedSec = etimeParts[0] * 60 + etimeParts[1];
|
|
52
|
+
else if (etimeParts.length === 3) elapsedSec = etimeParts[0] * 3600 + etimeParts[1] * 60 + etimeParts[2];
|
|
53
|
+
procList.push({ pid, cpu, mem, elapsedSec, cmd });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
for (const p of procList) {
|
|
57
|
+
for (const pattern of buildToolPatterns) {
|
|
58
|
+
if (pattern.match.test(p.cmd)) {
|
|
59
|
+
builds.set(pattern.name, {
|
|
60
|
+
tool: pattern.name,
|
|
61
|
+
rootPid: p.pid,
|
|
62
|
+
command: p.cmd.slice(0, 80),
|
|
63
|
+
compilerProcesses: 1,
|
|
64
|
+
totalCpu: p.cpu,
|
|
65
|
+
totalMem: p.mem,
|
|
66
|
+
elapsedSec: p.elapsedSec,
|
|
67
|
+
target: p.cmd.split(" ").slice(1, 4).join(" ")
|
|
68
|
+
});
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
for (const p of procList) {
|
|
74
|
+
if (compilerMatch.test(p.cmd)) {
|
|
75
|
+
if (builds.size > 0) {
|
|
76
|
+
const firstBuild = builds.values().next().value;
|
|
77
|
+
if (firstBuild) {
|
|
78
|
+
firstBuild.compilerProcesses += 1;
|
|
79
|
+
firstBuild.totalCpu += p.cpu;
|
|
80
|
+
firstBuild.totalMem += p.mem;
|
|
81
|
+
}
|
|
82
|
+
} else {
|
|
83
|
+
const key = "compiler";
|
|
84
|
+
const existing = builds.get(key);
|
|
85
|
+
if (existing) {
|
|
86
|
+
existing.compilerProcesses += 1;
|
|
87
|
+
existing.totalCpu += p.cpu;
|
|
88
|
+
existing.totalMem += p.mem;
|
|
89
|
+
} else {
|
|
90
|
+
builds.set(key, {
|
|
91
|
+
tool: "compiler",
|
|
92
|
+
rootPid: p.pid,
|
|
93
|
+
command: p.cmd.slice(0, 80),
|
|
94
|
+
compilerProcesses: 1,
|
|
95
|
+
totalCpu: p.cpu,
|
|
96
|
+
totalMem: p.mem,
|
|
97
|
+
elapsedSec: p.elapsedSec
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return Array.from(builds.values());
|
|
104
|
+
} catch {
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export {
|
|
110
|
+
collectBuilds
|
|
111
|
+
};
|