[프로젝트] raspi-monitor | DB에 값 저장하기

dev2820·2021년 10월 9일
0

dotenv

저의 경우 .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에서 위와 같이 불러올 수 있습니다.

db에 쿼리보내기

이제 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로 순회하며

  1. 값을 읽는다.
  2. 값을 파싱한다.(init)
  3. 값을 db에 INSERT 한다.

위의 세 가지 과정을 반복하면 되겠군요.

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이 완료되기를 기다림

table의 tuple을 일정 개수만 유지하기

프로세스가 종료될때 까지 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분 정도 돌려보고 잘 되는지 봅시다.

profile
공부,번역하고 정리하는 곳

0개의 댓글