Notice: Due to size constraints and loading performance considerations, scripts referenced in blog posts are not attached directly. To request access, please complete the following form: Script Request Form Note: A Google account is required to access the form.
Disclaimer: I do not accept responsibility for any issues arising from scripts being run without adequate understanding. It is the user's responsibility to review and assess any code before execution. More information

DNS Performance Monitoring: From Data Collection to Visual Dashboard

 In my previous post, I covered setting up performance data collection using Windows Performance Counters to monitor DNS query rates across multiple servers 

Now that I had the data flowing into CSV files every 60 minutes, I needed an effective way to visualize and analyze this performance data.

The Visual Results

This is the base website where you will need the CSV from the previous post, from here you can drag and drop the CSV in the grey area or click and select the file:



This will then read the CSV as below:


Then you will see a summary of the data as you can see below:



Then you will see the data from the DNS performance counters as below:


Then you can filter on certain servers and trend those servers, using the tickboxes:


The Challenge: Making Sense of Raw Performance Data

Having raw performance counter data is one thing, but turning it into actionable insights requires proper visualization. I needed a dashboard that could:

  • Display data from multiple DNS servers on a single chart
  • Allow toggling individual servers on/off for comparison
  • Handle varying numbers of servers as infrastructure grows
  • Update easily when new data becomes available
  • Look professional, not like something "pooped out by a unicorn"

The CSV data structure I was working with looked like this:

Timestamp,ServerName,TotalQueryReceivedPerSec
2025-06-11 09:00:00,bearclaws,245.5
2025-06-11 09:00:00,bearpaws,312.8
2025-06-11 09:00:00,grizzlybear,198.2
2025-06-11 09:01:00,honeybear,267.1

Initial Approach: Static HTML Generation

I started by creating a PowerShell script that would:

  1. Read the CSV data from performance counters
  2. Generate a complete HTML file with embedded data
  3. Create a static dashboard with all the JavaScript and styling included
$csvData = Import-Csv -Path "dns_performance.csv"
$servers = $csvData | Select-Object -ExpandProperty ServerName | Sort-Object -Unique

# Convert data to JavaScript format
$jsData = foreach ($row in $csvData) {
    @{
        timestamp = $row.Timestamp
        server = $row.ServerName
        value = [double]$row.TotalQueryReceivedPerSec
    }
}

# Generate HTML with embedded data
$htmlContent = @"
<script>
const rawData = $($jsData | ConvertTo-Json);
</script>
"@

This approach worked technically - the script could parse CSV files, assign unique colors to each server, and generate a functional dashboard. However, I quickly realized this had limitations:

  • Required re-running the PowerShell script every time data updated
  • Made it cumbersome to quickly analyze different datasets
  • Created a disconnect between data collection and visualization

My Solution: Dynamic CSV Upload Dashboard

After testing the static generation approach, I pivoted to a more flexible solution - a single HTML dashboard that could dynamically load any CSV file through drag-and-drop functionality. This proved far more effective because:

Simply drag a CSV file onto the dashboard and instantly see the visualization - no script execution required.

The CSV parsing logic looked like this:

async function parseCSVData(csvText) {
    const lines = csvText.trim().split('\n');
    const headers = lines[0].split(',');
    
    // Find column indices automatically
    const timestampIndex = headers.findIndex(h => 
        h.toLowerCase().includes('timestamp') || h.toLowerCase().includes('time')
    );
    const serverIndex = headers.findIndex(h => 
        h.toLowerCase().includes('server') || h.toLowerCase().includes('name')
    );
    const queryIndex = headers.findIndex(h => 
        h.toLowerCase().includes('query') || h.toLowerCase().includes('total')
    );
    
    // Process each data row
    const rawData = [];
    for (let i = 1; i < lines.length; i++) {
        const values = lines[i].split(',');
        rawData.push({
            timestamp: new Date(values[timestampIndex]),
            server: values[serverIndex],
            queries: parseFloat(values[queryIndex])
        });
    }
    
    return rawData;
}

Design Evolution: From Unicorn Rainbow to Professional Clean

While visually striking, this design was completely inappropriate for a professional environment. I stripped it down to a clean, minimalistic design:

/* Clean, professional styling */
body {
    background: #f5f5f5;
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}

.stat-card {
    background: white;
    border: 1px solid #e0e0e0;
    border-radius: 8px;
}

.btn {
    background: #28a745;
    border: none;
    border-radius: 4px;
}

.btn:hover {
    background: #218838;
}

Technical Details

The final dashboard includes several key features:

Automatic Server Detection: The system reads the CSV headers and automatically identifies all unique servers:

function generateServerControls() {
    const servers = Object.keys(performanceData);
    
    servers.forEach(server => {
        const color = serverColors[server];
        const checkbox = document.createElement('div');
        checkbox.innerHTML = `
            <label>
                <input type="checkbox" id="server-${server}" checked onchange="updateChart()">
                <span class="server-color" style="background-color: ${color}"></span>
                <span class="server-name">${server.toUpperCase()}</span>
            </label>
        `;
        checkboxContainer.appendChild(checkbox);
    });
}

Interactive Controls: Each server gets a checkbox with its assigned color indicator. Users can toggle individual servers on/off:

function updateChart() {
    const servers = Object.keys(performanceData);
    servers.forEach((server, index) => {
        const checkbox = document.getElementById(`server-${server}`);
        const dataset = chart.data.datasets[index];
        if (dataset) {
            dataset.hidden = !checkbox.checked;
        }
    });
    chart.update();
}

Raw Data Visualization: Unlike many monitoring tools that aggregate data, this dashboard plots every single data point from the CSV, preserving all granular detail for analysis.

Previous Post Next Post

نموذج الاتصال