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:
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:
- Read the CSV data from performance counters
- Generate a complete HTML file with embedded data
- 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.