portal/web/stat/webstat.mjs

168 lines
4.8 KiB
JavaScript

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,
}