UNPKG

network-performance-monitor

Version:

A comprehensive network performance monitoring tool that continuously tests and tracks your network's performance over time

332 lines 13.6 kB
"use strict"; 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.Database = void 0; const sqlite3 = __importStar(require("sqlite3")); const fs = __importStar(require("fs")); const path = __importStar(require("path")); const config_1 = require("./config"); const logger_1 = require("./logger"); class Database { db; MAX_RETRIES = 5; RETRY_DELAY = 100; // ms constructor(customDbPath) { if (customDbPath) { const dir = path.dirname(customDbPath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } this.db = new sqlite3.Database(customDbPath, sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE); } else { this.ensureDirectoryExists(); const dbPath = path.join(config_1.config.db.directory, config_1.config.db.filename); this.db = new sqlite3.Database(dbPath, sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE); } } ensureDirectoryExists() { if (!fs.existsSync(config_1.config.db.directory)) { fs.mkdirSync(config_1.config.db.directory, { recursive: true }); } } async initialize() { return new Promise((resolve, reject) => { // Enable WAL mode for better concurrency this.db.run('PRAGMA journal_mode=WAL', (err) => { if (err) { logger_1.logger.error('Failed to set WAL mode:', err); } }); this.db.serialize(() => { // DNS query results table this.db.run(` CREATE TABLE IF NOT EXISTS dns_queries ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, server TEXT NOT NULL, query TEXT NOT NULL, latency REAL, error TEXT, success BOOLEAN DEFAULT 1, network_name TEXT ) `); // Ping results table this.db.run(` CREATE TABLE IF NOT EXISTS ping_results ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, host TEXT NOT NULL, latency REAL, packet_loss REAL, error TEXT, success BOOLEAN DEFAULT 1, network_name TEXT ) `); // Website test results table this.db.run(` CREATE TABLE IF NOT EXISTS website_tests ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, url TEXT NOT NULL, latency REAL, status_code INTEGER, error TEXT, success BOOLEAN DEFAULT 1, network_name TEXT ) `); // Speed test results table this.db.run(` CREATE TABLE IF NOT EXISTS speed_tests ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, download_speed REAL, upload_speed REAL, ping REAL, server TEXT, error TEXT, trigger_reason TEXT, success BOOLEAN DEFAULT 1, network_name TEXT ) `); // Latency baselines table for anomaly detection this.db.run(` CREATE TABLE IF NOT EXISTS latency_baselines ( id INTEGER PRIMARY KEY AUTOINCREMENT, test_type TEXT NOT NULL, target TEXT NOT NULL, baseline_latency REAL, sample_count INTEGER DEFAULT 0, last_updated DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE(test_type, target) ) `, (err) => { if (err) { reject(err); } else { // After creating tables, add network_name columns if they don't exist this.addNetworkColumns().then(() => { // Then migrate existing data this.migrateExistingData().then(() => resolve()).catch(reject); }).catch(reject); } }); }); }); } async addNetworkColumns() { const tables = ['dns_queries', 'ping_results', 'website_tests', 'speed_tests']; for (const table of tables) { await new Promise((resolve, reject) => { // Check if column exists this.db.all(`PRAGMA table_info(${table})`, (err, rows) => { if (err) { reject(err); return; } const hasNetworkName = rows.some(row => row.name === 'network_name'); if (!hasNetworkName) { // Add column this.db.run(`ALTER TABLE ${table} ADD COLUMN network_name TEXT`, (err) => { if (err) reject(err); else resolve(); }); } else { resolve(); } }); }); } } async migrateExistingData() { const tables = ['dns_queries', 'ping_results', 'website_tests', 'speed_tests']; for (const table of tables) { await new Promise((resolve, reject) => { this.db.run(`UPDATE ${table} SET network_name = 'DragonsDen' WHERE network_name IS NULL`, (err) => { if (err) reject(err); else resolve(); }); }); } } async insertDnsQuery(server, query, latency, error, networkName) { return this.runWithRetry(() => new Promise((resolve, reject) => { this.db.run('INSERT INTO dns_queries (server, query, latency, error, success, network_name) VALUES (?, ?, ?, ?, ?, ?)', [server, query, latency, error, error ? 0 : 1, networkName], (err) => { if (err) { logger_1.logger.error('Failed to insert DNS query:', { error: err.message, code: err.code, errno: err.errno, server, networkName }); reject(err); } else resolve(); }); }), 'insertDnsQuery'); } async insertPingResult(host, latency, packetLoss, error, networkName) { return new Promise((resolve, reject) => { this.db.run('INSERT INTO ping_results (host, latency, packet_loss, error, success, network_name) VALUES (?, ?, ?, ?, ?, ?)', [host, latency, packetLoss, error, error ? 0 : 1, networkName], (err) => { if (err) { logger_1.logger.error('Failed to insert ping result:', { error: err.message, code: err.code }); reject(err); } else resolve(); }); }); } async insertWebsiteTest(url, latency, statusCode, error, networkName) { return new Promise((resolve, reject) => { this.db.run('INSERT INTO website_tests (url, latency, status_code, error, success, network_name) VALUES (?, ?, ?, ?, ?, ?)', [url, latency, statusCode, error, error ? 0 : 1, networkName], (err) => { if (err) reject(err); else resolve(); }); }); } async insertSpeedTest(downloadSpeed, uploadSpeed, ping, server, error, triggerReason, networkName) { return new Promise((resolve, reject) => { this.db.run('INSERT INTO speed_tests (download_speed, upload_speed, ping, server, error, trigger_reason, success, network_name) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', [downloadSpeed, uploadSpeed, ping, server, error, triggerReason, error ? 0 : 1, networkName], (err) => { if (err) reject(err); else resolve(); }); }); } async updateBaseline(testType, target, latency) { return new Promise((resolve, reject) => { this.db.run(`INSERT INTO latency_baselines (test_type, target, baseline_latency, sample_count, last_updated) VALUES (?, ?, ?, 1, CURRENT_TIMESTAMP) ON CONFLICT(test_type, target) DO UPDATE SET baseline_latency = (baseline_latency * sample_count + ?) / (sample_count + 1), sample_count = sample_count + 1, last_updated = CURRENT_TIMESTAMP`, [testType, target, latency, latency], (err) => { if (err) reject(err); else resolve(); }); }); } async getBaseline(testType, target) { return new Promise((resolve, reject) => { this.db.get('SELECT baseline_latency FROM latency_baselines WHERE test_type = ? AND target = ?', [testType, target], (err, row) => { if (err) reject(err); else resolve(row?.baseline_latency || null); }); }); } async getNetworkNames() { return new Promise((resolve, reject) => { const query = ` SELECT DISTINCT network_name FROM ( SELECT network_name FROM dns_queries WHERE network_name IS NOT NULL UNION SELECT network_name FROM ping_results WHERE network_name IS NOT NULL UNION SELECT network_name FROM website_tests WHERE network_name IS NOT NULL UNION SELECT network_name FROM speed_tests WHERE network_name IS NOT NULL ) ORDER BY network_name `; this.db.all(query, (err, rows) => { if (err) reject(err); else resolve(rows.map(row => row.network_name)); }); }); } close() { this.db.close(); } async runWithRetry(operation, operationName) { let lastError = null; for (let attempt = 0; attempt < this.MAX_RETRIES; attempt++) { try { await operation(); return; // Success } catch (err) { lastError = err; // Check if it's a busy/locked/readonly error if (err.code === 'SQLITE_BUSY' || err.code === 'SQLITE_LOCKED' || err.code === 'SQLITE_READONLY') { logger_1.logger.warn(`Database issue on ${operationName}:`, { code: err.code, message: err.message, attempt: attempt + 1, maxRetries: this.MAX_RETRIES }); // For readonly, log additional diagnostic info if (err.code === 'SQLITE_READONLY') { const dbPath = path.join(config_1.config.db.directory, config_1.config.db.filename); logger_1.logger.error('Database is readonly. Check file permissions:', { path: dbPath, exists: fs.existsSync(dbPath), stats: fs.existsSync(dbPath) ? fs.statSync(dbPath).mode : null }); } await new Promise(resolve => setTimeout(resolve, this.RETRY_DELAY * (attempt + 1))); continue; } // For other errors, fail immediately throw err; } } // If we get here, all retries failed logger_1.logger.error(`Failed ${operationName} after ${this.MAX_RETRIES} attempts`); throw lastError; } } exports.Database = Database; //# sourceMappingURL=database.js.map