pyre-cli 10.4.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/package.json +1 -1
- package/src/monitors/platform/windows.ts +49 -26
- package/src/platform.test.ts +11 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pyre-cli",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.5.0",
|
|
4
4
|
"description": "Cross-platform system monitoring CLI for macOS, Linux, and Windows: temps, system stats, live dashboard, packet monitor, battery predictor, export",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -562,7 +562,15 @@ export async function collectWindowsPower(): Promise<PowerData | null> {
|
|
|
562
562
|
|
|
563
563
|
// ─── GPU Telemetry Across Intel, AMD, NVIDIA ─────────────────────────────────
|
|
564
564
|
|
|
565
|
+
let nvidiaSmiPathCache: { path: string | null; checkedAt: number } | null = null;
|
|
566
|
+
const NVIDIA_SMI_NEGATIVE_TTL_MS = 5 * 60 * 1000;
|
|
567
|
+
|
|
565
568
|
async function findNvidiaSmiPath(): Promise<string> {
|
|
569
|
+
if (nvidiaSmiPathCache) {
|
|
570
|
+
if (nvidiaSmiPathCache.path) return nvidiaSmiPathCache.path;
|
|
571
|
+
// Not found previously: only re-probe occasionally (e.g. driver installed while running)
|
|
572
|
+
if (Date.now() - nvidiaSmiPathCache.checkedAt < NVIDIA_SMI_NEGATIVE_TTL_MS) return 'nvidia-smi';
|
|
573
|
+
}
|
|
566
574
|
const commonPaths = [
|
|
567
575
|
'nvidia-smi',
|
|
568
576
|
path.join(process.env.ProgramFiles || 'C:\\Program Files', 'NVIDIA Corporation', 'NVSMI', 'nvidia-smi.exe'),
|
|
@@ -571,14 +579,19 @@ async function findNvidiaSmiPath(): Promise<string> {
|
|
|
571
579
|
for (const p of commonPaths) {
|
|
572
580
|
try {
|
|
573
581
|
const test = await run(`"${p}" -L 2>nul`, '');
|
|
574
|
-
if (test.includes('GPU 0:'))
|
|
582
|
+
if (test.includes('GPU 0:')) {
|
|
583
|
+
nvidiaSmiPathCache = { path: p, checkedAt: Date.now() };
|
|
584
|
+
return p;
|
|
585
|
+
}
|
|
575
586
|
} catch {}
|
|
576
587
|
}
|
|
588
|
+
nvidiaSmiPathCache = { path: null, checkedAt: Date.now() };
|
|
577
589
|
return 'nvidia-smi';
|
|
578
590
|
}
|
|
579
591
|
|
|
580
592
|
async function runNvidiaSmiQuery(query: string): Promise<string> {
|
|
581
593
|
const smiPath = await findNvidiaSmiPath();
|
|
594
|
+
if (nvidiaSmiPathCache && nvidiaSmiPathCache.path === null) return '';
|
|
582
595
|
return (await run(`"${smiPath}" --query-gpu=${query} --format=csv,noheader,nounits 2>nul`, '')).trim();
|
|
583
596
|
}
|
|
584
597
|
|
|
@@ -656,50 +669,60 @@ export function parseWindowsVideoControllers(csvRaw: string): WindowsVideoContro
|
|
|
656
669
|
return controllers;
|
|
657
670
|
}
|
|
658
671
|
|
|
672
|
+
function isVirtualGpuName(name: string): boolean {
|
|
673
|
+
return /microsoft basic|remote display|hyper-v|virtual|vmware|virtualbox|parsec|citrix|indirect display|displaylink/i.test(name);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
function isDiscreteGpuName(name: string): boolean {
|
|
677
|
+
const n = name.toLowerCase();
|
|
678
|
+
if (isVirtualGpuName(n)) return false;
|
|
679
|
+
return (
|
|
680
|
+
/\b(rtx|gtx|geforce|quadro|titan|tesla)\b/.test(n) ||
|
|
681
|
+
/\bnvidia\b/.test(n) ||
|
|
682
|
+
/radeon\s+(rx|pro|vii|r[5-9]|hd)\b/.test(n) ||
|
|
683
|
+
/\bfirepro\b/.test(n) ||
|
|
684
|
+
(/\barc\b/.test(n) && /\b(a\d{3}|b\d{3}|pro)\b/.test(n))
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
|
|
659
688
|
export function prioritizeGpuControllers(controllers: WindowsVideoControllerInfo[]): WindowsVideoControllerInfo[] {
|
|
660
|
-
//
|
|
689
|
+
// Order: discrete (NVIDIA, AMD Radeon RX/Pro, Intel Arc) > integrated (Intel UHD/Iris/Xe, AMD Radeon(TM) Graphics) > virtual/basic adapters
|
|
690
|
+
const rank = (c: WindowsVideoControllerInfo) =>
|
|
691
|
+
isVirtualGpuName(c.name) ? 2 : isDiscreteGpuName(c.name) ? 0 : 1;
|
|
661
692
|
return [...controllers].sort((a, b) => {
|
|
662
|
-
const
|
|
663
|
-
const
|
|
664
|
-
|
|
665
|
-
aName.includes('radeon rx') || aName.includes('arc');
|
|
666
|
-
const bIsDiscrete = bName.includes('rtx') || bName.includes('gtx') || bName.includes('geforce') ||
|
|
667
|
-
bName.includes('radeon rx') || bName.includes('arc');
|
|
668
|
-
if (aIsDiscrete && !bIsDiscrete) return -1;
|
|
669
|
-
if (!aIsDiscrete && bIsDiscrete) return 1;
|
|
693
|
+
const ra = rank(a);
|
|
694
|
+
const rb = rank(b);
|
|
695
|
+
if (ra !== rb) return ra - rb;
|
|
670
696
|
return b.adapterRam - a.adapterRam;
|
|
671
697
|
});
|
|
672
698
|
}
|
|
673
699
|
|
|
674
|
-
export async function getWindowsGpu64BitVram(): Promise<number> {
|
|
700
|
+
export async function getWindowsGpu64BitVram(adapterName?: string): Promise<number> {
|
|
675
701
|
// Win32_VideoController clamps AdapterRAM to 4GB (UInt32).
|
|
676
|
-
// Read exact 64-bit VRAM from
|
|
702
|
+
// Read exact 64-bit VRAM from the Display Class registry keys. The value is literally named
|
|
703
|
+
// 'HardwareInformation.qwMemorySize' (dot is part of the name), so it must be quoted.
|
|
704
|
+
// Match by DriverDesc so hybrid systems report the VRAM of the selected adapter.
|
|
677
705
|
try {
|
|
706
|
+
const safeName = (adapterName || '').replace(/'/g, "''");
|
|
678
707
|
const raw = await runPowerShell(
|
|
679
|
-
|
|
708
|
+
`$name = '${safeName}'; $items = Get-ItemProperty -Path 'HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Class\\{4d36e968-e325-11ce-bfc1-08002be10318}\\0*' -ErrorAction SilentlyContinue | Where-Object { $_.'HardwareInformation.qwMemorySize' -gt 0 }; $pick = $items | Where-Object { $name -and $_.DriverDesc -eq $name } | Select-Object -First 1; if (-not $pick) { $pick = $items | Sort-Object -Property 'HardwareInformation.qwMemorySize' -Descending | Select-Object -First 1 }; if ($pick) { [string][uint64]$pick.'HardwareInformation.qwMemorySize' }`,
|
|
680
709
|
''
|
|
681
710
|
);
|
|
682
|
-
const bytes =
|
|
683
|
-
if (bytes > 0) return bytes;
|
|
684
|
-
} catch {}
|
|
685
|
-
|
|
686
|
-
// Fallback to Performance Counters Dedicated Usage + Free
|
|
687
|
-
try {
|
|
688
|
-
const rawDedicated = await runPowerShell(
|
|
689
|
-
`(Get-Counter '\\GPU Adapter Memory(*)\\Dedicated Usage' -ErrorAction SilentlyContinue).CounterSamples | Measure-Object -Property CookedValue -Sum | Select-Object -ExpandProperty Sum`,
|
|
690
|
-
''
|
|
691
|
-
);
|
|
692
|
-
const bytes = safeParseFloat(rawDedicated);
|
|
711
|
+
const bytes = safeParseFloat(raw.trim());
|
|
693
712
|
if (bytes > 0) return bytes;
|
|
694
713
|
} catch {}
|
|
695
714
|
|
|
696
715
|
return 0;
|
|
697
716
|
}
|
|
698
717
|
|
|
718
|
+
/**
|
|
719
|
+
* 3D engine utilization via CIM performance classes. Unlike Get-Counter paths these class/property
|
|
720
|
+
* names are not localized, so this works on non-English Windows. Works for NVIDIA, AMD and Intel (WDDM 2.0+).
|
|
721
|
+
*/
|
|
699
722
|
export async function getWindowsGpu3dUtilization(): Promise<{ utilization: number; processCount: number }> {
|
|
700
723
|
try {
|
|
701
724
|
const raw = await runPowerShell(
|
|
702
|
-
`$samples =
|
|
725
|
+
`$samples = Get-CimInstance Win32_PerfFormattedData_GPUPerformanceCounters_GPUEngine -ErrorAction SilentlyContinue | Where-Object { $_.Name -like '*engtype_3D*' }; if ($samples) { $activePids = $samples | Where-Object { $_.UtilizationPercentage -gt 0 } | ForEach-Object { if ($_.Name -match 'pid_(\\d+)') { $matches[1] } } | Select-Object -Unique; $total = ($samples | Measure-Object -Property UtilizationPercentage -Sum).Sum; "$total|$(@($activePids).Count)" } else { '0|0' }`,
|
|
703
726
|
'0|0'
|
|
704
727
|
);
|
|
705
728
|
const [utilStr, procCountStr] = raw.split('|');
|
|
@@ -729,7 +752,7 @@ export async function collectWindowsGpu(): Promise<GpuData | null> {
|
|
|
729
752
|
const bestController = prioritized[0];
|
|
730
753
|
|
|
731
754
|
// Read 64-bit VRAM (avoids 4GB uint32 clamp on RX 7900, Arc A770, etc.)
|
|
732
|
-
let vramBytes = await getWindowsGpu64BitVram();
|
|
755
|
+
let vramBytes = await getWindowsGpu64BitVram(bestController.name);
|
|
733
756
|
if (vramBytes === 0 && bestController.adapterRam > 0) {
|
|
734
757
|
vramBytes = bestController.adapterRam;
|
|
735
758
|
}
|
package/src/platform.test.ts
CHANGED
|
@@ -119,6 +119,17 @@ describe('Intel, AMD & NVIDIA Hardware Telemetry', () => {
|
|
|
119
119
|
assert.strictEqual(prioritized[0].name, 'Intel(R) Arc(TM) A770 Graphics');
|
|
120
120
|
});
|
|
121
121
|
|
|
122
|
+
it('should rank hybrid laptop adapters: discrete > integrated > virtual', async () => {
|
|
123
|
+
const { prioritizeGpuControllers } = await import('./monitors/platform/windows.js');
|
|
124
|
+
const mk = (name: string, adapterRam = 0) => ({ name, adapterRam, driverVersion: '', videoProcessor: '', pnpDeviceId: '' });
|
|
125
|
+
const order = (names: string[]) => prioritizeGpuControllers(names.map(n => mk(n))).map(c => c.name);
|
|
126
|
+
|
|
127
|
+
assert.strictEqual(order(['Microsoft Basic Display Adapter', 'Intel(R) Iris(R) Xe Graphics', 'NVIDIA GeForce RTX 4060 Laptop GPU'])[0], 'NVIDIA GeForce RTX 4060 Laptop GPU');
|
|
128
|
+
assert.strictEqual(order(['Intel(R) UHD Graphics', 'Microsoft Remote Display Adapter'])[0], 'Intel(R) UHD Graphics');
|
|
129
|
+
assert.strictEqual(order(['AMD Radeon(TM) Graphics', 'AMD Radeon Pro W7800'])[0], 'AMD Radeon Pro W7800');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
|
|
122
133
|
it('should detect CPU vendors and estimate architecture TDP accurately', async () => {
|
|
123
134
|
const { detectProcessorVendor, estimateCpuTdp } = await import('./monitors/platform/windows.js');
|
|
124
135
|
// AMD tests
|