UNPKG

network-performance-analyzer

Version:

Automated analysis tool for network performance test datasets containing DNS testing results and iperf3 performance measurements

250 lines (220 loc) 9.94 kB
// Dataset Discovery Service Implementation import { DatasetDiscoveryService, Dataset, FileSystemError } from '../models'; import { DefaultErrorHandler } from '../utils/ErrorHandler'; import { DataValidator } from '../utils/DataValidator'; import fs from 'fs-extra'; import path from 'path'; /** * Default implementation of the DatasetDiscoveryService interface * Discovers and validates network performance test datasets in the filesystem */ export class DefaultDatasetDiscoveryService implements DatasetDiscoveryService { private errorHandler: DefaultErrorHandler; private validator: DataValidator; /** * Creates a new instance of DefaultDatasetDiscoveryService * @param errorHandler Optional custom error handler */ constructor(errorHandler?: DefaultErrorHandler) { this.errorHandler = errorHandler || new DefaultErrorHandler(); this.validator = new DataValidator(this.errorHandler); } /** * Discover all datasets in the given root path * @param rootPath The root directory to search for datasets * @returns A promise that resolves to an array of discovered datasets */ async discoverDatasets(rootPath: string): Promise<Dataset[]> { try { // Check if the root path exists if (!await this.pathExists(rootPath)) { const error = new Error(`Root path does not exist: ${rootPath}`) as FileSystemError; error.code = 'ENOENT'; error.path = rootPath; this.errorHandler.handleFileSystemError(error); return []; } // Get all directories in the root path const entries = await this.readDirectory(rootPath); if (!entries) return []; const directories = entries .filter(entry => entry.isDirectory()) .map(dir => dir.name); // Filter directories that match the pattern coredns-mtu*-aws-logs_* or stock-mtu*-aws-logs_* const datasetDirs = directories.filter(dir => /^(coredns|stock)-mtu\d+-aws-logs_(enabled|disabled)$/.test(dir) ); // Process each dataset directory const datasets: Dataset[] = []; for (const dirName of datasetDirs) { try { const dirPath = path.join(rootPath, dirName); const dirEntries = await this.readDirectory(dirPath); if (!dirEntries) continue; // Look for parameters and results files const parametersFile = dirEntries.find(file => file.name.startsWith('parameters-results_')); const resultsFile = dirEntries.find(file => file.name.startsWith('results_')); if (!parametersFile && !resultsFile) { const error = new Error(`No parameter or result files found in directory: ${dirPath}`) as FileSystemError; error.code = 'ENOENT'; error.path = dirPath; this.errorHandler.handleFileSystemError(error); continue; } // Extract configuration from directory name const configInfo = this.extractConfigurationFromDirName(dirName); // Create dataset object const dataset: Dataset = { name: dirName, parametersFile: parametersFile ? path.join(dirPath, parametersFile.name) : '', resultsFile: resultsFile ? path.join(dirPath, resultsFile.name) : '', configuration: { mtu: configInfo.mtu, awsLogging: configInfo.awsLogging, backendServer: configInfo.backendServer, testDate: this.extractDateFromFileName(parametersFile?.name || resultsFile?.name || '') } }; // Check file existence first const hasParametersFile = dataset.parametersFile && await this.pathExists(dataset.parametersFile); const hasResultsFile = dataset.resultsFile && await this.pathExists(dataset.resultsFile); // A dataset is valid if it has at least a results file if (hasResultsFile) { // Only validate if we have the required files const validationResult = this.validator.validateDataset(dataset); if (validationResult.isValid || hasResultsFile) { // Accept dataset if validation passes OR if it has results file (more lenient) datasets.push(dataset); } else { // Only log validation errors for datasets that have files but fail validation const errorMessage = `Dataset validation failed in ${dirPath}: ${validationResult.errors .filter(e => e.severity === 'error') .map(e => `${e.field} - ${e.message}`) .join(', ')}`; console.warn(`[WARN] ${errorMessage}`); } } else { // Silently skip datasets without results files (they're incomplete) console.debug(`[DEBUG] Skipping dataset ${dirName} - no results file found`); } } catch (error) { const fsError = error instanceof Error ? error : new Error(String(error)); (fsError as FileSystemError).path = path.join(rootPath, dirName); (fsError as FileSystemError).code = (error as any).code || 'UNKNOWN'; this.errorHandler.handleFileSystemError(fsError as FileSystemError); } } return datasets; } catch (error) { const fsError = error instanceof Error ? error : new Error(String(error)); (fsError as FileSystemError).path = rootPath; (fsError as FileSystemError).code = (error as any).code || 'UNKNOWN'; this.errorHandler.handleFileSystemError(fsError as FileSystemError); return []; } } /** * Validate that a dataset has all required files and information * @param dataset The dataset to validate * @returns True if the dataset is complete, false otherwise */ validateDatasetCompleteness(dataset: Dataset): boolean { try { // Use the validator to validate the dataset const validationResult = this.validator.validateDataset(dataset); if (!validationResult.isValid) { return false; } // Check if either parameters file or results file exists const hasParametersFile = !!(dataset.parametersFile && fs.existsSync(dataset.parametersFile)); const hasResultsFile = !!(dataset.resultsFile && fs.existsSync(dataset.resultsFile)); // At least one file must exist return hasParametersFile || hasResultsFile; } catch (error) { // Log the error but don't throw this.errorHandler.logError( error instanceof Error ? error : new Error(String(error)), `Error validating dataset completeness: ${dataset.name}` ); return false; } } /** * Check if a path exists with error handling * @param filePath Path to check * @returns True if the path exists, false otherwise */ private async pathExists(filePath: string): Promise<boolean> { try { return await fs.pathExists(filePath); } catch (error) { const fsError = error instanceof Error ? error : new Error(String(error)); (fsError as FileSystemError).path = filePath; (fsError as FileSystemError).code = (error as any).code || 'UNKNOWN'; this.errorHandler.handleFileSystemError(fsError as FileSystemError); return false; } } /** * Read a directory with error handling * @param dirPath Path to the directory * @returns Array of directory entries or null if an error occurred */ private async readDirectory(dirPath: string): Promise<fs.Dirent[] | null> { try { return await fs.readdir(dirPath, { withFileTypes: true }); } catch (error) { const fsError = error instanceof Error ? error : new Error(String(error)); (fsError as FileSystemError).path = dirPath; (fsError as FileSystemError).code = (error as any).code || 'UNKNOWN'; this.errorHandler.handleFileSystemError(fsError as FileSystemError); return null; } } /** * Extract configuration information from directory name * @param dirName The directory name to parse * @returns Configuration information extracted from the directory name */ private extractConfigurationFromDirName(dirName: string): { mtu: number; awsLogging: boolean; backendServer: string; } { // Parse directory name like "coredns-mtu1500-aws-logs_enabled" const mtuMatch = dirName.match(/mtu(\d+)/); const loggingMatch = dirName.match(/aws-logs_(enabled|disabled)/); const serverMatch = dirName.match(/^(coredns|stock)/); return { mtu: mtuMatch && mtuMatch[1] ? parseInt(mtuMatch[1], 10) : 0, awsLogging: loggingMatch ? loggingMatch[1] === 'enabled' : false, backendServer: serverMatch && serverMatch[1] ? serverMatch[1] : 'unknown' }; } /** * Extract date from file name * @param fileName The file name to parse * @returns Date string extracted from the file name */ private extractDateFromFileName(fileName: string): string { // Parse date from file name like "parameters-results_20250717_151520.json" const dateMatch = path.basename(fileName).match(/(\d{8}_\d{6})/); if (dateMatch && dateMatch[1]) { const dateStr = dateMatch[1]; // Format: YYYYMMDD_HHMMSS try { const year = dateStr.substring(0, 4); const month = dateStr.substring(4, 6); const day = dateStr.substring(6, 8); const hour = dateStr.substring(9, 11); const minute = dateStr.substring(11, 13); const second = dateStr.substring(13, 15); return `${year}-${month}-${day} ${hour}:${minute}:${second}`; } catch (error) { console.warn(`Failed to parse date from ${dateStr}: ${error}`); } } return new Date().toISOString(); } } export { DatasetDiscoveryService };