const τ = Math.PI * 2 function qpush(arr, val, len) { arr.push(val) if (arr.length > len) { arr.shift() } } class Stat { constructor() { this.Stat = {} this.LastStat = {} } async update() { let resp = await fetch("proc/stat") let stext = await resp.text() let lines = stext.split("\n") this.Date = new Date(resp.headers.get("Date")) this.LastStat = this.Stat this.Stat = {} for (let line of lines) { let parts = line.split(/\s+/) let key = parts.shift() let vals = parts.map(Number) this.Stat[key] = vals } } cpu(n="") { let key = `cpu${n}` let vals = this.Stat[key] let prev = this.LastStat[key] if (!vals || !prev) { return {} } let total = vals.reduce((a,b)=>a+b) - prev.reduce((a,b)=>a+b) return { user: (vals[0] - prev[0]) / total, nice: (vals[1] - prev[1]) / total, sys: (vals[2] - prev[2]) / total, idle: (vals[3] - prev[3]) / total, wait: (vals[4] - prev[4]) / total, irq: (vals[5] - prev[5]) / total, softirq: (vals[6] - prev[6]) / total, steal: (vals[7] - prev[7]) / total, guest: (vals[8] - prev[8]) / total, guestNice: (vals[9] - prev[9]) / total, } } } class AreaChart { constructor(element, stat, width=60) { this.stat = stat this.width = width this.canvas = element.appendChild(document.createElement("canvas")) this.data ={ labels: [], datasets: [] } this.datasets = {} for (let label of ["user", "nice", "sys", "idle", "wait"]) { let d = [] this.data.datasets.push({ label: label, data: d, }) this.datasets[label] = d } this.chart = new Chart( this.canvas, { type: "line", data: this.data, options: { responsive: true, pointStyle: false, scales: { x: { title: { display: false }, ticks: { display: false }, grid: { display: false }, }, y: { stacked: true, ticks: { display: false }, } } } } ) } async update() { let cpu = this.stat.cpu() qpush(this.data.labels, this.stat.Date, this.width) qpush(this.datasets.user, cpu.user, this.width) qpush(this.datasets.nice, cpu.nice, this.width) qpush(this.datasets.sys, cpu.sys, this.width) qpush(this.datasets.idle, cpu.idle, this.width) qpush(this.datasets.wait, cpu.wait, this.width) this.chart.update() } } class PieChart { constructor(element, stat) { this.stat = stat this.canvas = element.appendChild(document.createElement("canvas")) this.ctx = this.canvas.getContext("2d") this.r = 250 this.θ = 0 this.canvas.width = this.r*2 this.canvas.height = this.r*2 } arc(angle, color) { this.ctx.fillStyle = color this.ctx.beginPath() this.ctx.arc(this.r, this,r, this,r, this.θ, this.θ + angle) this.ctx.fill() } async update() { let cpu = this.stat.cpu() this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height) let colors = ["SkyBlue", "SeaGreen", "Gold", "Tomato"] let values = [cpu.user, cpu.nice, cpu.sys, cpu.wait] let labels = ["u", "n", "s", "w"] let θ = -τ/4 this.ctx.save() this.ctx.translate(this.r, this.r) this.ctx.font = `bold ${this.r/3}px sans-serif` for (let i = 0; i < colors.length; i++) { let angle = values[i] * τ this.ctx.fillStyle = colors[i] this.ctx.beginPath() this.ctx.arc(0, 0, this.r, θ, θ + angle, false) this.ctx.lineTo(0, 0) this.ctx.fill() if (angle > τ/12) { let mid = θ + angle/2 this.ctx.save() this.ctx.rotate(mid) this.ctx.translate(this.r*0.7, 0) this.ctx.rotate(-mid) this.ctx.fillStyle = "black" this.ctx.textBaseline = "middle" this.ctx.textAlign = "center" this.ctx.fillText(labels[i], 0, 0) this.ctx.restore() } θ += angle } this.ctx.restore() } } export { Stat, PieChart, }