diodejs 0.4.0 → 0.4.2

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.
@@ -0,0 +1,476 @@
1
+ process.env.LOG = 'false';
2
+ process.env.DEBUG = 'false';
3
+
4
+ const fs = require('fs');
5
+ const os = require('os');
6
+ const path = require('path');
7
+ const { execSync } = require('child_process');
8
+ const { performance } = require('perf_hooks');
9
+ const Module = require('module');
10
+
11
+ const repo = path.resolve(__dirname, '..');
12
+ const CurrentManager = require(path.join(repo, 'clientManager.js'));
13
+
14
+ const DEVICE_CANDIDATES = [
15
+ '0x4632c04cf8c44a586554951e7de03ca2bd3e8f1c',
16
+ '0xca1e71d8105a598810578fb6042fa8cbc1e7f039',
17
+ '0x5365baf29cb7ab58de588dfc448913cb609283e2',
18
+ ];
19
+
20
+ function parseArgs(argv) {
21
+ const args = {
22
+ providerFile: null,
23
+ networkDiscoveryLive: false,
24
+ networkDiscoverySnapshot: null,
25
+ };
26
+ for (let index = 0; index < argv.length; index += 1) {
27
+ const token = argv[index];
28
+ if (token === '--provider-file' && argv[index + 1]) {
29
+ args.providerFile = path.resolve(repo, argv[index + 1]);
30
+ index += 1;
31
+ } else if (token === '--network-discovery-live') {
32
+ args.networkDiscoveryLive = true;
33
+ } else if (token === '--network-discovery-snapshot' && argv[index + 1]) {
34
+ args.networkDiscoverySnapshot = path.resolve(repo, argv[index + 1]);
35
+ index += 1;
36
+ }
37
+ }
38
+ return args;
39
+ }
40
+
41
+ function loadOldManager() {
42
+ const source = execSync('git show HEAD:clientManager.js', { cwd: repo, encoding: 'utf8' });
43
+ const replacements = {
44
+ "require('./connection')": `require(${JSON.stringify(path.join(repo, 'connection.js'))})`,
45
+ "require('./rpc')": `require(${JSON.stringify(path.join(repo, 'rpc.js'))})`,
46
+ "require('./logger')": `require(${JSON.stringify(path.join(repo, 'logger.js'))})`,
47
+ };
48
+
49
+ let patched = source;
50
+ for (const [from, to] of Object.entries(replacements)) {
51
+ patched = patched.replace(from, to);
52
+ }
53
+
54
+ const moduleInstance = new Module(path.join(repo, '.old-clientManager.inline.js'), module);
55
+ moduleInstance.filename = path.join(repo, '.old-clientManager.inline.js');
56
+ moduleInstance.paths = Module._nodeModulePaths(repo);
57
+ moduleInstance._compile(patched, moduleInstance.filename);
58
+ return moduleInstance.exports;
59
+ }
60
+
61
+ function keyForConnection(connection) {
62
+ if (!connection) return null;
63
+ if (connection._managerHostKey) return connection._managerHostKey;
64
+ return `${connection.host}:${connection.port}`;
65
+ }
66
+
67
+ function inferRelaySource(manager, hostKey) {
68
+ if (!hostKey) return null;
69
+ const score = manager.relayScores && manager.relayScores.get(hostKey);
70
+ if (score && score.discoveredFrom) {
71
+ return score.discoveredFrom;
72
+ }
73
+ if (Array.isArray(manager.initialHosts) && manager.initialHosts.includes(hostKey)) {
74
+ return manager._hasExplicitHost || manager._hasExplicitHosts ? 'configured' : 'seed';
75
+ }
76
+ return 'unknown';
77
+ }
78
+
79
+ async function timed(fn) {
80
+ const startedAt = performance.now();
81
+ try {
82
+ const value = await fn();
83
+ return { ok: true, ms: performance.now() - startedAt, value };
84
+ } catch (error) {
85
+ return {
86
+ ok: false,
87
+ ms: performance.now() - startedAt,
88
+ error: String(error && error.message ? error.message : error),
89
+ };
90
+ }
91
+ }
92
+
93
+ async function pingConnection(manager, connection, count = 3) {
94
+ const rpc = manager._getRpcFor ? manager._getRpcFor(connection) : (connection.RPC || null);
95
+ const samplesMs = [];
96
+ for (let index = 0; index < count; index += 1) {
97
+ const startedAt = performance.now();
98
+ const pong = await rpc.ping();
99
+ const elapsedMs = performance.now() - startedAt;
100
+ if (!pong) {
101
+ throw new Error(`ping failed for ${keyForConnection(connection)}`);
102
+ }
103
+ samplesMs.push(elapsedMs);
104
+ }
105
+
106
+ const total = samplesMs.reduce((sum, value) => sum + value, 0);
107
+ return {
108
+ samplesMs,
109
+ avgMs: total / samplesMs.length,
110
+ minMs: Math.min(...samplesMs),
111
+ maxMs: Math.max(...samplesMs),
112
+ };
113
+ }
114
+
115
+ function meanDefined(values) {
116
+ const filtered = values.filter((value) => Number.isFinite(value));
117
+ if (filtered.length === 0) {
118
+ return null;
119
+ }
120
+ return filtered.reduce((sum, value) => sum + value, 0) / filtered.length;
121
+ }
122
+
123
+ async function resolveDeviceSample(manager, deviceId) {
124
+ const result = await timed(() => manager.getConnectionForDevice(deviceId));
125
+ const reconciliationTrace = manager._lastDeviceResolutionTrace || null;
126
+ if (!result.ok || !result.value) {
127
+ return {
128
+ deviceId,
129
+ ok: false,
130
+ error: result.error || 'resolution failed',
131
+ resolutionMs: result.ms,
132
+ hostKey: null,
133
+ source: null,
134
+ reconciliationTrace,
135
+ };
136
+ }
137
+
138
+ const hostKey = keyForConnection(result.value);
139
+ return {
140
+ deviceId,
141
+ ok: true,
142
+ hostKey,
143
+ source: inferRelaySource(manager, hostKey),
144
+ resolutionMs: result.ms,
145
+ reconciliationTrace,
146
+ };
147
+ }
148
+
149
+ async function resolveAllDevices(manager) {
150
+ const results = [];
151
+ for (const deviceId of DEVICE_CANDIDATES) {
152
+ const sample = await resolveDeviceSample(manager, deviceId);
153
+ sample.category = classifyDeviceResolution(sample);
154
+ results.push(sample);
155
+ }
156
+ return results;
157
+ }
158
+
159
+ function classifyDeviceResolution(deviceResolution) {
160
+ if (!deviceResolution) {
161
+ return null;
162
+ }
163
+ if (deviceResolution.ok === false) {
164
+ return 'resolution failed';
165
+ }
166
+ const trace = deviceResolution && deviceResolution.reconciliationTrace;
167
+ if (!trace) {
168
+ return null;
169
+ }
170
+ if (trace.reconciliation && trace.reconciliation.choseAlternate) {
171
+ return 'alternate answer available and chosen';
172
+ }
173
+ if (trace.controlPlaneSlow) {
174
+ return 'control-plane lookup itself was slow';
175
+ }
176
+ if (
177
+ trace.reconciliation
178
+ && trace.reconciliation.triggered
179
+ && trace.reconciliation.attempted
180
+ && !trace.reconciliation.hadAlternateAnswer
181
+ ) {
182
+ return 'no alternate answer available';
183
+ }
184
+ if (trace.reconciliation && trace.reconciliation.triggered && trace.reconciliation.hadAlternateAnswer) {
185
+ return 'alternate answer available but not chosen';
186
+ }
187
+ return 'no reconciliation needed';
188
+ }
189
+
190
+ function flattenDeviceResolutions(trials, version) {
191
+ return trials.flatMap((trial) => (trial[version].deviceResolutions || []).map((resolution) => ({
192
+ phase: trial.phase,
193
+ ...resolution,
194
+ })));
195
+ }
196
+
197
+ function summarizeDeviceResults(deviceResults) {
198
+ const byDevice = new Map();
199
+ for (const result of deviceResults) {
200
+ if (!byDevice.has(result.deviceId)) {
201
+ byDevice.set(result.deviceId, []);
202
+ }
203
+ byDevice.get(result.deviceId).push(result);
204
+ }
205
+
206
+ return Array.from(byDevice.entries()).map(([deviceId, results]) => {
207
+ const successful = results.filter((result) => result.ok && Number.isFinite(result.resolutionMs));
208
+ const categoryCounts = results.reduce((acc, result) => {
209
+ const category = result.category || 'unclassified';
210
+ acc[category] = (acc[category] || 0) + 1;
211
+ return acc;
212
+ }, {});
213
+ const hosts = Array.from(new Set(results.map((result) => result.hostKey).filter(Boolean)));
214
+ return {
215
+ deviceId,
216
+ samples: results.length,
217
+ successCount: successful.length,
218
+ avgResolutionMs: round(meanDefined(successful.map((result) => result.resolutionMs)) || 0),
219
+ hosts,
220
+ categoryCounts,
221
+ };
222
+ });
223
+ }
224
+
225
+ async function runScenario(ManagerClass, keyLocation, options = {}) {
226
+ const manager = new ManagerClass({ keyLocation, ...options });
227
+ const connectResult = await timed(() => manager.connect());
228
+ if (!connectResult.ok) {
229
+ return {
230
+ ok: false,
231
+ connectMs: connectResult.ms,
232
+ error: connectResult.error,
233
+ };
234
+ }
235
+
236
+ const startupConnections = manager.getConnections().map(keyForConnection);
237
+ const selectionSequence = [];
238
+ for (let index = 0; index < Math.max(6, startupConnections.length); index += 1) {
239
+ selectionSequence.push(keyForConnection(manager.getNearestConnection()));
240
+ }
241
+
242
+ await new Promise((resolve) => setTimeout(resolve, 1500));
243
+ const warmedConnections = manager.getConnections().map(keyForConnection);
244
+
245
+ const pingStats = {};
246
+ for (const connection of manager.getConnections()) {
247
+ const hostKey = keyForConnection(connection);
248
+ try {
249
+ pingStats[hostKey] = await pingConnection(manager, connection, 3);
250
+ } catch (error) {
251
+ pingStats[hostKey] = { error: String(error && error.message ? error.message : error) };
252
+ }
253
+ }
254
+
255
+ const bestConnected = Object.entries(pingStats)
256
+ .filter((entry) => Number.isFinite(entry[1].avgMs))
257
+ .sort((left, right) => left[1].avgMs - right[1].avgMs)[0];
258
+ const selectedHost = selectionSequence[0] || null;
259
+ const selectedSource = inferRelaySource(manager, selectedHost);
260
+ const deviceResolutions = await resolveAllDevices(manager);
261
+
262
+ manager.close();
263
+
264
+ return {
265
+ ok: true,
266
+ connectMs: connectResult.ms,
267
+ startupConnections,
268
+ warmedConnections,
269
+ selectedHost,
270
+ selectedSource,
271
+ selectionSequence,
272
+ selectedPingAvgMs: selectedHost && pingStats[selectedHost] && Number.isFinite(pingStats[selectedHost].avgMs)
273
+ ? pingStats[selectedHost].avgMs
274
+ : null,
275
+ bestConnectedHost: bestConnected ? bestConnected[0] : null,
276
+ bestConnectedPingAvgMs: bestConnected ? bestConnected[1].avgMs : null,
277
+ gapToBestMs: bestConnected && selectedHost && Number.isFinite(pingStats[selectedHost]?.avgMs)
278
+ ? pingStats[selectedHost].avgMs - bestConnected[1].avgMs
279
+ : null,
280
+ pingStats,
281
+ deviceResolutions,
282
+ networkDiscoveryStats: manager._lastNetworkDiscoveryStats || null,
283
+ };
284
+ }
285
+
286
+ function round(value) {
287
+ if (!Number.isFinite(value)) {
288
+ return 'n/a';
289
+ }
290
+ return Math.round(value * 100) / 100;
291
+ }
292
+
293
+ function mean(values) {
294
+ return values.reduce((sum, value) => sum + value, 0) / values.length;
295
+ }
296
+
297
+ async function main() {
298
+ const args = parseArgs(process.argv.slice(2));
299
+ const OldManager = loadOldManager();
300
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'diode-relay-bench-'));
301
+ const oldKeyLocation = path.join(tempRoot, 'old', 'keys.json');
302
+ const currentKeyLocation = path.join(tempRoot, 'current', 'keys.json');
303
+ const phases = ['cold', 'warm-1', 'warm-2'];
304
+ const trials = [];
305
+ let providerEntries = null;
306
+ let networkDiscoverySnapshot = null;
307
+ if (args.providerFile) {
308
+ providerEntries = JSON.parse(fs.readFileSync(args.providerFile, 'utf8'));
309
+ if (!Array.isArray(providerEntries)) {
310
+ throw new Error('Provider file must contain a JSON array');
311
+ }
312
+ }
313
+ if (args.networkDiscoverySnapshot) {
314
+ networkDiscoverySnapshot = JSON.parse(fs.readFileSync(args.networkDiscoverySnapshot, 'utf8'));
315
+ if (!Array.isArray(networkDiscoverySnapshot)) {
316
+ throw new Error('Network discovery snapshot file must contain a JSON array');
317
+ }
318
+ }
319
+
320
+ class SnapshotDiscoveryManager extends CurrentManager {
321
+ async _fetchNetworkDiscoveryNodes() {
322
+ return networkDiscoverySnapshot || [];
323
+ }
324
+ }
325
+
326
+ const currentOptions = {
327
+ relaySelection: {
328
+ continueProbingUntestedSeeds: !(args.networkDiscoveryLive || networkDiscoverySnapshot),
329
+ networkDiscovery: {
330
+ enabled: !!(args.networkDiscoveryLive || networkDiscoverySnapshot),
331
+ backgroundBatchSize: args.networkDiscoveryLive || networkDiscoverySnapshot ? 0 : undefined,
332
+ },
333
+ },
334
+ };
335
+ if (providerEntries) {
336
+ currentOptions.relaySelection.discoveryProvider = async () => providerEntries;
337
+ }
338
+
339
+ const CurrentManagerClass = networkDiscoverySnapshot ? SnapshotDiscoveryManager : CurrentManager;
340
+
341
+ for (const phase of phases) {
342
+ trials.push({
343
+ phase,
344
+ old: await runScenario(OldManager, oldKeyLocation),
345
+ current: await runScenario(CurrentManagerClass, currentKeyLocation, currentOptions),
346
+ });
347
+ }
348
+
349
+ const summary = {
350
+ oldAvgConnectMs: round(mean(trials.map((trial) => trial.old.connectMs))),
351
+ currentAvgConnectMs: round(mean(trials.map((trial) => trial.current.connectMs))),
352
+ oldAvgSelectedPingMs: round(meanDefined(trials.map((trial) => trial.old.selectedPingAvgMs)) || 0),
353
+ currentAvgSelectedPingMs: round(meanDefined(trials.map((trial) => trial.current.selectedPingAvgMs)) || 0),
354
+ oldAvgBestConnectedPingMs: round(meanDefined(trials.map((trial) => trial.old.bestConnectedPingAvgMs)) || 0),
355
+ currentAvgBestConnectedPingMs: round(meanDefined(trials.map((trial) => trial.current.bestConnectedPingAvgMs)) || 0),
356
+ oldAvgGapToBestMs: round(meanDefined(trials.map((trial) => trial.old.gapToBestMs)) || 0),
357
+ currentAvgGapToBestMs: round(meanDefined(trials.map((trial) => trial.current.gapToBestMs)) || 0),
358
+ oldAvgDeviceResolutionMs: round(meanDefined(flattenDeviceResolutions(trials, 'old').map((result) => (
359
+ result.ok ? result.resolutionMs : null
360
+ ))) || 0),
361
+ currentAvgDeviceResolutionMs: round(meanDefined(flattenDeviceResolutions(trials, 'current').map((result) => (
362
+ result.ok ? result.resolutionMs : null
363
+ ))) || 0),
364
+ currentDeviceResolutionCategories: flattenDeviceResolutions(trials, 'current').reduce((acc, result) => {
365
+ const category = result.category || 'unclassified';
366
+ acc[category] = (acc[category] || 0) + 1;
367
+ return acc;
368
+ }, {}),
369
+ oldPerDevice: summarizeDeviceResults(flattenDeviceResolutions(trials, 'old')),
370
+ currentPerDevice: summarizeDeviceResults(flattenDeviceResolutions(trials, 'current')),
371
+ };
372
+
373
+ const generatedAt = new Date().toISOString().replace(/[:.]/g, '-');
374
+ const reportBase = `relay-selection-benchmark-${generatedAt}`;
375
+ const payload = {
376
+ generatedAt: new Date().toISOString(),
377
+ baselineCommit: execSync('git rev-parse HEAD', { cwd: repo, encoding: 'utf8' }).trim(),
378
+ tempRoot,
379
+ providerFile: args.providerFile,
380
+ providerCandidatesLoaded: providerEntries ? providerEntries.length : 0,
381
+ networkDiscoveryMode: args.networkDiscoveryLive ? 'live' : (networkDiscoverySnapshot ? 'snapshot' : 'disabled'),
382
+ networkDiscoverySnapshot: args.networkDiscoverySnapshot,
383
+ summary,
384
+ trials,
385
+ };
386
+
387
+ fs.mkdirSync(path.join(repo, 'reports'), { recursive: true });
388
+ const jsonPath = path.join(repo, 'reports', `${reportBase}.json`);
389
+ fs.writeFileSync(jsonPath, JSON.stringify(payload, null, 2), 'utf8');
390
+
391
+ const lines = [];
392
+ lines.push('# Relay Selection Benchmark Report');
393
+ lines.push('');
394
+ lines.push(`- Generated: ${payload.generatedAt}`);
395
+ lines.push(`- Baseline old manager source: \`HEAD\` commit \`${payload.baselineCommit}\``);
396
+ lines.push('- Candidate manager source: current working tree `clientManager.js`');
397
+ lines.push('- Relay pool: default pre-net seeds');
398
+ lines.push(`- Device lookup samples: ${DEVICE_CANDIDATES.join(', ')}`);
399
+ lines.push(`- Network discovery mode: ${payload.networkDiscoveryMode}`);
400
+ if (payload.networkDiscoveryMode !== 'disabled') {
401
+ lines.push('- Benchmark profile: startup discovery only; background discovery probing disabled for deterministic timing');
402
+ }
403
+ if (payload.providerFile) {
404
+ lines.push(`- Provider file: \`${payload.providerFile}\``);
405
+ lines.push(`- Provider candidates loaded: ${payload.providerCandidatesLoaded}`);
406
+ }
407
+ if (payload.networkDiscoverySnapshot) {
408
+ lines.push(`- Network discovery snapshot: \`${payload.networkDiscoverySnapshot}\``);
409
+ }
410
+ lines.push('');
411
+ lines.push('## Summary');
412
+ lines.push('');
413
+ lines.push(`- Average connect time: old ${summary.oldAvgConnectMs} ms, current ${summary.currentAvgConnectMs} ms`);
414
+ lines.push(`- Average selected relay RTT: old ${summary.oldAvgSelectedPingMs} ms, current ${summary.currentAvgSelectedPingMs} ms`);
415
+ lines.push(`- Average best connected RTT: old ${summary.oldAvgBestConnectedPingMs} ms, current ${summary.currentAvgBestConnectedPingMs} ms`);
416
+ lines.push(`- Average gap to best: old ${summary.oldAvgGapToBestMs} ms, current ${summary.currentAvgGapToBestMs} ms`);
417
+ lines.push(`- Average device resolution: old ${summary.oldAvgDeviceResolutionMs} ms, current ${summary.currentAvgDeviceResolutionMs} ms`);
418
+ if (payload.networkDiscoveryMode !== 'disabled') {
419
+ const categories = Object.entries(summary.currentDeviceResolutionCategories)
420
+ .map(([label, count]) => `${label}: ${count}`)
421
+ .join('; ');
422
+ lines.push(`- Current device-resolution categories: ${categories || 'n/a'}`);
423
+ }
424
+ lines.push('');
425
+ lines.push('## Per-Phase Relay Summary');
426
+ lines.push('');
427
+ lines.push('| Phase | Version | Connect ms | Startup relays | Selected relay | Source | Selected RTT ms | Best relay | Best RTT ms | Gap ms | Discovery usable | Discovery startup |');
428
+ lines.push('| --- | --- | ---: | ---: | --- | --- | ---: | --- | ---: | ---: | ---: | ---: |');
429
+ for (const trial of trials) {
430
+ for (const version of ['old', 'current']) {
431
+ const row = trial[version];
432
+ const discoveryUsable = row.networkDiscoveryStats ? row.networkDiscoveryStats.usableCount : 'n/a';
433
+ const discoveryStartup = row.networkDiscoveryStats ? row.networkDiscoveryStats.startupProbeCount : 'n/a';
434
+ lines.push(`| ${trial.phase} | ${version} | ${round(row.connectMs)} | ${row.startupConnections.length} | ${row.selectedHost} | ${row.selectedSource} | ${round(row.selectedPingAvgMs)} | ${row.bestConnectedHost} | ${round(row.bestConnectedPingAvgMs)} | ${round(row.gapToBestMs)} | ${discoveryUsable} | ${discoveryStartup} |`);
435
+ }
436
+ }
437
+
438
+ lines.push('');
439
+ lines.push('## Per-Device Summary');
440
+ lines.push('');
441
+ lines.push('| Device | Version | Samples | Successes | Avg resolution ms | Hosts seen | Categories |');
442
+ lines.push('| --- | --- | ---: | ---: | ---: | --- | --- |');
443
+ for (const version of ['oldPerDevice', 'currentPerDevice']) {
444
+ const label = version === 'oldPerDevice' ? 'old' : 'current';
445
+ for (const row of summary[version]) {
446
+ const hostsSeen = row.hosts.length > 0 ? row.hosts.join(', ') : 'n/a';
447
+ const categories = Object.entries(row.categoryCounts)
448
+ .map(([category, count]) => `${category}: ${count}`)
449
+ .join('; ');
450
+ lines.push(`| ${row.deviceId} | ${label} | ${row.samples} | ${row.successCount} | ${row.avgResolutionMs} | ${hostsSeen} | ${categories || 'n/a'} |`);
451
+ }
452
+ }
453
+
454
+ lines.push('');
455
+ lines.push('## Per-Device Samples');
456
+ lines.push('');
457
+ lines.push('| Phase | Version | Device | Relay | Source | Resolution ms | Category |');
458
+ lines.push('| --- | --- | --- | --- | --- | ---: | --- |');
459
+ for (const trial of trials) {
460
+ for (const version of ['old', 'current']) {
461
+ for (const result of trial[version].deviceResolutions || []) {
462
+ lines.push(`| ${trial.phase} | ${version} | ${result.deviceId} | ${result.hostKey || 'n/a'} | ${result.source || 'n/a'} | ${round(result.resolutionMs)} | ${result.category || 'n/a'} |`);
463
+ }
464
+ }
465
+ }
466
+
467
+ const mdPath = path.join(repo, 'reports', `${reportBase}.md`);
468
+ fs.writeFileSync(mdPath, `${lines.join('\n')}\n`, 'utf8');
469
+
470
+ console.log(JSON.stringify({ jsonPath, mdPath, summary }, null, 2));
471
+ }
472
+
473
+ main().catch((error) => {
474
+ console.error(error);
475
+ process.exit(1);
476
+ });