network-performance-monitor
Version:
A comprehensive network performance monitoring tool that continuously tests and tracks your network's performance over time
187 lines • 7.72 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.SpeedTest = void 0;
const child_process_1 = require("child_process");
const util_1 = require("util");
const logger_1 = require("./logger");
const fs = __importStar(require("fs"));
const execAsync = (0, util_1.promisify)(child_process_1.exec);
// Determine speedtest-cli path based on OS
function getSpeedtestPath() {
const paths = [
'/opt/homebrew/bin/speedtest-cli', // macOS with Homebrew
'/usr/local/bin/speedtest-cli', // Common Unix path
'/usr/bin/speedtest-cli', // System path
'speedtest-cli' // Fallback to PATH
];
for (const path of paths) {
if (path === 'speedtest-cli' || fs.existsSync(path)) {
return path;
}
}
return 'speedtest-cli'; // Default fallback
}
const SPEEDTEST_PATH = getSpeedtestPath();
class SpeedTest {
db;
constructor(db) {
this.db = db;
}
async runSpeedTestWithSpawn() {
return new Promise((resolve, reject) => {
const child = (0, child_process_1.spawn)(SPEEDTEST_PATH, ['--json', '--no-pre-allocate'], {
env: {
...process.env,
PYTHONUNBUFFERED: '1',
NO_COLOR: '1',
TERM: 'dumb',
FORCE_COLOR: '0',
PYTHONWARNINGS: 'ignore::DeprecationWarning'
}
});
let stdout = '';
let stderr = '';
const timeout = setTimeout(() => {
child.kill();
reject(new Error('Speedtest timed out after 2 minutes'));
}, 120000);
child.stdout.on('data', (data) => {
stdout += data.toString();
});
child.stderr.on('data', (data) => {
stderr += data.toString();
});
child.on('close', (code) => {
clearTimeout(timeout);
if (code !== 0) {
logger_1.logger.debug('Speedtest-cli exited with code:', code);
logger_1.logger.debug('Stderr (ignored):', stderr);
}
// Always return stdout, even if exit code is non-zero
resolve(stdout);
});
child.on('error', (err) => {
clearTimeout(timeout);
reject(err);
});
});
}
async runTest(triggerReason, networkName) {
logger_1.logger.debug(`Running speedtest with path: ${SPEEDTEST_PATH}`);
const result = {
downloadSpeed: null,
uploadSpeed: null,
ping: null,
server: null,
error: null,
success: false,
triggerReason
};
try {
// Always use spawn method for consistency - it gives us better control over stdout/stderr
logger_1.logger.debug('Running speedtest with spawn method');
const stdout = await this.runSpeedTestWithSpawn();
// Log raw output for debugging
if (!stdout || stdout.trim().length === 0) {
logger_1.logger.error('Speedtest produced no stdout', {
triggerReason,
speedtestPath: SPEEDTEST_PATH,
stdoutLength: stdout ? stdout.length : 0
});
throw new Error('No output from speedtest-cli');
}
logger_1.logger.debug('Speedtest stdout received', {
triggerReason,
stdoutLength: stdout.length,
firstChars: stdout.substring(0, 100)
});
// Strip any ANSI escape codes and filter out any non-JSON content
const lines = stdout
.replace(/\u001b\[[0-9;]*m/g, '') // Remove ANSI codes
.split('\n')
.map(line => line.trim())
.filter(line => line.length > 0);
// Find the JSON line (should start with {)
const jsonLine = lines.find(line => line.startsWith('{') && line.endsWith('}'));
if (!jsonLine) {
logger_1.logger.debug('No JSON found in speedtest output:', {
lineCount: lines.length,
firstFewLines: lines.slice(0, 5),
lastFewLines: lines.slice(-5)
});
throw new Error('No valid JSON output from speedtest-cli');
}
try {
const data = JSON.parse(jsonLine);
// Convert from bits/s to Mbps
result.downloadSpeed = data.download / 1000000;
result.uploadSpeed = data.upload / 1000000;
result.ping = data.ping;
result.server = `${data.server.name} (${data.server.sponsor})`;
result.success = true;
(0, logger_1.logTestResult)('SPEEDTEST', 'speedtest-cli', true, result.ping || undefined, undefined);
}
catch (parseError) {
logger_1.logger.error('Failed to parse speedtest JSON:', {
error: parseError instanceof Error ? parseError.message : 'Unknown error',
jsonLine: jsonLine.substring(0, 200) // First 200 chars for debugging
});
throw new Error('Failed to parse speedtest-cli output');
}
}
catch (error) {
result.error = error instanceof Error ? error.message : 'Unknown error';
(0, logger_1.logTestResult)('SPEEDTEST', 'speedtest-cli', false, undefined, result.error);
}
// Store in database
await this.db.insertSpeedTest(result.downloadSpeed, result.uploadSpeed, result.ping, result.server, result.error, result.triggerReason, networkName);
return result;
}
formatResult(result) {
if (result.success) {
return `Speed Test Results (${result.triggerReason}):
Download: ${result.downloadSpeed?.toFixed(2)} Mbps
Upload: ${result.uploadSpeed?.toFixed(2)} Mbps
Ping: ${result.ping} ms
Server: ${result.server}`;
}
else {
return `Speed Test Failed (${result.triggerReason}): ${result.error}`;
}
}
}
exports.SpeedTest = SpeedTest;
//# sourceMappingURL=speedTest.js.map