// A continuous-axis heatmap: rows stay categorical (one host per row) while the
// x axis is a real datetime scale. Each cell sits at its true timestamp and the
// axis shows sparse proportional date/time ticks instead of one label per cell.
// Because cells are placed by value, a metrics outage (when the collector
// stopped reporting for every host at once) shows up as real empty space, which
// index-based tiling cannot represent.
var HOUR = 60 * 60 * 1000
var start = new Date('2024-03-04T00:00:00.000Z').getTime()
var totalHours = 72
// A deterministic pseudo-random generator so the demo is stable across reloads.
function makeRand(seed) {
var s = seed
return function () {
s = (s * 1103515245 + 12345) & 0x7fffffff
return s / 0x7fffffff
}
}
// Hours 30..39 are dropped for every host: a 10-hour window where the metrics
// collector was down and no CPU readings were recorded.
function isOutage(h) {
return h >= 30 && h < 40
}
// CPU utilization (%) with a daily rhythm: load builds through the working day,
// peaks mid-afternoon (14:00 UTC), and falls off overnight. `base` sets the
// baseline load and `swing` the size of the daily peak, so each host has its
// own profile (steady database, spiky web tier, mostly-idle cache).
function hostSeries(name, seed, base, swing) {
var rand = makeRand(seed)
var data = []
for (var h = 0; h < totalHours; h++) {
if (isOutage(h)) continue
var hourOfDay = h % 24
var daily = (Math.cos(((hourOfDay - 14) / 24) * Math.PI * 2) + 1) / 2
var cpu = base + swing * daily + (rand() - 0.5) * 12
data.push({
x: start + h * HOUR,
y: Math.max(1, Math.min(99, Math.round(cpu))),
})
}
return { name: name, data: data }
}
var heatData = [
hostSeries('web-01', 7, 22, 52),
hostSeries('web-02', 19, 20, 56),
hostSeries('api-01', 42, 27, 46),
hostSeries('api-02', 88, 25, 49),
hostSeries('cache-01', 123, 14, 16),
// The database is the hot box: steady high load that saturates (red) at the
// afternoon peak.
hostSeries('db-01', 256, 66, 18),
]
var options = {
series: heatData,
chart: {
height: 220,
type: 'heatmap',
toolbar: { show: false },
},
dataLabels: {
enabled: false,
},
title: {
text: 'Server CPU utilization across the fleet (a metrics outage shows as a real gap)',
align: 'left',
},
plotOptions: {
heatmap: {
// Flat CPU-health buckets on a green (idle) to red (saturated) scale, so a
// cell's color reads as a load level straight off the legend.
enableShades: false,
colorScale: {
ranges: [
{ from: 0, to: 30, color: '#00A65A', name: '0-30%' },
{ from: 30, to: 50, color: '#7CB342', name: '30-50%' },
{ from: 50, to: 70, color: '#FBC02D', name: '50-70%' },
{ from: 70, to: 85, color: '#FB8C00', name: '70-85%' },
{ from: 85, to: 100, color: '#E53935', name: '85-100%' },
],
},
},
},
xaxis: {
type: 'datetime',
labels: {
datetimeUTC: true,
datetimeFormatter: {
year: 'yyyy',
month: "MMM 'yy",
day: 'MMM dd',
hour: 'HH:mm',
},
},
},
tooltip: {
x: { show: true, format: 'MMM dd HH:mm' },
y: {
formatter: function (val) {
return val + '%'
},
},
},
}
var chart = new ApexCharts(document.querySelector('#chart'), options)
chart.render()