Node.jsで.csv形式でログを取って.tar.gz形式で圧縮する

https://soluna-eureka.hatenablog.com/entry/2022/02/26/070110

の続き




# 開発背景
電力が貴重なこの時代！学術研究でも計算機の消費電力は無視できない！そもそもCPU温度が下がらない状況は危ない！<br>
ということで常駐で温度を監視してログを取れるやつにまで昇華しました，やってることは基礎的なことのはずなんだけどな…

ちなみに冷却装置の設定によっては室温との相関があるかもしれない，流体の熱伝導を考えればそれはそうという感じはあるが




# 機能

+ 1秒毎にCPU温度を監視
  + 設定温度を超過するとSlackにログを送信
+ 毎分10n秒でログを`.csv`に書き込む
  + excelとかで見るのを前提に
+ 毎時0分でSlackにログを送信
  + 動作確認の意味合いも兼ねて
+ 毎日0時5分に前日の`.csv`を`.tar.gz`に圧縮し元の`.csv`を削除
  + もちろん最大圧縮




# 以下，反省と言い訳


## csvの操作がクソ難しい
こちらもオブジェクト型や配列型の酷使に慣れていなかったというのもあるが，単純に`npm`の`csv`の各ライブラリ（`csv-parse`とか`csv-stringify`とか）の仕様がかなり変わっていて導入が面倒だった，ごく最近のいい感じのブログとかもなかったので公式ドキュメントを見ないとなかなか実装できなかった，**あっ営利目的でこの記事を参考にしてもいいけど参考にしたらここのリンクを広めといてね♡**


## `async`/`await`多すぎ問題
これって`sync`なライブラリだよな〜と思ったら`async`でした〜みたいなことがちょっと多すぎて頭にきますよ〜，じゃけん`async`/`await`かけますね（保険的な意味合いで），`async function`で定義した関数を`await`なしで呼び出すと`promise`が返ってくるなんて初めて知りましたね…言われてみればそうなんだけど，本当にそうなんだ…


## `.csv`ファイルの成形問題
ヘッダの有無くらいしか実装できなかったが，実際の利用においては問題はないと判断したのでこれで行くことにした，調べた限りではヘッダありで`.csv`をパースして`json`ライクにオブジェクト型でデータを操作する的な場合もあるらしいが，わざわざ各ループの間で状態を渡し続ける実装なんて面倒だし，もう1行目の完全一致だけで判定した方がはるかに早そうなのでなぁ…


## その無駄っぽい変数宣言いる？
いる（確信），書いてるうちにわからなくなりかけたので必要だった（確信），ちなみに文字数と修飾語で用途を分けている


## 地味に温度の精度が悪い
1℃単位の測定しか許容されていなかったり誤差が無駄に大きかったりという噂がOpen Hardware Monitorにはあるらしいが，まぁ気休め程度だと思って使うことにする，実際に測定結果を見ていても面白いから問題なし




# コード

```index.js
// IO plugin
const fs = require('fs-extra');
const tar = require('tar');
const tryCatch = require('try-catch');
const scheduler = require('node-schedule');

// API plugin
const { getJSON } = require('simple-get-json');
const { stringify } = require('csv-stringify/sync');
const { parse } = require('csv-parse/sync');
const csvStringify = stringify;
const csvParsing = parse;

//Util plugin
const datetime = require('date-and-time');
const { IncomingWebhook } = require("@slack/webhook");

// Parameters
const slackURL = "https://hooks.slack.com/services/xxx/yyy/zzz";
const slackWebhook = new IncomingWebhook(slackURL);
const monitorURL = "http://localhost:8085/data.json";
const coreTempLimit = 60;




// API for Open Hardware Monitor
async function accessJSON() {
    try {
        var res = await getJSON(monitorURL);
        var raw = JSON.stringify(res, null, 4);
        var obj = JSON.parse(raw)['Children']; // for Open Hardware Monitor
        var tgt = obj[0]['Children'][1]['Children'][1]['Children']; // for Open Hardware Monitor
        return tgt;
    } catch (err) {
        console.log("something wrong happened with CPU Monitor!")
        tgt = [
            { Text: "0", Value: "0" } // for open hardware monitor
        ];
        return tgt;
    }
}


// Shaper for Open Hardware Monitor
async function makeMessage(tgt) {
    var cpuParam = "";
    var coreName = "";
    var coreTemp = "";
    var heatFlag = false;

    var i = 0;
    var j = 0;
    tgt.forEach(cpuCore => {
        coreName = cpuCore['Text']; // for Open Hardware Monitor
        coreTemp = cpuCore['Value']; // for Open Hardware Monitor
        cpuParam += (coreName + " is " + coreTemp + "\n");
        floatCoreTemp = parseFloat(coreTemp);
        i = i + 1
        if (floatCoreTemp > coreTempLimit) {
            j = j + 1;
        }
    });
    if (i == j) {
        heatFlag = true; // whether need to notice or not
    }

    var txt = cpuParam + datetime.format(new Date(), 'YYYY/MM/DD HH:mm:ss:SSS') + "\n";
    var flg = heatFlag;
    return [flg, txt];
}


async function makeCSVtext(tgt) {
    var cpuParam = [];
    var coreName = "";
    var coreNames = ["date"];
    var coreTemp = "";
    var coreTemps = [datetime.format(new Date(), 'YYYY/MM/DD HH:mm:ss:SSS')];
    var heatFlag = false;

    var i = 0;
    var j = 0;
    tgt.forEach(cpuCore => {
        coreName = cpuCore['Text']; // for Open Hardware Monitor
        coreNames.push(coreName);
        coreTemp = cpuCore['Value']; // for Open Hardware Monitor
        floatCoreTemp = parseFloat(coreTemp);
        coreTemps.push(floatCoreTemp);
        i = i + 1
        if (floatCoreTemp > coreTempLimit) {
            j = j + 1;
        }
    });
    if (i == j) {
        heatFlag = true; // judge whether to notice or not
    }

    cpuParam = [coreNames, coreTemps];
    var txt = csvStringify(cpuParam, { header: false }, { delimiter: "," });
    var flg = heatFlag;
    return [flg, txt];
}



// csv(array)_txt(string) Parse&Build interface
async function parseCSVdata(txt) {
    var csv = await csvParsing(txt, { columns: false, delimiter: "," });
    return csv;
}

async function parseCSVdataHead(txt) {
    var csv = await csvParsing(txt, { columns: false, delimiter: ",", to_line: 1 }); // get only header line
    return csv;
}

async function buildCSVdata(csv) {
    var txt = await csvStringify(csv, { header: false, delimiter: "," });
    return txt;
}



// API for Slack
async function sendMessage(flg, txt) {
    if (flg) { // grasp whether to notice or not
        try {
            await slackWebhook.send({
                text: `${txt}`
            });
        } catch (err) {
            var text = "temp flag is " + flg + ", but something error happened with slack!";
            console.log(text);
        }
    }
}

async function sendLogging(flg, txt) {
    try {
        await slackWebhook.send({
            text: `${txt}`
        });
    } catch (err) {
        var text = "something error happened with slack!";
        console.log(text);
    }
}



// commonly file system utils
async function createFolder(path) {
    try {
        await fs.ensureDir(path);
    } catch (err) {
        var text = path + " (folder) can not be created!";
        console.log(text);
    };
}

async function createFile(path) {
    try {
        await fs.ensureFile(path);
    } catch (err) {
        var text = path + " (file) can not be created!";
        console.log(text);
    };
}

async function writeupFile(path, txt) {
    await createFile(path);
    try {
        await fs.appendFile(path, txt);
    } catch (err) {
        var text = path + " (file) can not be written!";
        console.log(text);
    };
}

async function writeupCSVdata(path, txt) {
    var preTxt;
    var preCsv;
    var csv;
    try {
        preTxt = await fs.readFile(path);
    } catch (err) {
        var text = path + " (file) can not be read!";
        console.log(text);
        preTxt = "";
    };
    preCsv = await parseCSVdataHead(preTxt);
    csv = await parseCSVdataHead(txt);
    if (JSON.stringify(preCsv) == JSON.stringify(csv)) { // check a header, need to developed
        csv = await parseCSVdata(txt);
        csv.shift();
        txt = await buildCSVdata(csv);
    }
    await writeupFile(path, txt);
}



async function createArchive(filePath, archivePath) {
    try {
        tar.create({
            sync: true,
            file: archivePath,
            gzip: '-k -9'
        }, [
            filePath
        ])
    } catch (err) {
        var text = archivePath + " (archive) can not be created!";
        console.log(text);
    };
}


async function removeFileFolder(path) {
    try {
        fs.removeSync(path);
    } catch (err) {
        var text = path + " (file) can not be removed!";
        console.log(text);
    };
}




// initialize
(async() => {
    createFolder(".", "/Logs");
})();




// routine definition
const scheduleSendMessage = scheduler.scheduleJob("0-59 * * * * *", function() {
    var tgt;
    var txt;
    var flg;
    (async() => {
        tgt = await accessJSON();
        [flg, txt] = await makeMessage(tgt);
        await sendMessage(flg, txt);
    })();
});



const scheduleSendLogging = scheduler.scheduleJob("0 0 */1 * * * ", function() {
    var tgt;
    var txt;
    var flg;
    (async() => {
        tgt = await accessJSON();
        [flg, txt] = await makeMessage(tgt);
        await sendLogging(flg, txt);
    })();
});



const scheduleLogging = scheduler.scheduleJob("0,10,20,30,40,50 * * * * *", function() {
    var date = datetime.format(new Date(), 'YYYY_MM_DD');
    var path = "./Logs/" + date + ".csv"
    var tgt;
    var txt;
    var flg;
    (async() => {
        tgt = await accessJSON();
        [flg, txt] = await makeCSVtext(tgt);
        await writeupCSVdata(path, txt);
    })();
});



const manageLogfiles = scheduler.scheduleJob("0 5 0 */1 * *", function() {
    var cur = new Date();
    var old = datetime.addDays(cur, -1);
    var curDate = datetime.format(cur, 'YYYY_MM_DD');
    var oldDate = datetime.format(old, 'YYYY_MM_DD');
    var curPath = "./Logs/" + curDate + ".csv";
    var oldPath = "./Logs/" + oldDate + ".csv";
    var curGzip = "./Logs/" + curDate + ".tar.gz";
    var oldGzip = "./Logs/" + oldDate + ".tar.gz";
    (async() => {
        await createFile(curPath);
        await createFile(curGzip);
        await createArchive(oldPath, oldGzip);
        await removeFileFolder(oldPath);
    })();
    console.log(datetime.format(new Date(), 'YYYY/MM/DD HH:mm:ss:SSS'));
    var text = "logging files are regularLy updated!";
    console.log(text);
});
```