network-performance-analyzer
Version:
Automated analysis tool for network performance test datasets containing DNS testing results and iperf3 performance measurements
475 lines (410 loc) • 16.8 kB
text/typescript
// Report Template Manager for Network Performance Analyzer
import fs from 'fs-extra';
import path from 'path';
import { ConfigurationManager } from '../config/ConfigurationManager';
/**
* Template section definition
*/
export interface TemplateSection {
/**
* Unique ID of the section
*/
id: string;
/**
* Display name of the section
*/
name: string;
/**
* Template content for the section
*/
template: string;
/**
* Whether the section is required
*/
required: boolean;
/**
* Order of the section in the report
*/
order: number;
}
/**
* Report template definition
*/
export interface ReportTemplate {
/**
* Template name
*/
name: string;
/**
* Template description
*/
description: string;
/**
* Template format (markdown, html, json)
*/
format: 'markdown' | 'html' | 'json';
/**
* Template sections
*/
sections: TemplateSection[];
}
/**
* Default report template
*/
export const DEFAULT_TEMPLATE: ReportTemplate = {
name: 'Default Markdown Template',
description: 'Standard markdown report template with all sections',
format: 'markdown',
sections: [
{
id: 'header',
name: 'Report Header',
template: '# Network Performance Analysis Report\n\n**Date:** {{date}}\n**Datasets Analyzed:** {{datasetCount}}\n\n',
required: true,
order: 0
},
{
id: 'executive-summary',
name: 'Executive Summary',
template: '## Executive Summary\n\nThis report presents a comprehensive analysis of network performance across different configurations, focusing on bandwidth, latency, reliability, and DNS resolution performance.\n\n### Key Findings\n\n{{#each keyFindings}}- {{this}}\n{{/each}}\n\n### Optimal Configuration\n\nBased on the analysis, the **{{optimalConfiguration}}** configuration provides the best overall performance.\n\n### Performance Highlights\n\n{{#each performanceHighlights}}- {{this}}\n{{/each}}\n',
required: true,
order: 1
},
{
id: 'configuration-overview',
name: 'Configuration Overview',
template: '## Configuration Overview\n\nThe following configurations were analyzed and ranked based on overall performance:\n\n| Rank | Configuration | Overall Score | Bandwidth Score | Latency Score | Reliability Score |\n|------|--------------|--------------|----------------|--------------|------------------|\n{{#each configurations}}| {{rank}} | {{configuration}} | {{overallScore}} | {{bandwidthScore}} | {{latencyScore}} | {{reliabilityScore}} |\n{{/each}}\n',
required: true,
order: 2
},
{
id: 'bandwidth-analysis',
name: 'Bandwidth Analysis',
template: '## Bandwidth Performance Analysis\n\n### Bandwidth Metrics\n\nThe following table shows bandwidth performance metrics across different configurations:\n\n| Configuration | Avg (Mbps) | Median (Mbps) | Max (Mbps) | Min (Mbps) | Std Dev | 95th % | 99th % |\n|--------------|------------|---------------|------------|------------|---------|--------|--------|\n{{#each bandwidthMetrics}}| {{configuration}} | {{avgBandwidthMbps}} | {{medianBandwidthMbps}} | {{maxBandwidthMbps}} | {{minBandwidthMbps}} | {{standardDeviation}} | {{percentile95}} | {{percentile99}} |\n{{/each}}\n',
required: false,
order: 3
},
{
id: 'latency-analysis',
name: 'Latency Analysis',
template: '## Latency Performance Analysis\n\n### Latency Metrics\n\nThe following table shows latency performance metrics across different configurations:\n\n| Configuration | Avg (ms) | Median (ms) | Max (ms) | Min (ms) | Jitter (ms) |\n|--------------|----------|-------------|----------|----------|-------------|\n{{#each latencyMetrics}}| {{configuration}} | {{avgLatencyMs}} | {{medianLatencyMs}} | {{maxLatencyMs}} | {{minLatencyMs}} | {{jitterMs}} |\n{{/each}}\n',
required: false,
order: 4
},
{
id: 'dns-analysis',
name: 'DNS Analysis',
template: '## DNS Performance Analysis\n\n### DNS Performance Metrics\n\nThe following table shows DNS performance metrics across different configurations:\n\n| Configuration | Avg Response Time (ms) | Median Response Time (ms) | Success Rate (%) |\n|--------------|------------------------|---------------------------|------------------|\n{{#each dnsMetrics}}| {{configuration}} | {{avgResponseTimeMs}} | {{medianResponseTimeMs}} | {{successRate}} |\n{{/each}}\n\n### Slowest DNS Domains\n\nThe following table shows the 10 slowest domains by average response time:\n\n| Domain | Avg Response Time (ms) | Success Rate (%) | Query Count |\n|--------|------------------------|------------------|-------------|\n{{#each slowestDomains}}| {{domain}} | {{avgResponseTimeMs}} | {{successRate}} | {{queryCount}} |\n{{/each}}\n',
required: false,
order: 5
},
{
id: 'anomalies',
name: 'Performance Anomalies',
template: '## Performance Anomalies\n\n{{#if anomalies.length}}The following performance anomalies were detected during analysis:\n\n{{#each anomalies}}### {{severity}} Severity: {{type}} Anomaly in {{configuration}}\n\n**Description:** {{description}}\n\n**Affected Metrics:**\n{{#each affectedMetrics}}- {{this}}\n{{/each}}\n\n**Recommendations:**\n{{#each recommendations}}- {{this}}\n{{/each}}\n\n{{/each}}{{else}}No significant performance anomalies were detected in the analyzed datasets.{{/if}}\n',
required: false,
order: 6
},
{
id: 'recommendations',
name: 'Recommendations',
template: '## Recommendations\n\nBased on the comprehensive analysis of network performance across configurations, the following recommendations are provided:\n\n{{#each recommendations}}- {{this}}\n{{/each}}\n',
required: true,
order: 7
},
{
id: 'footer',
name: 'Report Footer',
template: '\n\n---\n\n*Report generated by Network Performance Analyzer on {{timestamp}}*',
required: true,
order: 8
}
]
};
/**
* Report Template Manager for loading, customizing, and applying report templates
*/
export class ReportTemplateManager {
private templates: Map<string, ReportTemplate> = new Map();
private configManager: ConfigurationManager;
private activeTemplate: string = 'default';
/**
* Create a new ReportTemplateManager instance
* @param configManager Configuration manager instance
*/
constructor(configManager: ConfigurationManager) {
this.configManager = configManager;
// Register default template
this.registerTemplate('default', DEFAULT_TEMPLATE);
}
/**
* Register a template
* @param id Template ID
* @param template Template definition
* @returns This ReportTemplateManager instance for chaining
*/
registerTemplate(id: string, template: ReportTemplate): ReportTemplateManager {
this.templates.set(id, template);
return this;
}
/**
* Load a template from a file
* @param filePath Path to the template file
* @param id Optional ID for the template (defaults to filename without extension)
* @returns Promise that resolves to the loaded template
*/
async loadTemplateFromFile(filePath: string, id?: string): Promise<ReportTemplate> {
try {
const templateData = await fs.readJson(filePath);
const templateId = id || path.basename(filePath, path.extname(filePath));
// Validate template
if (!this.isValidTemplate(templateData)) {
throw new Error(`Invalid template format in ${filePath}`);
}
// Register template
this.registerTemplate(templateId, templateData);
return templateData;
} catch (error) {
console.error(`Error loading template from ${filePath}:`, error);
throw error;
}
}
/**
* Save a template to a file
* @param templateId Template ID
* @param filePath Path to save the template file
* @returns Promise that resolves when the file is saved
*/
async saveTemplateToFile(templateId: string, filePath: string): Promise<void> {
const template = this.getTemplate(templateId);
if (!template) {
throw new Error(`Template not found: ${templateId}`);
}
try {
// Ensure directory exists
await fs.ensureDir(path.dirname(filePath));
// Write template to file
await fs.writeJson(filePath, template, { spaces: 2 });
console.log(`Template saved to ${filePath}`);
} catch (error) {
console.error(`Error saving template to ${filePath}:`, error);
throw error;
}
}
/**
* Get a template by ID
* @param id Template ID
* @returns The template or undefined if not found
*/
getTemplate(id: string): ReportTemplate | undefined {
return this.templates.get(id);
}
/**
* Get all registered templates
* @returns Array of template IDs and names
*/
getTemplates(): Array<{ id: string; name: string }> {
return Array.from(this.templates.entries()).map(([id, template]) => ({
id,
name: template.name
}));
}
/**
* Set the active template
* @param templateId Template ID
* @returns This ReportTemplateManager instance for chaining
*/
setActiveTemplate(templateId: string): ReportTemplateManager {
if (!this.templates.has(templateId)) {
throw new Error(`Template not found: ${templateId}`);
}
this.activeTemplate = templateId;
return this;
}
/**
* Get the active template
* @returns The active template
*/
getActiveTemplate(): ReportTemplate {
const template = this.templates.get(this.activeTemplate);
if (!template) {
// Fall back to default template
return DEFAULT_TEMPLATE;
}
return template;
}
/**
* Get template sections for the active template
* @param includedSections Optional array of section IDs to include
* @returns Array of template sections
*/
getTemplateSections(includedSections?: string[]): TemplateSection[] {
const template = this.getActiveTemplate();
let sections = template.sections;
// Filter sections if includedSections is provided
if (includedSections) {
sections = sections.filter(section =>
section.required || includedSections.includes(section.id)
);
}
// Sort sections by order
return sections.sort((a, b) => a.order - b.order);
}
/**
* Create a custom template by modifying the active template
* @param customizations Template customizations
* @returns The customized template
*/
createCustomTemplate(customizations: Partial<ReportTemplate>): ReportTemplate {
const baseTemplate = this.getActiveTemplate();
// Create a deep copy of the base template
const customTemplate: ReportTemplate = JSON.parse(JSON.stringify(baseTemplate));
// Apply customizations
if (customizations.name) customTemplate.name = customizations.name;
if (customizations.description) customTemplate.description = customizations.description;
if (customizations.format) customTemplate.format = customizations.format;
// Merge sections
if (customizations.sections) {
for (const customSection of customizations.sections) {
const existingIndex = customTemplate.sections.findIndex(s => s.id === customSection.id);
if (existingIndex >= 0) {
// Update existing section
customTemplate.sections[existingIndex] = {
...customTemplate.sections[existingIndex],
...customSection
};
} else {
// Add new section
customTemplate.sections.push(customSection);
}
}
}
return customTemplate;
}
/**
* Apply a template to generate a report
* @param template Template to apply
* @param data Data to use in the template
* @returns The generated report
*/
applyTemplate(template: ReportTemplate, data: any): string {
// Get included sections from configuration
const reportConfig = this.configManager.getSection('reporting');
const includedSections = reportConfig?.includeSections;
// Get template sections
const sections = this.getTemplateSections(includedSections);
// Apply each section template
const renderedSections = sections.map(section => {
return this.renderTemplateSection(section.template, data);
});
// Join sections to create the complete report
return renderedSections.join('\n\n');
}
/**
* Render a template section with data
* @param template Template string
* @param data Data to use in the template
* @returns The rendered template
* @private
*/
private renderTemplateSection(template: string, data: any): string {
// Simple template rendering with {{variable}} and {{#each array}}...{{/each}} support
let result = template;
// Replace {{variable}} with data values
result = result.replace(/\{\{([^#\/][^}]*)\}\}/g, (match, key) => {
const trimmedKey = key.trim();
return this.getNestedValue(data, trimmedKey) || '';
});
// Handle {{#each array}}...{{/each}} loops
result = result.replace(/\{\{#each\s+([^}]*)\}\}([\s\S]*?)\{\{\/each\}\}/g, (match, arrayKey, content) => {
const trimmedKey = arrayKey.trim();
const array = this.getNestedValue(data, trimmedKey);
if (!Array.isArray(array)) {
return '';
}
return array.map(item => {
let itemContent = content;
// Replace {{this}} with the current item
itemContent = itemContent.replace(/\{\{this\}\}/g, String(item));
// Replace {{property}} with item properties
itemContent = itemContent.replace(/\{\{([^#\/][^}]*)\}\}/g, (propMatch: string, propKey: string) => {
const trimmedPropKey = propKey.trim();
if (typeof item === 'object' && item !== null) {
return item[trimmedPropKey] !== undefined ? String(item[trimmedPropKey]) : '';
}
return '';
});
return itemContent;
}).join('');
});
// Handle {{#if condition}}...{{else}}...{{/if}} conditionals
result = result.replace(/\{\{#if\s+([^}]*)\}\}([\s\S]*?)(?:\{\{else\}\}([\s\S]*?))?\{\{\/if\}\}/g, (match, condition, ifContent, elseContent = '') => {
const trimmedCondition = condition.trim();
const conditionValue = this.getNestedValue(data, trimmedCondition);
if (conditionValue) {
return ifContent;
} else {
return elseContent;
}
});
return result;
}
/**
* Get a nested value from an object using dot notation
* @param obj Object to get value from
* @param path Path to the value using dot notation
* @returns The value or undefined if not found
* @private
*/
private getNestedValue(obj: any, path: string): any {
if (!obj) return undefined;
const keys = path.split('.');
let value = obj;
for (const key of keys) {
if (value === undefined || value === null) {
return undefined;
}
value = value[key];
}
return value;
}
/**
* Check if an object is a valid template
* @param obj Object to check
* @returns True if the object is a valid template
* @private
*/
private isValidTemplate(obj: any): obj is ReportTemplate {
return obj &&
typeof obj.name === 'string' &&
typeof obj.description === 'string' &&
(obj.format === 'markdown' || obj.format === 'html' || obj.format === 'json') &&
Array.isArray(obj.sections) &&
obj.sections.every((section: any) =>
typeof section.id === 'string' &&
typeof section.name === 'string' &&
typeof section.template === 'string' &&
typeof section.required === 'boolean' &&
typeof section.order === 'number'
);
}
/**
* Create a default template file if it doesn't exist
* @param filePath Path to create the template file
* @returns Promise that resolves when the file is created
*/
static async createDefaultTemplate(filePath: string): Promise<void> {
try {
if (!await fs.pathExists(filePath)) {
// Ensure directory exists
await fs.ensureDir(path.dirname(filePath));
// Write default template to file
await fs.writeJson(filePath, DEFAULT_TEMPLATE, { spaces: 2 });
console.log(`Default template created at ${filePath}`);
}
} catch (error) {
console.error(`Error creating default template at ${filePath}:`, error);
throw error;
}
}
}