network-performance-analyzer
Version:
Automated analysis tool for network performance test datasets containing DNS testing results and iperf3 performance measurements
357 lines (313 loc) • 10.9 kB
text/typescript
// Network Performance Analyzer - Command Line Interface
import { Command } from 'commander';
import path from 'path';
import fs from 'fs-extra';
import { createNetworkPerformanceAnalyzer, AnalyzerConfig } from './services/NetworkPerformanceAnalyzer';
/**
* CLI configuration options
*/
interface CliOptions {
output?: string;
verbose?: boolean;
continueOnError?: boolean;
anomalyThresholds?: string;
parallel?: string;
monitor?: boolean;
environment?: string;
config?: string;
plugins?: string;
template?: string;
sections?: string;
}
/**
* Progress indicator for CLI
*/
class ProgressIndicator {
private spinner: string[] = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
private currentIndex = 0;
private intervalId: NodeJS.Timeout | null = null;
private message: string = '';
private isActive = false;
/**
* Start the progress indicator with a message
* @param message The message to display
*/
start(message: string): void {
this.message = message;
this.isActive = true;
if (process.stdout.isTTY) {
this.intervalId = setInterval(() => {
process.stdout.clearLine(0);
process.stdout.cursorTo(0);
process.stdout.write(`${this.spinner[this.currentIndex]} ${this.message}`);
this.currentIndex = (this.currentIndex + 1) % this.spinner.length;
}, 100);
} else {
console.log(`${this.message}...`);
}
}
/**
* Update the progress message
* @param message The new message to display
*/
update(message: string): void {
this.message = message;
if (!process.stdout.isTTY && this.isActive) {
console.log(`${this.message}...`);
}
}
/**
* Stop the progress indicator and display a completion message
* @param message The completion message to display
*/
stop(message?: string): void {
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
}
if (process.stdout.isTTY) {
process.stdout.clearLine(0);
process.stdout.cursorTo(0);
}
if (message) {
console.log(message);
}
this.isActive = false;
}
}
/**
* Custom logger for CLI output
*/
class CliLogger {
private isVerbose: boolean;
private progress: ProgressIndicator;
/**
* Create a new CLI logger
* @param isVerbose Whether to enable verbose logging
*/
constructor(isVerbose: boolean = false) {
this.isVerbose = isVerbose;
this.progress = new ProgressIndicator();
}
/**
* Log an informational message
* @param message The message to log
*/
info(message: string): void {
console.log(`[INFO] ${message}`);
}
/**
* Log a verbose message (only if verbose mode is enabled)
* @param message The message to log
*/
logVerbose(message: string): void {
if (this.isVerbose) {
console.log(`[VERBOSE] ${message}`);
}
}
/**
* Log an error message
* @param message The error message to log
* @param error Optional error object
*/
error(message: string, error?: unknown): void {
console.error(`[ERROR] ${message}`);
if (error && this.isVerbose) {
if (error instanceof Error) {
console.error(error.stack);
} else {
console.error(String(error));
}
}
}
/**
* Log a warning message
* @param message The warning message to log
*/
warn(message: string): void {
console.warn(`[WARNING] ${message}`);
}
/**
* Log a success message
* @param message The success message to log
*/
success(message: string): void {
console.log(`[SUCCESS] ${message}`);
}
/**
* Start a progress indicator
* @param message The progress message to display
*/
startProgress(message: string): void {
this.progress.start(message);
}
/**
* Update the progress indicator message
* @param message The new progress message to display
*/
updateProgress(message: string): void {
this.progress.update(message);
}
/**
* Stop the progress indicator
* @param message Optional completion message to display
*/
stopProgress(message?: string): void {
this.progress.stop(message);
}
}
/**
* Parse anomaly thresholds from a JSON string
* @param thresholdsStr JSON string containing anomaly thresholds
* @returns Parsed anomaly thresholds object
*/
function parseAnomalyThresholds(thresholdsStr: string): any {
try {
return JSON.parse(thresholdsStr);
} catch (error) {
throw new Error(`Invalid anomaly thresholds format: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Validate input directory exists and contains dataset directories
* @param inputDir Path to input directory
* @returns True if valid, throws error otherwise
*/
async function validateInputDirectory(inputDir: string): Promise<boolean> {
try {
const stats = await fs.stat(inputDir);
if (!stats.isDirectory()) {
throw new Error(`Input path is not a directory: ${inputDir}`);
}
const contents = await fs.readdir(inputDir);
// Check if there are any potential dataset directories
const potentialDatasets = contents.filter(item =>
item.startsWith('coredns-mtu') || item.startsWith('stock-mtu')
);
if (potentialDatasets.length === 0) {
throw new Error(`No potential dataset directories found in: ${inputDir}`);
}
return true;
} catch (error) {
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
throw new Error(`Input directory does not exist: ${inputDir}`);
}
throw error;
}
}
/**
* Run the CLI application
* @param args Command line arguments
* @returns Promise that resolves when the application completes
*/
export async function run(args: string[] = process.argv): Promise<void> {
const program = new Command();
program
.name('network-performance-analyzer')
.description('Analyze network performance test datasets and generate comparative reports')
.version('1.0.0')
.argument('<input-dir>', 'Directory containing network performance test datasets')
.option('-o, --output <file>', 'Output file path for the generated report (default: network-analysis-report.md)')
.option('-v, --verbose', 'Enable verbose logging', false)
.option('-c, --continue-on-error', 'Continue analysis when individual datasets fail', true)
.option('-t, --anomaly-thresholds <json>', 'Custom thresholds for anomaly detection as JSON string')
.option('-p, --parallel <number>', 'Maximum number of parallel tasks to run', '4')
.option('-m, --monitor', 'Enable performance monitoring', false)
.option('-e, --environment <env>', 'Environment to use for configuration (development, production, testing)')
.option('-C, --config <file>', 'Path to configuration file')
.option('-P, --plugins <dirs>', 'Comma-separated list of plugin directories')
.option('-T, --template <id>', 'Report template ID to use')
.option('-S, --sections <list>', 'Comma-separated list of report sections to include')
.addHelpText('after', `
Examples:
$ network-performance-analyzer ./test-datasets
$ network-performance-analyzer ./test-datasets -o ./reports/analysis.md
$ network-performance-analyzer ./test-datasets -v -c
$ network-performance-analyzer ./test-datasets -t '{"bandwidthVariation":0.2,"latencyVariation":0.3}'
$ network-performance-analyzer ./test-datasets -C ./config.json -e production
$ network-performance-analyzer ./test-datasets -P ./plugins,./custom-plugins
$ network-performance-analyzer ./test-datasets -T custom -S executive-summary,configuration-overview,recommendations
`)
.parse(args);
const inputDir = program.args[0];
const options = program.opts<CliOptions>();
// Create logger
const logger = new CliLogger(options.verbose);
try {
// Validate input directory
if (inputDir) {
logger.startProgress(`Validating input directory: ${inputDir}`);
await validateInputDirectory(inputDir);
logger.stopProgress(`Input directory validated: ${inputDir}`);
} else {
logger.error('No input directory specified');
program.help();
return;
}
// Parse anomaly thresholds if provided
let anomalyThresholds;
if (options.anomalyThresholds) {
try {
anomalyThresholds = parseAnomalyThresholds(options.anomalyThresholds);
logger.logVerbose(`Using custom anomaly thresholds: ${JSON.stringify(anomalyThresholds)}`);
} catch (error) {
if (error instanceof Error) {
logger.error(error.message);
} else {
logger.error(String(error));
}
process.exit(1);
}
}
// Determine output file path
const outputPath = options.output || path.join(process.cwd(), 'network-analysis-report.md');
logger.logVerbose(`Report will be saved to: ${outputPath}`);
// Configure analyzer
const config: AnalyzerConfig = {
continueOnError: options.continueOnError,
logProgress: options.verbose,
reportOutputPath: outputPath,
anomalyThresholds,
maxParallelTasks: options.parallel ? parseInt(options.parallel, 10) : undefined,
enablePerformanceMonitoring: options.monitor,
environment: options.environment,
configPath: options.config,
pluginDirectories: options.plugins ? options.plugins.split(',') : undefined,
reportTemplateId: options.template,
includeSections: options.sections ? options.sections.split(',') : undefined
};
// Create analyzer
const analyzer = createNetworkPerformanceAnalyzer(config);
// Run analysis
logger.info(`Starting network performance analysis on ${inputDir}`);
logger.startProgress('Analyzing network performance data');
const report = await analyzer.analyze(inputDir);
logger.stopProgress('Analysis completed successfully');
logger.success(`Report generated and saved to: ${outputPath}`);
// Print report summary
if (options.verbose) {
const reportLines = report.split('\n');
const summaryLines = reportLines.slice(0, Math.min(20, reportLines.length));
logger.logVerbose('Report summary:');
console.log(summaryLines.join('\n') + (reportLines.length > 20 ? '\n...' : ''));
}
} catch (error) {
logger.stopProgress();
if (error instanceof Error) {
logger.error('Analysis failed', error);
console.error(`Error: ${error.message}`);
} else {
logger.error('Analysis failed with unknown error');
console.error(`Error: ${String(error)}`);
}
process.exit(1);
}
}
// Run the CLI if this file is executed directly
if (require.main === module) {
run().catch(error => {
console.error('Unhandled error:', error instanceof Error ? error.message : String(error));
process.exit(1);
});
}