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
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.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