저의 경우 .env
에 데이터베이스 계정 정보를 적어놓고 프로젝트 내에서 불러오는 식으로 구현했습니다. 저번장에서 dotenv를 설치했으니 불러와야겠죠.
.env
파일을 아래와 같은 형식으로 작성합니다.
INTERVAL=3
HOST='[db 호스트 ip]'
USER='[db에 접속할 계정 명]'
password='[비밀번호]'
database='[데이터베이스 이름]'
INTERVAL은 몇 초 간격으로 데이터를 저장할지 결정하는 환경변수입니다. 저는 3초로 두었습니다.
require('dotenv').config();
const INTERVAL = process.env.INTERVAL;
const HOST = process.env.HOST;
const USER = process.env.USER;
const PASSWORD = process.env.PASSWORD;
const DATABASE = process.env.DATABASE;
app.js
에서 위와 같이 불러올 수 있습니다.
이제 mariadb에 접속할 모듈을 불러와야겠죠. 저는 pool을 지원하는 mysql2를 이용해 js로 db에 접근하겠습니다.
npm install mysql2
아래의 줄을 추가해 pool을 만들고 쓸 수 있습니다.
const { createPool } = require('mysql2/promise');
pool과 connection을 만들어 반환해줄 함수를 만듧시다.
const makeConnectList = async (option) => {
const connectionList = [];
const pool = createPool({
host:option.host,
user:option.user,
password:option.password,
database:option.database,
limit:option.limit
})
for(let i=0;i<option.limit;i++)
connectionList.push(await pool.getConnection(async conn=>conn))
return connectionList;
}
다음으로 파일을 읽고 객체에 저장해줄 함수가 필요합니다.
/*
asyncProcFilesRead
파일 경로로부터 파일을 읽고 내용을 반환한다.
*/
const asyncProcFilesRead = async () => {
const filePathList = [
"/proc/uptime", // uptime 정보
"/sys/class/hwmon/hwmon0/temp1_input", // cpu 온도 정보
"/proc/loadavg", // loadavg 1분 5분 15분 정보
"/proc/stat", // cpu 사용률 정보
"/proc/meminfo", // 메모리 정보
"/proc/diskstats", // 디스크 정보
"/proc/net/dev" // 네트워크 정보
];
const filePromiseList = filePathList.map(path=>{
return fs.readFile(path,{ encoding:'utf-8'})
})
const fileContentList = await Promise.all(filePromiseList)
return {
uptime: fileContentList[0],
cpuThermal: fileContentList[1],
loadavg: fileContentList[2],
cpu: fileContentList[3],
mem: fileContentList[4],
disk: fileContentList[5],
net: fileContentList[6],
}
}
위 asyncProcFilesRead
함수로 각 파일의 정보를 읽어 객체로 저장합니다.
그 다음 이 정보를 파싱해 저장할 객체를 만들어줘야하는데, uptime
,cpu_thermal
,loadavg
,cpu
,mem
,disk
,net
정보를 저장할 각 객체들을 uptimeObj
,cpuThermalObj
,loadavgObj
,cpuObj
,memObj
,diskObj
,netObj
라고 하겠습니다.
모두 올리기엔 소스코드 거의 전문을 올려야할테니 netObj
만 간단히 보겠습니다.
/*net 관리 객체 */
const netObj = {
init(content) {
try {
const lines = content.split('\n');
const values = lines.map(line=>line.replace(/\s+/g, ' ').trim().split(' '));
values.forEach(stats => {
const netName = stats[0];
switch(netName) {
case 'eth0:': {
const receiveBytes = parseInt(stats[1]);
const receiveErrBytes = parseInt(stats[3]);
const transmitBytes = parseInt(stats[9]);
const transmitErrBytes = parseInt(stats[11]);
this.netReceive = (receiveBytes-this.beforeNetReceive)*8/INTERVAL/1024;
this.netTransmit = (transmitBytes-this.beforeNetTransmit)*8/INTERVAL/1024;
this.netReceiveErr = (receiveErrBytes-this.beforeNetReceiveErr)*8/INTERVAL/1024;
this.netTransmitErr = (transmitErrBytes-this.beforeNetTransmitErr)*8/INTERVAL/1024;
this.beforeNetReceive = receiveBytes;
this.beforeNetTransmit = transmitBytes;
this.beforeNetReceiveErr = receiveErrBytes;
this.beforeNetTransmitErr = transmitErrBytes;
break;
}
}
})
this.isInit = true;
}
catch(err) {
console.log(err)
this.isInit = false;
}
},
isInit: false,
beforeNetReceive: 0,
beforeNetTransmit: 0,
beforeNetReceiveErr: 0,
beforeNetTransmitErr: 0,
netReceive: 0,
netTransmit: 0,
netReceiveErr: 0,
netTransmitErr: 0
}
netReceive 등의 정보를 어떻게 계산하는지는 이전 포스트에서 설명했으니, init
메소드가 값을 받고 객체에 저장한다는 점만 기억하면 될 것 같습니다. isInit
은 읽고 파싱하는데 성공했는지 저장하는 값입니다.
asyncProcFilesRead
로 읽어온 값을 netObj.init
을 통해 파싱하고, netObj를 통해 값을 불러오면 되겠네요.
그 다음 각 테이블에 INSERT할 때 필요한 쿼리를 정의합니다. 마찬가지로 netObj용 INSERT 쿼리만 봅시다.
const networkInsertQuery = `INSERT INTO network_status(
date,
net_receive,
net_transmit,
net_receive_err,
net_transmit_err
) VALUES (
?,?,?,?,?
)`;
이제 setInterval
로 순회하며
위의 세 가지 과정을 반복하면 되겠군요.
const connectionList = await makeConnectList(dbOptions);// connection 가져오기
const intervalHandler = setInterval(async () => {
const err = await getValuesFromFileToObjs(objs)
if(err){
throw err;
}
const promiseList = [];
const system_time = moment(Date.now()).format('YYYY-MM-DD HH:mm:ss'); // 현재 시간 string으로 저장(moment 라이브러리 이용)
connectionList.forEach(connection=>connection.beginTransaction()); // 각 connection에 쿼리를 보낼 준비
...
//network_status insert
promiseList.push(connectionList[3].query(networkInsertQuery,[
system_time,
objs.netObj.netReceive.toFixed(1),
objs.netObj.netTransmit.toFixed(1),
objs.netObj.netReceiveErr.toFixed(1),
objs.netObj.netTransmitErr.toFixed(1),
]));
...
await Promise.all(promiseList) // query문이 전부 실행되기를 기다림
const commitList = connectionList.map(connection=>{
return connection.commit();
})
await Promise.all(commitList) // 모든 commit이 완료되기를 기다림
프로세스가 종료될때 까지 3초간격으로 계속 시스템 정보를 저장하면 용량이 너무 커지니, 우선 1주일치만 저장하도록 만들어줍시다.
먼저 각 테이블의 튜플 개수를 새줍니다.
const tableList = ['cpu_status','memory_status','io_status','network_status','summary_status'];
//각 table별 tuple 개수 새기
promiseList.splice(0); // promiseList 배열 비우기
tableList.forEach((tableName,index)=>{
const query = `SELECT COUNT(*) AS cnt FROM ${tableName}`;//원소 개수를 세는 쿼리
promiseList.push(connectionList[0].query(query))
})
const countList = await Promise.all(promiseList);
이제 각 테이블의 튜플 개수가 7 * 24 * 60 * 60 /3
개를 넘어가면(1주일간 3초간 저장한 경우 튜플의 수) 가장 오래된 튜플부터 지웁시다.
//tuple이 15*60/3개(15분 치)가 넘어가면 가장 오래된 tuple부터 지움
const amountOfWeekTuple = 15*60/3;
promiseList.splice(0);
tableList.forEach((tableName,index)=>{
let [row,field] = countList[index][0];
const cnt = row['cnt'];
if(cnt>amountOfWeekTuple) {
const deleteLatestQuery = `DELETE FROM ${tableName} ORDER BY date limit ${cnt-amountOfWeekTuple}`;
promiseList.push(connectionList[0].query(deleteLatestQuery));
}
})
await Promise.all(promiseList);
const deleteOldTupleCommitList = connectionList.map(connection=>{
connection.commit();
});
await Promise.all(deleteOldTupleCommitList);//commit 실행
connectionList.forEach(connection=>{
connection.release();// 사용한 connection 반납
})
이제 15분 정도 돌려보고 잘 되는지 봅시다.