D3.jsの勉強がてら何か作ってみようかと思い、PCのCPU使用率を表示するものを作ってみました。
概要
実際の値を表示したかったので、
CPU使用率を返すサーバサイドをNode.jsで作成し、
情報を表示するクライアントサイドをD3.jsで作成しました。
動作例:PC3台でそれぞれサーバアプリを動作させて、クライアントで3つ分並べて表示
実施環境
- サーバサイド
- Node.js (v14.15.3)
- express : 4.17.1
- cors : 2.8.5
- Node.js (v14.15.3)
- クライアントサイド
- D3.js (v6.3.1)
※Versionに指定があるわけはなく、とりあえず試した際の各ソフトの最新Verを使用
サーバサイド(Node.js)側の実装
Node.jsにて express とcorsを使って、APIアクセスしたらCPU使用率の情報を返すアプリを作成。
CPU使用率は、os.cpus() でCPU情報を1秒周期で取得して、各モードの動作時間の増加量より算出。
サーバサイドのプロジェクト作成
プロジェクト用のディレクトリを作成したら
npmコマンドでプロジェクトを作成&必要なライブラリのインストールをする。
npm init
npm install express --save
npm install cors --save
サーバサイドのプログラム作成
実装の概要は以下
- setInterval()を使用して1秒周期でCPU使用率を測定
- os.cpus() で各CPUコア毎の各モードの動作時間の増加を取得
- idle時間以外はCPU使用中とみなして、CPU使用率は
1 - (idle時間 / 全モード合計時間)
で算出- 実装では%の値(100%の時は数値を
100
)にしたかったので100倍した式で記載
- 実装では%の値(100%の時は数値を
- 過去100回分の測定結果を保持
- httpで
http://***:12345/api/performance
にアクセスしたらCPU使用率の測定履歴をJSON形式で返却
var express = require("express");
var cors = require('cors')
var os = require('os');
var app = express();
app.use(cors());
var server = app.listen(12345);
// パフォーマンス測定用
var performance = class {
constructor() {
this.timer = null; // Interval Timer用
this.info = {}; // 測定情報
}
// 測定開始
start(interval, max_history) {
// 既存タイマーの停止
this.stop();
// 基本の情報の作成
let old_os = os.cpus();
let platform = `${os.platform()} ${os.release()} (${os.arch()})`
this.info = { 'platform': platform, 'interval': interval, 'max_history': max_history, 'items': {}};
for(let index = 0; index < old_os.length; index++) {
this.info.items['cpu' + index] = {'model': old_os[index].model, history:[]};
}
// タイマーの開始
let info = this.info;
this.timer = setInterval(function() {
// 各CPU使用率を算出
let now_os = os.cpus();
for(let index = 0; index < now_os.length; index++) {
// 1CPUずつCPU使用率を算出
let total = 0;
for (let type in now_os[index].times) { total += now_os[index].times[type]; }
for (let type in old_os[index].times) { total -= old_os[index].times[type]; }
let idle = now_os[index].times.idle - old_os[index].times.idle;
let cpu_per = Math.floor(100 - (100 * idle / total));
info.items['cpu' + index].history.unshift(cpu_per); // 先頭に最新情報追加
}
old_os = now_os;
// 履歴件数超過時に古い情報削除
for(let item in info.items) {
while (info.items[item].history.length > max_history) {
info.items[item].history.pop(); // 末尾の情報を削除
}
}
}, interval);
}
// 測定停止
stop() {
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
}
// 測定履歴の取得
get_info() {
return this.info;
}
};
var pref = new performance();
pref.start(1000, 100);
app.get("/api/performance", (req, res, next) => { res.json(pref.get_info()); });
サーバサイドの実行例
お試しで、ブラウザなどでhttp://***:12345/api/performance
にアクセスすると以下が返ってきます。
{
"platform":"linux 5.4.0-1022-raspi (arm64)",
"interval":1000,
"max_history":100,
"items":{
"cpu0":{
"model":"unknown",
"history":[1,0,0,0,4,5,0,0,0,1,2,2,2,0,1,0,2,5,0,1,1,0,0,1,0,2,0,41,0,0,1,0,1,2,0,6,0,5,0,1,0,1,1,1,1,1,14,23,1,0,0,0,0,1,0,0,1,2,0,41,1,0,0,2,1,0,1,1,0,2,1,1,1,0,3,1,3,7,1,2,1,1,0,0,2,1,2,0,1,1,2,9,0,0,0,0,2,0,0,2]
},
"cpu1":{
"model":"unknown",
"history":[1,1,0,0,0,0,0,1,1,2,0,1,0,0,1,1,0,3,1,1,0,0,1,1,1,2,2,11,0,3,0,1,0,0,0,1,2,1,0,2,0,0,1,1,0,0,0,0,2,0,1,0,1,1,0,1,0,0,2,7,1,1,1,0,0,6,1,0,1,1,1,1,0,3,0,1,20,17,5,3,0,0,2,1,0,1,0,1,1,0,2,10,1,1,0,1,1,1,0,0]
},
"cpu2":{
"model":"unknown",
"history":[0,0,0,2,2,0,0,1,3,1,0,2,1,1,1,1,0,2,1,2,1,2,1,2,2,0,2,40,1,4,1,0,1,2,1,1,1,1,1,0,0,1,0,1,0,1,3,2,3,4,0,2,1,2,1,1,1,1,0,43,1,0,1,0,0,1,1,0,0,0,0,0,1,0,0,1,0,1,2,2,1,2,1,0,0,1,1,0,2,0,1,46,1,2,0,5,0,0,1,0]
},
"cpu3":{
"model":"unknown",
"history":[1,1,2,2,2,3,1,0,2,1,1,1,1,1,0,1,8,20,1,2,0,1,0,0,0,1,2,9,1,4,1,1,0,0,3,0,2,0,1,0,3,1,0,1,2,0,1,2,28,12,1,3,1,0,2,0,0,0,2,8,0,1,0,1,2,0,0,0,1,2,1,1,0,0,1,1,1,1,0,5,1,1,1,0,1,2,1,2,0,2,0,34,1,1,1,0,1,2,0,2]
}
}}
クライアントサイドの作成
D3.jsでCPU使用率をグラフ表示する処理を作成
実装の概要は以下
- performanceMonitorクラス
- 1PCのCPU使用率情報を表示するためのクラス
- コンストラクタ
- D3.jsを使って、ベースとなるSVGの追加
- 座標計算用のd3.scaleLinearや、折れ線グラフ用のd3.lineの準備
- グラフの背景や、Y軸目盛の作成
- updateメソッド
- fetch APIを使って、サーバサイドのCPU使用率情報を取得
- CPU使用率情報の取得に成功したら、drawメソッドを呼び出して描画更新させる
- drawメソッド
- 初回のみSVGに折れ線グラフ表示用のpathと、凡例用のlineとtextを追加
- CPU使用率情報に従ってpath(折れ線グラフ)を更新、凡例のtext内容を更新
- setInterval()関数を使って1秒ごとにCPU情報を再取得して描画更新
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>Performance Monitor</title>
<script type="text/javascript" src="https://d3js.org/d3.v6.min.js"></script>
</head>
<body>
<div id="monitor1" style="float:left;"></div>
<div id="monitor2" style="float:left;"></div>
<div id="monitor3" style="float:left;"></div>
<script type="text/javascript">
class performanceMonitor {
constructor(select, width, height, url) {
const margin = {'top': 10, 'bottom':10, 'left':40, 'right': 40};
const history_max = 100;
this.width = width;
this.height = height;
this.url = url;
this.items = {};
this.svg = d3.select(select).append("svg").attr("width", width).attr("height", height);
this.yScale = d3.scaleLinear().domain([0, 100]).range([height - margin.bottom, margin.top]);
this.xScale = d3.scaleLinear().domain([0, history_max]).range([width - margin.right, margin.left]);
this.cScale = d3.scaleOrdinal(d3.schemePaired);
this.line = d3.line().x((d, i) => this.xScale(i)).y((d, i) => this.yScale(d));
// グラフの背景
this.svg.append("rect").attr("fill","#F8F8F8").attr("stroke","#000000")
.attr("x", this.xScale(100)).attr("width", this.xScale(0)-this.xScale(100))
.attr("y", this.yScale(100)).attr("height", this.yScale(0)-this.yScale(100));
// 左右にY軸表示
this.svg.append("g").attr("class", "axis axis-l").attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(this.yScale).ticks(10));
this.svg.append("g").attr("class", "axis axis-r").attr("transform", `translate(${width - margin.right},0)`)
.call(d3.axisRight(this.yScale).ticks(10));
// プラットフォームの表示
this.platform = this.svg.append("text").attr("x",65).attr("y",40).attr("font-size","16px");
}
update() {
let parent = this;
fetch(this.url).then(res => res.json()).then(data => parent.draw(data));
}
draw(data) {
// プラットフォームテキストの更新
this.platform.text(data.platform);
// アイテム数分繰り返し
for(let item in data.items) {
if ((item in this.items) == false) {
// 初回のみsvgにpathを追加
let index = Object.keys(this.items).length;
let offset_y = 60 + 16 * index;
this.items[item] = {};
this.items[item].color = this.cScale(index);
this.items[item].path = this.svg.append("path");
this.items[item].line = this.svg.append("line")
.attr("x1",65).attr("x2",75).attr("y1",offset_y).attr("y2",offset_y)
.attr("stroke-width",2).attr("stroke",this.items[item].color);
this.items[item].text = this.svg.append("text").
attr("x",80).attr("y",offset_y).attr("font-size","10px");
}
// データの更新
this.items[item].path.datum(data.items[item].history)
.attr("fill", "none")
.attr("stroke", this.items[item].color)
.attr("d", this.line);
this.items[item].text.text(`${item} [${data.items[item].model}] ${data.items[item].history[0]}%`);
}
}
}
var monitor1 = new performanceMonitor("#monitor1", 600, 400, "http://192.168.11.12:12345/api/performance");
var monitor2 = new performanceMonitor("#monitor2", 600, 400, "http://192.168.11.5:12345/api/performance");
var monitor3 = new performanceMonitor("#monitor3", 600, 400, "http://192.168.11.11:12345/api/performance");
function updateDataset() {
monitor1.update();
monitor2.update();
monitor3.update();
}
setInterval(updateDataset,1000);
updateDataset();
</script>
</body>
</html>
- performanceMonitorのインスタンス作成部分
monitor1
、monitor2
、monitor3
のところは、各環境に合わせて変更を- 表示件数を増減させたければ、
<div id="monitor1">
のところと、new performanceMonitor
しているところと、monitor1.update();
を呼び出しているところの3か所を修正
- 表示件数を増減させたければ、
以上
サーバサイドをNode.jsで実装し、CPU使用率などもNode.jsの標準のもので取得しているため、
動作させるPCなどを意識せずに利用できます。
⇒実際にWindows10と、Ubuntu(RasPi3)で同じサーバアプリを動かしてみてます。