sysload
Version:
System Load Determination
188 lines (165 loc) • 6.88 kB
JavaScript
/*!
** SysLoad -- System Load Determination
** Copyright (c) 2017-2021 Dr. Ralf S. Engelschall <rse@engelschall.com>
**
** Permission is hereby granted, free of charge, to any person obtaining
** a copy of this software and associated documentation files (the
** "Software"), to deal in the Software without restriction, including
** without limitation the rights to use, copy, modify, merge, publish,
** distribute, sublicense, and/or sell copies of the Software, and to
** permit persons to whom the Software is furnished to do so, subject to
** the following conditions:
**
** The above copyright notice and this permission notice shall be included
** in all copies or substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
** SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/* internal requirements */
const os = require("os")
/* the exported API class */
module.exports = class SysLoad {
/* initialize internal state */
constructor (config = "std") {
if (typeof config === "string") {
if (config === "std") {
/* the standard time slot configuration */
this.config = {
"s1": 1,
"s10": 10 * 1,
"m1": 6 * 10 * 1,
"m10": 10 * 6 * 10 * 1,
"h1": 6 * 10 * 6 * 10 * 1,
"h10": 10 * 6 * 10 * 6 * 10 * 1
}
}
else if (config === "unix") {
/* the Unix-style time slot configuration */
this.config = {
"m5": 5 * 60,
"m10": 10 * 60,
"m15": 15 * 60
}
}
else
throw new Error("invalid time slot configuration (expected \"std\" or \"unix\")")
}
else if (typeof config === "object")
this.config = config
else
throw new Error("invalid time slot configuration (expected string or object)")
this.measuring = false
this.loads = null
}
/* measure once over a certain duration */
measure (duration = 100) {
return new Promise((resolve /*, reject */) => {
/* determine absolute CPU times once */
const cpuTimes = () => {
let idle = 0
let total = 0
const cpus = os.cpus()
for (let i = 0; i < cpus.length; i++) {
for (const type in cpus[i].times)
total += cpus[i].times[type]
idle += cpus[i].times.idle
}
return {
total: total / cpus.length,
idle: idle / cpus.length
}
}
/* determine CPU times at start */
const start = cpuTimes()
setTimeout(() => {
/* determine CPU times at end */
const end = cpuTimes()
/* calculate the difference */
const diff = { total: 0, idle: 0 }
if (end.total > start.total) {
/* regular case */
diff.total = end.total - start.total
diff.idle = end.idle - start.idle
}
else {
/* special case: integer overflow */
diff.total = Math.MAX_SAFE_INTEGER - start.total + end.total
diff.idle = Math.MAX_SAFE_INTEGER - start.idle + end.idle
}
/* calculate the system load */
let load = 100 - (100 * diff.idle / diff.total)
/* reduce resolution */
load = Math.trunc(load * 10) / 10
resolve(load)
}, duration)
})
}
/* start continuous measurement */
start () {
/* sanity check usage context */
if (this.measuring)
throw new Error("continuous measurement already in progress (stop first)")
/* indicate measurement */
this.measuring = true
/* the measurement intervals */
const measurementDuration = 1000
/* initialize load values and provide function for accounting subsequent values */
this.loads = {}
Object.keys(this.config).forEach((slot) => {
this.loads[slot] = []
})
const account = (slot, duration, load) => {
this.loads[slot].push(load)
if (this.loads[slot].length > Math.round(duration / measurementDuration))
this.loads[slot].shift()
}
/* continuously perform measurements */
const handler = async () => {
if (!this.measuring)
return
/* perform a single measurement over the measurement duration */
const load = await this.measure(measurementDuration)
/* account the measured load for different average slots */
Object.keys(this.config).forEach((slot) => {
account(slot, this.config[slot] * 1000, load)
})
this.timer = setTimeout(handler, 0)
}
this.timer = setTimeout(handler, 0)
}
/* stop continuous measurement */
stop () {
/* sanity check usage context */
if (!this.measuring)
throw new Error("continuous measurement not started (start first)")
/* indicate that the measuring should stop */
this.measuring = false
}
/* retrieve averaged system load values */
average () {
/* sanity check usage context */
if (!this.measuring)
throw new Error("continuous measurement not started (start first)")
/* calculate a single average load value */
const calcAverage = (slot) => {
if (this.loads[slot].length === 0)
return 0.0
let load = this.loads[slot].reduce((sum, val) =>
sum + val, 0) / this.loads[slot].length
load = Math.trunc(load * 10) / 10
return load
}
/* provide the averaged load values */
const result = {}
Object.keys(this.config).forEach((slot) => {
result[slot] = calcAverage(slot)
})
return result
}
}