idlebench 1.0.0 → 1.0.1
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/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +102 -97
- package/dist/commands/upload.d.ts.map +1 -1
- package/dist/commands/upload.js +12 -2
- package/dist/lib/docker.d.ts.map +1 -1
- package/dist/lib/docker.js +29 -29
- package/dist/lib/gpu.js +2 -2
- package/package.json +2 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"AA4IA,wBAAsB,mBAAmB,kBAgMxC"}
|
package/dist/commands/run.js
CHANGED
|
@@ -10,22 +10,22 @@ import { runBenchmark } from '../lib/benchmark.js';
|
|
|
10
10
|
import { calculateGpuScore, calculateRuntimeScore, calculateNetworkScore, calculateInferenceScore, calculateStabilityScore, calculateFinalScore, getGrade, } from '../lib/scoring.js';
|
|
11
11
|
import { buildReport, saveReportLocally } from '../lib/report.js';
|
|
12
12
|
const CLI_VERSION = '1.0.0';
|
|
13
|
-
const DEFAULT_API_URL = 'https://idlebench.
|
|
13
|
+
const DEFAULT_API_URL = 'https://idlebench.vercel.app';
|
|
14
14
|
function line() {
|
|
15
15
|
console.log(chalk.dim(' ─────────────────────────────────────────'));
|
|
16
16
|
}
|
|
17
17
|
function header() {
|
|
18
18
|
console.log();
|
|
19
|
-
console.log(chalk.bold.white(' IdleBench'));
|
|
19
|
+
console.log(chalk.bold.white(' IdleBench') + chalk.dim(` v${CLI_VERSION}`));
|
|
20
20
|
console.log(chalk.dim(' The verification layer for idle compute.'));
|
|
21
21
|
console.log();
|
|
22
|
-
console.log(chalk.bgYellow.black(' SECURITY
|
|
22
|
+
console.log(chalk.bgYellow.black(' SECURITY ') + chalk.yellow(' IdleBench never asks for private keys or seed phrases.'));
|
|
23
23
|
console.log();
|
|
24
24
|
line();
|
|
25
25
|
console.log();
|
|
26
26
|
}
|
|
27
27
|
function checkRow(label, value, ok) {
|
|
28
|
-
const icon = ok === true ? chalk.green('✓') : ok === false ? chalk.red('✗') : chalk.
|
|
28
|
+
const icon = ok === true ? chalk.green('✓') : ok === false ? chalk.red('✗') : chalk.dim('—');
|
|
29
29
|
console.log(` ${icon} ${chalk.dim(label.padEnd(26))} ${chalk.white(value)}`);
|
|
30
30
|
}
|
|
31
31
|
function scoreBar(label, score, width = 20) {
|
|
@@ -34,6 +34,31 @@ function scoreBar(label, score, width = 20) {
|
|
|
34
34
|
const color = score >= 85 ? chalk.green : score >= 70 ? chalk.yellow : chalk.red;
|
|
35
35
|
console.log(` ${chalk.dim(label.padEnd(28))} ${color(bar)} ${chalk.bold(score)}`);
|
|
36
36
|
}
|
|
37
|
+
async function safeJsonFetch(url, options) {
|
|
38
|
+
let rawText = '';
|
|
39
|
+
try {
|
|
40
|
+
const res = await fetch(url, options);
|
|
41
|
+
rawText = await res.text();
|
|
42
|
+
let json;
|
|
43
|
+
try {
|
|
44
|
+
json = JSON.parse(rawText);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
throw new Error(`Server returned non-JSON (HTTP ${res.status}). ` +
|
|
48
|
+
(rawText.length > 0 ? rawText.slice(0, 120) : 'Empty response.'));
|
|
49
|
+
}
|
|
50
|
+
if (!res.ok) {
|
|
51
|
+
const msg = typeof json.error === 'string' ? json.error : `HTTP ${res.status}`;
|
|
52
|
+
throw new Error(msg);
|
|
53
|
+
}
|
|
54
|
+
return json;
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
if (err instanceof Error)
|
|
58
|
+
throw err;
|
|
59
|
+
throw new Error(`Network error: ${String(err)}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
37
62
|
async function promptSetup() {
|
|
38
63
|
console.log(chalk.dim(' Answer a few questions to configure your benchmark.\n'));
|
|
39
64
|
const answers = await inquirer.prompt([
|
|
@@ -42,11 +67,11 @@ async function promptSetup() {
|
|
|
42
67
|
name: 'providerWallet',
|
|
43
68
|
message: 'Solana wallet address (public key):',
|
|
44
69
|
validate: (input) => {
|
|
45
|
-
|
|
70
|
+
const t = input.trim();
|
|
71
|
+
if (!t)
|
|
46
72
|
return 'Wallet address is required';
|
|
47
|
-
if (
|
|
48
|
-
return '
|
|
49
|
-
}
|
|
73
|
+
if (t.length < 32 || t.length > 44)
|
|
74
|
+
return 'Solana wallet address must be 32–44 characters';
|
|
50
75
|
return true;
|
|
51
76
|
},
|
|
52
77
|
},
|
|
@@ -88,28 +113,13 @@ async function promptSetup() {
|
|
|
88
113
|
}
|
|
89
114
|
},
|
|
90
115
|
},
|
|
91
|
-
{
|
|
92
|
-
type: 'input',
|
|
93
|
-
name: 'apiUrl',
|
|
94
|
-
message: `IdleBench API URL:`,
|
|
95
|
-
default: DEFAULT_API_URL,
|
|
96
|
-
validate: (input) => {
|
|
97
|
-
try {
|
|
98
|
-
new URL(input.trim());
|
|
99
|
-
return true;
|
|
100
|
-
}
|
|
101
|
-
catch {
|
|
102
|
-
return 'Enter a valid URL';
|
|
103
|
-
}
|
|
104
|
-
},
|
|
105
|
-
},
|
|
106
116
|
]);
|
|
107
117
|
return {
|
|
108
118
|
providerWallet: answers.providerWallet.trim(),
|
|
109
119
|
resourceName: answers.resourceName.trim(),
|
|
110
120
|
resourceType: answers.resourceType,
|
|
111
121
|
idleGatewayUrl: answers.idleGatewayUrl.trim() || null,
|
|
112
|
-
apiUrl:
|
|
122
|
+
apiUrl: process.env.IDLEBENCH_API_URL?.trim() ?? DEFAULT_API_URL,
|
|
113
123
|
};
|
|
114
124
|
}
|
|
115
125
|
export async function runBenchmarkCommand() {
|
|
@@ -135,7 +145,7 @@ export async function runBenchmarkCommand() {
|
|
|
135
145
|
}
|
|
136
146
|
catch (err) {
|
|
137
147
|
spinner.fail('Failed to read system info');
|
|
138
|
-
throw err;
|
|
148
|
+
throw new Error(`System check failed: ${err instanceof Error ? err.message : err}`);
|
|
139
149
|
}
|
|
140
150
|
}
|
|
141
151
|
console.log();
|
|
@@ -143,19 +153,25 @@ export async function runBenchmarkCommand() {
|
|
|
143
153
|
let gpu;
|
|
144
154
|
{
|
|
145
155
|
const spinner = ora({ text: 'Detecting GPU', color: 'green' }).start();
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
156
|
+
try {
|
|
157
|
+
gpu = await getGpuInfo();
|
|
158
|
+
spinner.succeed(chalk.dim('GPU detection'));
|
|
159
|
+
if (gpu.name) {
|
|
160
|
+
checkRow('GPU Model', gpu.name, true);
|
|
161
|
+
if (gpu.vramGb)
|
|
162
|
+
checkRow('VRAM', `${gpu.vramGb} GB`);
|
|
163
|
+
checkRow('CUDA', gpu.cudaAvailable ? `${gpu.cudaVersion ?? 'Available'}` : 'Not available', gpu.cudaAvailable);
|
|
164
|
+
if (gpu.driverVersion)
|
|
165
|
+
checkRow('NVIDIA Driver', gpu.driverVersion, true);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
checkRow('GPU', 'Not detected', null);
|
|
169
|
+
console.log(chalk.dim(' → No GPU found. Running CPU-only checks.'));
|
|
170
|
+
}
|
|
155
171
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
172
|
+
catch (err) {
|
|
173
|
+
spinner.warn('GPU detection failed — continuing');
|
|
174
|
+
gpu = { name: null, vendor: null, vramGb: null, cudaAvailable: false, cudaVersion: null, driverVersion: null, temperature: null, utilizationPercent: null, detectionMethod: 'failed' };
|
|
159
175
|
}
|
|
160
176
|
}
|
|
161
177
|
console.log();
|
|
@@ -163,72 +179,62 @@ export async function runBenchmarkCommand() {
|
|
|
163
179
|
let docker;
|
|
164
180
|
{
|
|
165
181
|
const spinner = ora({ text: 'Checking Docker', color: 'green' }).start();
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
if (docker.
|
|
171
|
-
checkRow('Docker GPU Runtime', 'Available',
|
|
172
|
-
}
|
|
173
|
-
else {
|
|
174
|
-
checkRow('Docker GPU Runtime', docker.gpuRuntimeError ?? 'Not available', false);
|
|
182
|
+
try {
|
|
183
|
+
docker = await getDockerInfo();
|
|
184
|
+
spinner.succeed(chalk.dim('Docker check'));
|
|
185
|
+
checkRow('Docker', docker.available ? `v${docker.version}` : 'Not installed', docker.available);
|
|
186
|
+
if (docker.available) {
|
|
187
|
+
checkRow('Docker GPU Runtime', docker.gpuRuntime ? 'Available' : (docker.gpuRuntimeError ?? 'Not available'), docker.gpuRuntime);
|
|
175
188
|
}
|
|
176
189
|
}
|
|
190
|
+
catch (err) {
|
|
191
|
+
spinner.warn('Docker check failed — continuing');
|
|
192
|
+
docker = { available: false, version: null, gpuRuntime: false, gpuRuntimeError: 'Check failed' };
|
|
193
|
+
}
|
|
177
194
|
}
|
|
178
195
|
console.log();
|
|
179
196
|
// Benchmark
|
|
180
197
|
let benchmarkResult;
|
|
181
198
|
{
|
|
182
199
|
const spinner = ora({ text: 'Running benchmark', color: 'green' }).start();
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
200
|
+
try {
|
|
201
|
+
benchmarkResult = await runBenchmark();
|
|
202
|
+
spinner.succeed(chalk.dim('Benchmark complete'));
|
|
203
|
+
checkRow('CPU hash workload', `${benchmarkResult.cpuHashMs}ms`);
|
|
204
|
+
checkRow('Matrix calculation', `${benchmarkResult.matrixMs}ms`);
|
|
205
|
+
}
|
|
206
|
+
catch (err) {
|
|
207
|
+
spinner.warn('Benchmark failed — using defaults');
|
|
208
|
+
benchmarkResult = { cpuHashMs: 500, matrixMs: 500, totalMs: 1000 };
|
|
209
|
+
}
|
|
187
210
|
}
|
|
188
211
|
console.log();
|
|
189
212
|
// Network
|
|
190
213
|
let network;
|
|
191
214
|
{
|
|
192
215
|
const spinner = ora({ text: 'Testing network', color: 'green' }).start();
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
216
|
+
try {
|
|
217
|
+
network = await getNetworkInfo(apiUrl);
|
|
218
|
+
spinner.succeed(chalk.dim('Network check'));
|
|
219
|
+
checkRow('Latency', network.latencyMs !== null ? `${network.latencyMs}ms` : 'Could not measure', network.latencyMs !== null && network.latencyMs < 500);
|
|
220
|
+
if (network.downloadMbps !== null)
|
|
221
|
+
checkRow('Download estimate', `${network.downloadMbps} Mbps`);
|
|
222
|
+
}
|
|
223
|
+
catch (err) {
|
|
224
|
+
spinner.warn('Network check failed — continuing');
|
|
225
|
+
network = { latencyMs: null, downloadMbps: null, uploadMbps: null };
|
|
198
226
|
}
|
|
199
227
|
}
|
|
200
228
|
console.log();
|
|
201
229
|
line();
|
|
202
230
|
console.log();
|
|
203
231
|
// Calculate scores
|
|
204
|
-
const gpuScore = calculateGpuScore({
|
|
205
|
-
|
|
206
|
-
vramGb: gpu.vramGb,
|
|
207
|
-
cpuCores: system.cpuCores,
|
|
208
|
-
ramGb: system.ramGb,
|
|
209
|
-
cudaAvailable: gpu.cudaAvailable,
|
|
210
|
-
dockerAvailable: docker.available,
|
|
211
|
-
dockerGpuRuntime: docker.gpuRuntime,
|
|
212
|
-
});
|
|
213
|
-
const runtimeScore = calculateRuntimeScore({
|
|
214
|
-
gpuName: gpu.name,
|
|
215
|
-
vramGb: gpu.vramGb,
|
|
216
|
-
cpuCores: system.cpuCores,
|
|
217
|
-
ramGb: system.ramGb,
|
|
218
|
-
cudaAvailable: gpu.cudaAvailable,
|
|
219
|
-
dockerAvailable: docker.available,
|
|
220
|
-
dockerGpuRuntime: docker.gpuRuntime,
|
|
221
|
-
});
|
|
232
|
+
const gpuScore = calculateGpuScore({ gpuName: gpu.name, vramGb: gpu.vramGb, cpuCores: system.cpuCores, ramGb: system.ramGb, cudaAvailable: gpu.cudaAvailable, dockerAvailable: docker.available, dockerGpuRuntime: docker.gpuRuntime });
|
|
233
|
+
const runtimeScore = calculateRuntimeScore({ gpuName: gpu.name, vramGb: gpu.vramGb, cpuCores: system.cpuCores, ramGb: system.ramGb, cudaAvailable: gpu.cudaAvailable, dockerAvailable: docker.available, dockerGpuRuntime: docker.gpuRuntime });
|
|
222
234
|
const networkScore = calculateNetworkScore(network.latencyMs);
|
|
223
235
|
const inferenceScore = calculateInferenceScore(system.cpuCores, system.ramGb, benchmarkResult.totalMs, gpu.name);
|
|
224
236
|
const stabilityScore = calculateStabilityScore(system.cpuCores, system.ramGb);
|
|
225
|
-
const finalScore = calculateFinalScore({
|
|
226
|
-
gpuScore,
|
|
227
|
-
runtimeScore,
|
|
228
|
-
networkScore,
|
|
229
|
-
inferenceScore,
|
|
230
|
-
stabilityScore,
|
|
231
|
-
});
|
|
237
|
+
const finalScore = calculateFinalScore({ gpuScore, runtimeScore, networkScore, inferenceScore, stabilityScore });
|
|
232
238
|
const grade = getGrade(finalScore);
|
|
233
239
|
console.log(chalk.bold(' Score breakdown'));
|
|
234
240
|
console.log();
|
|
@@ -243,7 +249,7 @@ export async function runBenchmarkCommand() {
|
|
|
243
249
|
console.log();
|
|
244
250
|
line();
|
|
245
251
|
console.log();
|
|
246
|
-
// Build report
|
|
252
|
+
// Build + save report
|
|
247
253
|
const report = buildReport({
|
|
248
254
|
reportVersion: '1.0.0',
|
|
249
255
|
providerWallet,
|
|
@@ -258,27 +264,25 @@ export async function runBenchmarkCommand() {
|
|
|
258
264
|
benchmarkMs: benchmarkResult.totalMs,
|
|
259
265
|
cliVersion: CLI_VERSION,
|
|
260
266
|
});
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
267
|
+
let localPath;
|
|
268
|
+
try {
|
|
269
|
+
localPath = saveReportLocally(report);
|
|
270
|
+
console.log(chalk.dim(` Report saved locally: ${localPath}`));
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
localPath = 'unknown';
|
|
274
|
+
console.log(chalk.dim(' Could not save report locally.'));
|
|
275
|
+
}
|
|
264
276
|
console.log();
|
|
265
277
|
// Upload
|
|
266
278
|
const uploadSpinner = ora({ text: `Uploading to ${apiUrl}`, color: 'green' }).start();
|
|
267
279
|
try {
|
|
268
|
-
const
|
|
280
|
+
const json = await safeJsonFetch(`${apiUrl}/api/reports/upload`, {
|
|
269
281
|
method: 'POST',
|
|
270
282
|
headers: { 'Content-Type': 'application/json' },
|
|
271
283
|
body: JSON.stringify(report),
|
|
272
284
|
signal: AbortSignal.timeout(30000),
|
|
273
285
|
});
|
|
274
|
-
const json = await res.json();
|
|
275
|
-
if (!res.ok) {
|
|
276
|
-
uploadSpinner.fail('Upload failed');
|
|
277
|
-
console.error(chalk.red(` ✗ Server error: ${JSON.stringify(json)}`));
|
|
278
|
-
console.log(chalk.dim(` Your report was saved locally: ${localPath}`));
|
|
279
|
-
console.log(chalk.dim(` You can upload it later with: idlebench upload ${localPath}`));
|
|
280
|
-
return;
|
|
281
|
-
}
|
|
282
286
|
uploadSpinner.succeed('Report uploaded');
|
|
283
287
|
console.log();
|
|
284
288
|
console.log(chalk.bold(' Benchmark complete.'));
|
|
@@ -288,15 +292,16 @@ export async function runBenchmarkCommand() {
|
|
|
288
292
|
console.log(chalk.dim(' Public URL: '), chalk.cyan(String(json.publicUrl)));
|
|
289
293
|
console.log(chalk.dim(' Local report: '), chalk.dim(localPath));
|
|
290
294
|
console.log();
|
|
291
|
-
console.log(chalk.dim(
|
|
292
|
-
console.log(chalk.dim(` Badge URL: ${apiUrl}/api/badge/${json.resourceId}`));
|
|
295
|
+
console.log(chalk.dim(` Badge: ${apiUrl}/api/badge/${json.resourceId}`));
|
|
293
296
|
console.log();
|
|
294
297
|
}
|
|
295
298
|
catch (err) {
|
|
296
|
-
uploadSpinner.fail('Upload failed
|
|
299
|
+
uploadSpinner.fail('Upload failed');
|
|
300
|
+
console.error();
|
|
297
301
|
console.error(chalk.red(` ✗ ${err instanceof Error ? err.message : err}`));
|
|
298
|
-
console.
|
|
302
|
+
console.error();
|
|
299
303
|
console.log(chalk.dim(` Your report was saved locally: ${localPath}`));
|
|
300
304
|
console.log(chalk.dim(` Upload later with: idlebench upload ${localPath}`));
|
|
305
|
+
console.log();
|
|
301
306
|
}
|
|
302
307
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../../src/commands/upload.ts"],"names":[],"mappings":"AAqCA,wBAAsB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../../src/commands/upload.ts"],"names":[],"mappings":"AAqCA,wBAAsB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,iBAoE7E"}
|
package/dist/commands/upload.js
CHANGED
|
@@ -65,10 +65,20 @@ export async function runUpload(filePath, options) {
|
|
|
65
65
|
body: rawJson,
|
|
66
66
|
signal: AbortSignal.timeout(30000),
|
|
67
67
|
});
|
|
68
|
-
const
|
|
68
|
+
const rawText = await res.text();
|
|
69
|
+
let json;
|
|
70
|
+
try {
|
|
71
|
+
json = JSON.parse(rawText);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
spinner.fail('Upload failed');
|
|
75
|
+
console.error(chalk.red(` ✗ Server returned non-JSON (HTTP ${res.status}):`));
|
|
76
|
+
console.error(chalk.dim(` ${rawText.slice(0, 200)}`));
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
69
79
|
if (!res.ok) {
|
|
70
80
|
spinner.fail('Upload failed');
|
|
71
|
-
console.error(chalk.red(` ✗ ${JSON.stringify(json)}`));
|
|
81
|
+
console.error(chalk.red(` ✗ ${typeof json.error === 'string' ? json.error : JSON.stringify(json)}`));
|
|
72
82
|
process.exit(1);
|
|
73
83
|
}
|
|
74
84
|
spinner.succeed('Report uploaded');
|
package/dist/lib/docker.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"docker.d.ts","sourceRoot":"","sources":["../../src/lib/docker.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,OAAO,CAAA;IAClB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,UAAU,EAAE,OAAO,CAAA;IACnB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;CAC/B;
|
|
1
|
+
{"version":3,"file":"docker.d.ts","sourceRoot":"","sources":["../../src/lib/docker.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,OAAO,CAAA;IAClB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,UAAU,EAAE,OAAO,CAAA;IACnB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;CAC/B;AAeD,wBAAsB,aAAa,IAAI,OAAO,CAAC,UAAU,CAAC,CAuCzD"}
|
package/dist/lib/docker.js
CHANGED
|
@@ -1,48 +1,48 @@
|
|
|
1
1
|
import { execa } from 'execa';
|
|
2
|
-
|
|
3
|
-
// Check if docker is installed
|
|
4
|
-
let version = null;
|
|
2
|
+
async function runCmd(cmd, args, timeoutMs = 8000) {
|
|
5
3
|
try {
|
|
6
|
-
const { stdout } = await execa(
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
const { stdout } = await execa(cmd, args, {
|
|
5
|
+
timeout: timeoutMs,
|
|
6
|
+
shell: true, // use system shell — resolves PATH on Windows
|
|
7
|
+
reject: true,
|
|
8
|
+
});
|
|
9
|
+
return stdout.trim();
|
|
9
10
|
}
|
|
10
11
|
catch {
|
|
11
|
-
return
|
|
12
|
+
return null;
|
|
12
13
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
}
|
|
15
|
+
export async function getDockerInfo() {
|
|
16
|
+
// Check version
|
|
17
|
+
const versionOut = await runCmd('docker', ['--version']);
|
|
18
|
+
if (!versionOut) {
|
|
19
|
+
return { available: false, version: null, gpuRuntime: false, gpuRuntimeError: 'Docker not found in PATH' };
|
|
16
20
|
}
|
|
17
|
-
|
|
21
|
+
const match = versionOut.match(/Docker version ([\d.]+)/);
|
|
22
|
+
const version = match ? match[1] : versionOut.split('\n')[0];
|
|
23
|
+
// Check daemon is running
|
|
24
|
+
const infoOut = await runCmd('docker', ['info', '--format', '{{.ServerVersion}}'], 10000);
|
|
25
|
+
if (!infoOut) {
|
|
18
26
|
return { available: false, version, gpuRuntime: false, gpuRuntimeError: 'Docker daemon not running' };
|
|
19
27
|
}
|
|
20
|
-
//
|
|
28
|
+
// Test GPU passthrough
|
|
21
29
|
let gpuRuntime = false;
|
|
22
30
|
let gpuRuntimeError = null;
|
|
23
31
|
try {
|
|
24
|
-
await execa('docker', [
|
|
25
|
-
|
|
26
|
-
'--gpus', 'all',
|
|
27
|
-
'nvidia/cuda:12.3.0-base-ubuntu22.04',
|
|
28
|
-
'nvidia-smi', '-L',
|
|
29
|
-
], { timeout: 60000 });
|
|
30
|
-
gpuRuntime = true;
|
|
32
|
+
const { stdout } = await execa('docker', ['run', '--rm', '--gpus', 'all', 'nvidia/cuda:12.3.0-base-ubuntu22.04', 'nvidia-smi', '-L'], { timeout: 60000, shell: true });
|
|
33
|
+
gpuRuntime = stdout.length > 0;
|
|
31
34
|
}
|
|
32
35
|
catch (err) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (gpuRuntimeError.includes('could not select device driver')) {
|
|
36
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
37
|
+
if (msg.includes('could not select device driver') || msg.includes('nvidia')) {
|
|
36
38
|
gpuRuntimeError = 'NVIDIA container toolkit not installed';
|
|
37
39
|
}
|
|
38
|
-
else if (
|
|
40
|
+
else if (msg.includes('Unable to find image')) {
|
|
39
41
|
gpuRuntimeError = 'Could not pull test image';
|
|
40
42
|
}
|
|
43
|
+
else {
|
|
44
|
+
gpuRuntimeError = 'GPU runtime not available';
|
|
45
|
+
}
|
|
41
46
|
}
|
|
42
|
-
return {
|
|
43
|
-
available: true,
|
|
44
|
-
version,
|
|
45
|
-
gpuRuntime,
|
|
46
|
-
gpuRuntimeError,
|
|
47
|
-
};
|
|
47
|
+
return { available: true, version, gpuRuntime, gpuRuntimeError };
|
|
48
48
|
}
|
package/dist/lib/gpu.js
CHANGED
|
@@ -5,7 +5,7 @@ async function tryNvidiaSmi() {
|
|
|
5
5
|
const { stdout } = await execa('nvidia-smi', [
|
|
6
6
|
'--query-gpu=name,memory.total,driver_version,utilization.gpu,temperature.gpu',
|
|
7
7
|
'--format=csv,noheader,nounits',
|
|
8
|
-
], { timeout: 8000 });
|
|
8
|
+
], { timeout: 8000, shell: true });
|
|
9
9
|
const lines = stdout.trim().split('\n');
|
|
10
10
|
if (lines.length === 0)
|
|
11
11
|
return null;
|
|
@@ -15,7 +15,7 @@ async function tryNvidiaSmi() {
|
|
|
15
15
|
let cudaVersion = null;
|
|
16
16
|
let cudaAvailable = false;
|
|
17
17
|
try {
|
|
18
|
-
const { stdout: smiOut } = await execa('nvidia-smi', [], { timeout: 5000 });
|
|
18
|
+
const { stdout: smiOut } = await execa('nvidia-smi', [], { timeout: 5000, shell: true });
|
|
19
19
|
const cudaMatch = smiOut.match(/CUDA Version:\s*([\d.]+)/);
|
|
20
20
|
if (cudaMatch) {
|
|
21
21
|
cudaVersion = cudaMatch[1];
|
package/package.json
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "idlebench",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Benchmark and verify idle compute before it earns. The CLI for IDLE compute providers.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "IdleBench",
|
|
7
|
-
"
|
|
8
|
-
"type": "git",
|
|
9
|
-
"url": "https://github.com/idlebench/idlebench-cli"
|
|
10
|
-
},
|
|
7
|
+
"homepage": "https://idlebench.com",
|
|
11
8
|
"keywords": [
|
|
12
9
|
"benchmark",
|
|
13
10
|
"gpu",
|