본문 바로가기
[CS] 컴퓨터 공학/CS 수업 정리

[CS] 자료구조 및 시스템 프로그래밍 TOP , disk I/O diskstats 구현

by pjhcsol 2022. 12. 11.

gcc -o han han.c -lcurses 

 

1.top

/proc/stat
/var/run/utmp(user 수)
/proc/uptime
/proc/loadavg
/proc/meminfo(memory)
/proc(task)

2.DISK I/O

 

/proc/diskstats

 

cat을 사용하여 출력 가능 ex)cat /proc/diskstats

cpu 사용량과, memory 사용량, 관찰 하고싶은 system resource를 실시간으로 monitoring

하여 적재적소에 맞게끔 활용하는 것을 중점

 

top(cpu,memory,uptime,user,task)

disk I/O(diskstats)

 

즉,

top에서의 cpu 사용량과 memory 사용량 그리고 cat /proc/diskstats(ram0~15,sda,sdb,sdc)

디스크IO와 사용량을 체크 하는 프로그램

 

1. static 변수에는 기본적으로 fp를 하였을 때 파일이 제대로 열렸는지 체크하는 기능을 넣음

->디버깅 하기 좋음

2. 통계는 파일을 읽어서 배열에 저장하는 방식을 사용함.

 

3. main문에는 initscr과 출력 위주로 구현 하였고 main 밖에는 static 변수로 계산을 보기 편하게 구성하였다.

 

1. top구현(cpu,memory,uptime,user,task)

cpu,memory,uptime,user,task 를 구성하는 

디렉토리가 다르고 위치가 달라서 각각 fopen을 하여 원하는 정보 int or double fgetc 하여 가져옴.

2. disk I/O(diskstats)

cat /proc/diskstats 에서

sscanf 로 원하는 문자열을 뽑아 구성하였다.

저장된 장소

char TOP[1024];

char USER[1024];
char LOAD_average[1024];
char cuptime[1024];
char cuptime1[1024];
char cuptime2[1024];
char TASK[1024]; // task
char CPU[1024];
char Mem[1024];
char Swap[1024];
char DISK[1024];
char DISK1[1024];
char DISK2[1024];
char DISK3[10240][10240]; //full인 경우 //Segmentation fault

DISK 구현에서 static int DDISK(void) 를 사용할때 안의 문자열을 sprintf로 저장하는데

기존 addstr(char)sprintf 한줄 저장으로 해결이 불가능하여 2차원 배열 선언,

mainDDISK에서 while문으로 disk3에 저장하였다

 

main에서의 while문은 실제로 덮어쓰면서 최신화가 되기 때문에 ram0~부터 sdc까지 계속 출력 될 것이다.

그렇다면 sleep(?)초에 따라 계속 최신 data가 추가됨에 따라 길어짐으로 한 화면에서 보기 불편해진다.

 

따라서,

memset(DISK3, 0, sizeof(char) * 10240 * 10240);//사이즈 지정

을 사용하여 한 화면(배열,한 메모리)으로 덮어쓰게 된다.

 

void* memset(void* _Dst, int _Val, size_t _Size);
* _Dst : 세팅하고자 하는 시작 주소
* _Val : 세팅하고자 하는 (int로 받지만 unsigned char로 변환되어 사용됨)
* _Size : 길이(배열의 크기* sizeof(데이터 타입)) 반환 값 : 성공 시 _Dst 반환, 실패 시 NULL 반환

 

 

memset은 프로그래밍 수준의 "배열"이 아닌 하드웨어 수준의 "메모리"를 덮어쓰는 함수이다.

따라서 메모리는 한 바이트 단위로 사용자가 입력한 값으로 초기화된다.

 

TOP과 DISK I/O의 실시간 모니터링 구현

sleep을 걸어 원할때에 최신화시킬지 조절이 가능하다. sleep(1)..(2)...

//gcc -o han han.c -lcurses  //1.top 2.cat /proc/diskstats __ 박한솔
#include <stdio.h> 
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/utsname.h>
#include <ctype.h>
#include <fcntl.h>
#include <curses.h>
#include <dirent.h>
#include <time.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <signal.h>
#include <pwd.h>
#include <sys/param.h>
#include <sys/time.h>
#include <libgen.h>// disk
#include <sys/vfs.h>
#include <sys/types.h>
#define Ms_SIZE 64


struct cpui_nt
{
    unsigned int user;
    unsigned int nice;
    unsigned int system;
    unsigned int idle;
    unsigned int iowait;
    unsigned int irq;
    unsigned int softirq;
    unsigned int steal;
    unsigned int guest;
};
//static int memory(void);
static int M_Swap(void);
static int Uptime_top(void);
int task_su(char *path); // task
void Task_Pr();
int cpu_statld(struct cpui_nt *here); // cpu
void cpu_p(struct cpui_nt *prev, struct cpui_nt *cur);

// disk
const char *P_MOUNT = "/proc/mounts";      // mount
const char *DISKSTATs = "/proc/diskstats"; // disk
const char *PartitionsS = "/proc/partitions";
static int DDISK(void);
struct infosize_b{
    long blocks;
    long used;
    long avail;
};
typedef struct Mountconfo{
    FILE *fp;
    char devname[80];
    char mountdir[80];
    char fstype[12];
    struct infosize_b size;
} P_MNT;
// disk

typedef struct Diskcontent{// disk content
    FILE *fp;
    char diskname[80]; // name
    char throw9[50];
    long read_c;// read
    long read_some;// some read
    long read_tot;// total
    long read_tal;// read total
    long wr_c;// complete
    long wr_c1;
    long wr_c2;
    long cprc1;
    long com1;
    long mil1t;
} STAT_D;

//partition
typedef struct _partinfo{
    FILE *fp;
    char partname[80];
    char throw9[50];
} P_PART_T;
//

//top cpu,memory,user,uptime,mem,swap
char TOP[1024];
char USER[1024];
char LOAD_average[1024];
char cuptime[1024];
char cuptime1[1024];
char cuptime2[1024];
char TASK[1024]; // task
char CPU[1024];
char Mem[1024];
char Swap[1024];
char DISK[1024];
char DISK1[1024];
char DISK2[1024];
char DISK3[10240][10240]; //full인 경우 //Segmentation fault
int disk_cnt = 0;

int main(){
    struct cpui_nt a;
    struct cpui_nt b;
    struct utsname un;
    if (uname(&un) >= 0){
        fprintf(stdout, "RAM on name \"%s\", %s %s :\n", un.nodename, un.sysname, un.release);
    }
    while (1){
        initscr();
        clear();
        //구조체 함수
        //memory(); // total
        Task_Pr();
        cpu_statld(&a);
        sleep(1);//시간 두기
        cpu_statld(&b);
        cpu_p(&a, &b);
        M_Swap();
        Uptime_top();
        DDISK();

        // print str 출력
        addstr(TOP); // uptime()
        addstr(cuptime2); // uptime()
        addstr(cuptime1); // uptime()
        addstr(cuptime);  // uptime()
        addstr(USER);         // uptime()
        addstr(LOAD_average); // uptime()
        addstr(TASK); // task
        addstr(CPU);
        //addstr(cmemory); // memory()총량 check
        addstr(Mem);     // M_Swap()
        addstr(Swap);    // M_Swap()
        addstr(DISK);    // disk
        addstr(DISK1);   // disk
        addstr(DISK2);   // disk
        for (int i = 0; i < disk_cnt; i++){//<- ,i<disk_cnt; 넣으면 원횟수만큼 반복 
            addstr(DISK3[i]); // disk
        }
        memset(DISK3, 0, sizeof(char) * 10240 * 10240);//사이즈 지정
        refresh();
        sleep(1);
        clear();
    }
    return 0;
}

static int Uptime_top(void){// uptime-top구현
    struct tm *buff_Ty;
    FILE *fp;
    char token[Ms_SIZE];
    double suq1, suq2, suq3;
    int a, i;
    time_t now, t;

    if ((fp = fopen("/proc/uptime", "r")) == NULL){
        fprintf(stderr, "uptime open error\n");
        exit(1);
    }
    i = 0;
    a = fgetc(fp);
    while (a >= '0' && a <= '9'){
        token[i++] = a;
        a = fgetc(fp);
    }
    token[i] = '\0';
    fclose(fp);
    now = time(NULL);
    buff_Ty = localtime(&now);
    sprintf(TOP, "top - %02d:%02d:%02d ", buff_Ty->tm_hour, buff_Ty->tm_min, buff_Ty->tm_sec);
    t = atoi(token);
    if (t < 3600)
        sprintf(cuptime, "up %-2ld min, ", t / 60);
    else if (t < 86400)
        sprintf(cuptime1, "up %ld:%02ld, ", t / 3600, (t % 3600) / 60);
    else
        sprintf(cuptime2, "up %ld days, ", t / 86400);

    // utmp파일 오픈//user 접속
    if ((fp = fopen("/var/run/utmp", "r")) == NULL){
        fprintf(stderr, "utmp open error\n");
        exit(1);
    }
    i = 0;
    while (!feof(fp)){
        a = fgetc(fp);
        if (a == ':'){
            while ((a = fgetc(fp)) != ':')
                ;
            i++;
        }
    }
    fclose(fp);
    sprintf(USER, " %d user, ", i);
    if ((fp = fopen("/proc/loadavg", "r")) == NULL){
        fprintf(stderr, "loadavg open error\n");
        exit(1);
    }
    fscanf(fp, "%lf %lf %lf", &suq1, &suq2, &suq3);
    fclose(fp);
    sprintf(LOAD_average, "load average: %.2f, %.2f, %.2f\n", suq1, suq2, suq3);
}

// cpu
void cpu_p(struct cpui_nt *prev, struct cpui_nt *cur){
    unsigned int S_prev = 0;
    unsigned int preg = 0;
    unsigned int que = 0;

    S_prev = prev->user + prev->nice + prev->system + prev->idle + prev->iowait;
    preg = cur->user + cur->nice + cur->system + cur->idle + cur->iowait;
    que = preg - S_prev;
    double us = cur->user - prev->user;
    double ni = cur->nice - prev->nice;
    double sy = cur->system - prev->system;
    double id = cur->idle - prev->idle;
    double wa = cur->iowait - prev->iowait;
    double hi = cur->irq - prev->irq;
    double si = cur->softirq - prev->softirq;
    double st = cur->steal - prev->steal;

    sprintf(CPU, "%%Cpu(s): %.1lf us, %.1f sy, %.1lf ni, %.1lf id, %.1lf wa, %.1lf hi, %.1lf si, %.1lf st\n", us * 100 / que, sy * 100 / que, ni * 100 / que, id * 100 / que, wa * 100 / que, hi * 100 / que, si * 100 / que, st * 100 / que);
}

int cpu_statld(struct cpui_nt *here)
{
    int qw = 0;
    char blink[1024];
    FILE *fp;
    int len;

    if ((fp = fopen("/proc/stat", "r")) == 0)
    {
        printf("Cannot open : /proc/stat\n");
        exit(1);
    }

    if (fgets(blink, 1024, fp) == 0)
    {
        printf("Cannot read from : /proc/stat\n");
        exit(1);
    }

    len = strlen(blink);
    blink[-len] = 0; //—len

    qw = sscanf(blink, "%*s %u %u %u %u %u %u %u %u %u", &(here->user), &(here->nice), &(here->system), &(here->idle), &(here->iowait), &(here->irq), &(here->softirq), &(here->steal), &(here->guest));

    if (qw != 9){
        printf("Cannot parsing from : /proc/stat\n");
        exit(1);
    }
    return 0;
}
static int M_Swap(void){//Mem,Swap
    char gene[20];
    char gene2[10];
    long double gene3 = 0;
    long double m_gen[8];int i;
    FILE *fp;
    if ((fp = fopen("/proc/meminfo", "r")) == NULL){
        fprintf(stderr, "meminfo open error\n");
        exit(1);
    }
    for (i = 0; i < 24; i++){
        fscanf(fp, "%s %Le %s\n", gene, &gene3, gene2);
        if (i < 5)
            m_gen[i] = (long double)gene3 / 1024;
        else if (i == 14)
            m_gen[5] = (long double)gene3 / 1024;
        else if (i == 15)
            m_gen[6] = (long double)gene3 / 1024;
        else if (i == 23)
            m_gen[7] = (long double)gene3 / 1024;
    }
    gene3 = m_gen[3] + m_gen[4] + m_gen[7];
    sprintf(Mem, "KiB Mem : %8.1Lf total %8.1Lf free %8.1Lf used %8.1Lf buff/cache\n", m_gen[0], m_gen[1], (m_gen[0] - m_gen[1] - gene3), gene3);
    sprintf(Swap, "KiB Swap: %8.1Lf total %8.1Lf free %8.1Lf used %8.1Lf avail Mem\n", m_gen[5], m_gen[6], (m_gen[5] - m_gen[6]), m_gen[2]);
    fclose(fp);
}
// task
void Task_Pr(){
    DIR *R_stat;
    struct dirent *dt_buff;
    struct stat statbuff;
    char OLiadd[1024];
    int status[5];
    int oliq;
    for (int i = 0; i < 5; i++)
        status[i] = 0;

    if ((R_stat = opendir("/proc")) == NULL)
    {
        printf("Cannot open /prlc");
        exit(1);
    }
    while ((dt_buff = readdir(R_stat)) != NULL)
    {
        lstat(dt_buff->d_name, &statbuff);
        if (!S_ISDIR(statbuff.st_mode))
            continue;
        if ((oliq = atoi(dt_buff->d_name)) <= 0)
            continue;
        status[0]++;
        sprintf(OLiadd, "/proc/%d/status", oliq);
        status[task_su(OLiadd)]++;
    }

    sprintf(TASK, "Task : %5d, %5d running, %5d sleeping, %5d stopped, %5d zombie\n", status[0], status[1], status[2], status[3], status[4]);
}

int task_su(char *path){
    FILE *fp;
    char S_tore[1024];char S_len[512];char type[512];
    int qw,len = -1;
    if ((fp = fopen(path, "r")) == NULL){
        // printf("Cannot open %s\n", path);
        // 잘못된 oliq 값이 전달되었을 때 처리
        return -1;
    }
    while (fgets(S_tore, 1024, fp) != NULL)
    {
        sscanf(S_tore, "%s %s", S_len, type);
        len = strlen(S_len);
        S_len[len - 1] = '\0';
        if (strcmp(S_len, "State") == 0)
        {
            if (strcmp(type, "R") == 0)
                qw = 1;
            else if (strcmp(type, "S") == 0)
                qw = 2;
            else if (strcmp(type, "T") == 0)
                qw = 3;
            else if (strcmp(type, "Z") == 0)
                qw = 4;
            break;
        }
    }
    fclose(fp);
    return qw;
}

// disk
P_MNT *Fopen_MNT(){ 
    P_MNT *O_MNTP;
    O_MNTP = (P_MNT *)malloc(sizeof(P_MNT));
    if (!(O_MNTP->fp = fopen(P_MOUNT, "r"))){
        return NULL;
    }
    else
        return O_MNTP;
}
P_PART_T *Fopen_PART(){
    P_PART_T *Prac;
    Prac = (P_PART_T *)malloc(sizeof(P_PART_T));
    if (!(Prac->fp = fopen(PartitionsS, "r"))){
        return NULL;
    }
    else
        return Prac;
}
STAT_D *Fopen_DISK(){
    STAT_D *O_Disk;
    O_Disk = (STAT_D *)malloc(sizeof(STAT_D));
    if (!(O_Disk->fp = fopen(DISKSTATs, "r"))){
        return NULL;
    }
    else
        return O_Disk;
}
P_MNT *GET_Dd(P_MNT *O_MNTP){
    char blibuff[256];
    int is_ID = 0;
    struct statfs flstat;
    struct stat stat_q;
    while (fgets(blibuff, 255, O_MNTP->fp)){
        is_ID = 0;
        sscanf(blibuff, "%s%s%s", O_MNTP->devname, O_MNTP->mountdir, O_MNTP->fstype);
        if (strcmp(O_MNTP->mountdir, "/") == 0)
            is_ID = 1;
        if (stat(O_MNTP->devname, &stat_q) == 0 || is_ID){
            if (strstr(blibuff, O_MNTP->mountdir) && S_ISBLK(stat_q.st_mode) || is_ID){
                statfs(O_MNTP->mountdir, &flstat);
                O_MNTP->size.blocks = flstat.f_blocks * (flstat.f_bsize / 1024) / 1024;
                O_MNTP->size.avail = flstat.f_bavail * (flstat.f_bsize / 1024) / 1024;
                O_MNTP->size.used = O_MNTP->size.blocks - O_MNTP->size.avail;
                return O_MNTP;//mb ,used할당 사용
            }
        }
    }
    rewind(O_MNTP->fp);
    return NULL;
}

STAT_D *Gge_Disk(STAT_D *O_Disk){
    char bli[1024];
    while (fgets(bli, 1023, O_Disk->fp)){
        sscanf(bli, "%s%s%s%lu%lu%lu%lu%lu%lu%lu%lu%lu%lu", O_Disk->throw9, O_Disk->throw9, O_Disk->diskname, &O_Disk->read_c, &O_Disk->read_some, &O_Disk->read_tot, &O_Disk->read_tal, &O_Disk->wr_c, &O_Disk->wr_c1, &O_Disk->wr_c2, &O_Disk->cprc1, &O_Disk->com1, &O_Disk->mil1t);
        return O_Disk;
    }
    rewind(O_Disk->fp);
    return NULL;
}
static int DDISK(void){
    P_MNT *O_MNTP;
    STAT_D *O_Disk;
    P_PART_T *Prac;
    char qbuffer[1024];
    if ((O_MNTP = Fopen_MNT()) == NULL || (O_Disk = Fopen_DISK()) == NULL || (Prac = Fopen_PART()) == NULL){
        perror("error");
        return 1;
    }
    sprintf(DISK, "=======================[ DISK USAGE INFO ]====================== \n");//diskusage
    while (GET_Dd(O_MNTP)){
        sprintf(DISK1, "%-1s%-14s%10lu MB %10lu MB , Usage : %6.2f %% \n", O_MNTP->mountdir, O_MNTP->devname, O_MNTP->size.blocks, O_MNTP->size.avail, (float)(O_MNTP->size.used * 100 / O_MNTP->size.blocks));
    }//-14-30

    sprintf(DISK2, "========================[ DISK I/O INFO ]======================== \n");//diskI/O
    while (fgets(qbuffer, 1023, Prac->fp)){
        sscanf(qbuffer, "%s%s%s%s", Prac->throw9, Prac->throw9, Prac->throw9, Prac->partname);

        while (Gge_Disk(O_Disk)){
            if (strcmp(O_Disk->diskname, Prac->partname) == 0){
                sprintf(DISK3[disk_cnt++], " %-10s %13lu %13lu %13lu %13lu %13lu %13lu %13lu %13lu %13lu %13lu \n", O_Disk->diskname, O_Disk->read_c, O_Disk->read_some, O_Disk->read_tot, O_Disk->read_tal, O_Disk->wr_c, O_Disk->wr_c1, O_Disk->wr_c2, O_Disk->cprc1, O_Disk->com1, O_Disk->mil1t);
                // printf("1\n");
            }
            // printf("2\n");
        }
    }
    rewind(Prac->fp);
}

 

TOP과 DISK I/O의 실시간 모니터링 구현

스크롤을 구현하여 적용한다면 최신화와 이전 것 둘다 볼수 있도록 설계하였다.

 

윈도우에 주어진 하위 윈도우가 아이템들을 전부 보여줄 만큼 크지 않다면, 메뉴는 스크롤 된다.

당신이 현재 보여지는 리스트의 마지막 아이템을 보고 있을 때 만약 REQ_DOWN_ITEM 을 보내면, 이것은 REQ_SCR_DLINE 로 번역되어 한 아이템 만큼 메뉴가 스크롤 된다. 당신은 스크롤을 하기 위해 REQ_SCR_ 을 수동적으로 넘겨줘도 된다.

#include <curses.h>                                                                               
#include <menu.h>                                                                                 
                                                                                                  
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))                                                  
#define CTRLD   4                                                                                 
                                                                                                  
char *choices[] = {                                                                               
                        "Choice 1",                                                               
                        "Choice 2",                                                               
                        "Choice 3",                                                               
                        "Choice 4",                                                               
                        "Choice 5",                                                               
                        "Choice 6",                                                               
                        "Choice 7",                                                               
                        "Choice 8",                                                               
                        "Choice 9",                                                               
                        "Choice 10",                                                              
                        "Exit",                                                                   
                        (char *)NULL,                                                             
                  };                                                                              
void print_in_middle(WINDOW *win, int starty, int startx, int width, char *string, chtype color); 
                                                                                                  
int main()                                                                                        
{       ITEM **my_items;                                                                          
        int c;                                                                                    
        MENU *my_menu;                                                                            
        WINDOW *my_menu_win;                                                                      
        int n_choices, i;                                                                         
                                                                                                  
        /* Initialize curses */                                                                   
        initscr();                                                                                
        start_color();                                                                            
        cbreak();                                                                                 
        noecho();                                                                                 
        keypad(stdscr, TRUE);                                                                     
        init_pair(1, COLOR_RED, COLOR_BLACK);                                                     
        init_pair(2, COLOR_CYAN, COLOR_BLACK);                                                    
                                                                                                  
        /* Create items */                                                                        
        n_choices = ARRAY_SIZE(choices);                                                          
        my_items = (ITEM **)calloc(n_choices, sizeof(ITEM *));                                    
        for(i = 0; i < n_choices; ++i)                                                            
                my_items[i] = new_item(choices[i], choices[i]);                                   
                                                                                                  
        /* Crate menu */                                                                          
        my_menu = new_menu((ITEM **)my_items);                                                    
                                                                                                  
        /* Create the window to be associated with the menu */                                    
        my_menu_win = newwin(10, 40, 4, 4);                                                       
        keypad(my_menu_win, TRUE);                                                                
                                                                                                  
        /* Set main window and sub window */                                                      
        set_menu_win(my_menu, my_menu_win);                                                       
        set_menu_sub(my_menu, derwin(my_menu_win, 6, 38, 3, 1));                                  
        set_menu_format(my_menu, 5, 1);                                                           
                                                                                                  
        /* Set menu mark to the string " * " */                                                   
        set_menu_mark(my_menu, " * ");                                                            
                                                                                                  
        /* Print a border around the main window and print a title */                             
        box(my_menu_win, 0, 0);                                                                   
        print_in_middle(my_menu_win, 1, 0, 40, "My Menu", COLOR_PAIR(1));                         
        mvwaddch(my_menu_win, 2, 0, ACS_LTEE);                                                    
        mvwhline(my_menu_win, 2, 1, ACS_HLINE, 38);                                               
        mvwaddch(my_menu_win, 2, 39, ACS_RTEE);                                                   
                                                                                                  
        /* Post the menu */                                                                       
        post_menu(my_menu);                                                                       
        wrefresh(my_menu_win);                                                                    
                                                                                                  
        attron(COLOR_PAIR(2));                                                                    
        mvprintw(LINES - 2, 0, "Use PageUp and PageDown to scoll down or up a page of items");    
        mvprintw(LINES - 1, 0, "Arrow Keys to navigate (F1 to Exit)");                            
        attroff(COLOR_PAIR(2));                                                                   
        refresh();                                                                                
                                                                                                  
        while((c = wgetch(my_menu_win)) != KEY_F(1))                                              
        {       switch(c)                                                                         
                {       case KEY_DOWN:                                                            
                                menu_driver(my_menu, REQ_DOWN_ITEM);                              
                                break;                                                            
                        case KEY_UP:                                                              
                                menu_driver(my_menu, REQ_UP_ITEM);                                
                                break;                                                            
                        case KEY_NPAGE:                                                           
                                menu_driver(my_menu, REQ_SCR_DPAGE);                              
                                break;                                                            
                        case KEY_PPAGE:                                                           
                                menu_driver(my_menu, REQ_SCR_UPAGE);                              
                                break;                                                            
                }                                                                                 
                wrefresh(my_menu_win);                                                            
        }                                                                                         
                                                                                                  
        /* Unpost and free all the memory taken up */                                             
        unpost_menu(my_menu);                                                                     
        free_menu(my_menu);                                                                       
        for(i = 0; i < n_choices; ++i)                                                            
                free_item(my_items[i]);                                                           
        endwin();                                                                                 
}                                                                                                 
                                                                                                  
void print_in_middle(WINDOW *win, int starty, int startx, int width, char *string, chtype color)  
{       int length, x, y;                                                                         
        float temp;                                                                               
                                                                                                  
        if(win == NULL)                                                                           
                win = stdscr;                                                                     
        getyx(win, y, x);                                                                         
        if(startx != 0)                                                                           
                x = startx;                                                                       
        if(starty != 0)                                                                           
                y = starty;                                                                       
        if(width == 0)                                                                            
                width = 80;                                                                       
                                                                                                  
        length = strlen(string);                                                                  
        temp = (width - length)/ 2;                                                               
        x = startx + (int)temp;                                                                   
        wattron(win, color);                                                                      
        mvwprintw(win, y, x, "%s", string);                                                       
        wattroff(win, color);                                                                     
        refresh();                                                                                
}                                                               

 

 

리눅스 시스템 정보 수집에서의 캐싱 기법을 활용한 성능 최적화

1. CPU 정보 캐싱

현재 cpu_statld 함수는 매번 /proc/stat 파일을 열고 데이터를 읽습니다. 동일한 데이터를 반복적으로 읽는 상황이라면, 캐싱을 통해 파일 접근 횟수를 줄일 수 있습니다.

struct cpui_nt cached_cpu;
int is_cpu_cached = 0;

int cpu_statld(struct cpui_nt *here) {
    if (is_cpu_cached) {
        *here = cached_cpu;
        return 0;
    }

    FILE *fp;
    char buffer[1024];
    if ((fp = fopen("/proc/stat", "r")) == NULL) {
        fprintf(stderr, "Cannot open /proc/stat\n");
        exit(1);
    }

    if (fgets(buffer, sizeof(buffer), fp) == NULL) {
        fprintf(stderr, "Cannot read from /proc/stat\n");
        fclose(fp);
        exit(1);
    }

    sscanf(buffer, "%*s %u %u %u %u %u %u %u %u %u",
           &(here->user), &(here->nice), &(here->system),
           &(here->idle), &(here->iowait), &(here->irq),
           &(here->softirq), &(here->steal), &(here->guest));

    cached_cpu = *here;
    is_cpu_cached = 1;

    fclose(fp);
    return 0;
}

// 캐시 무효화 함수
void invalidate_cpu_cache() {
    is_cpu_cached = 0;
}
 

 

  • cpu_statld 함수는 캐시된 데이터를 반환하며, 필요 시 invalidate_cpu_cache()를 호출하여 캐시를 초기화합니다.
  • 1초 간격으로 데이터를 읽는 현재 구조에서는 매번 새로운 데이터를 읽는 대신 캐싱 데이터를 활용해 중복 작업을 줄일 수 있습니다.

2. 메모리 정보 캐싱

M_Swap 함수는 /proc/meminfo 파일을 열어 매번 데이터를 읽습니다. 이 또한 캐싱 기법을 활용하여 최적화할 수 있습니다.

struct meminfo_cached {
    long double m_gen[8];
    int is_cached;
} meminfo_cache = {{0}, 0};

void M_Swap_Cached() {
    if (meminfo_cache.is_cached) {
        sprintf(Mem, "KiB Mem : %8.1Lf total %8.1Lf free %8.1Lf used %8.1Lf buff/cache\n",
                meminfo_cache.m_gen[0], meminfo_cache.m_gen[1],
                (meminfo_cache.m_gen[0] - meminfo_cache.m_gen[1] - (meminfo_cache.m_gen[3] + meminfo_cache.m_gen[4] + meminfo_cache.m_gen[7])),
                (meminfo_cache.m_gen[3] + meminfo_cache.m_gen[4] + meminfo_cache.m_gen[7]));
        sprintf(Swap, "KiB Swap: %8.1Lf total %8.1Lf free %8.1Lf used %8.1Lf avail Mem\n",
                meminfo_cache.m_gen[5], meminfo_cache.m_gen[6],
                (meminfo_cache.m_gen[5] - meminfo_cache.m_gen[6]),
                meminfo_cache.m_gen[2]);
        return;
    }

    FILE *fp = fopen("/proc/meminfo", "r");
    if (fp == NULL) {
        fprintf(stderr, "meminfo open error\n");
        exit(1);
    }

    char key[20];
    long double value;
    char unit[10];
    for (int i = 0; i < 24; i++) {
        fscanf(fp, "%s %Lf %s\n", key, &value, unit);
        if (i < 5)
            meminfo_cache.m_gen[i] = value / 1024.0;
        else if (i == 14)
            meminfo_cache.m_gen[5] = value / 1024.0;
        else if (i == 15)
            meminfo_cache.m_gen[6] = value / 1024.0;
        else if (i == 23)
            meminfo_cache.m_gen[7] = value / 1024.0;
    }

    meminfo_cache.is_cached = 1;
    fclose(fp);
    M_Swap_Cached();
}

// 캐시 무효화 함수
void invalidate_mem_cache() {
    meminfo_cache.is_cached = 0;
}

3. 캐싱 적용 후 결과

  • /proc 파일 시스템은 매번 읽어도 OS 레벨에서 효율적으로 처리되지만, 프로세스 측면에서 반복적인 파일 열기와 읽기는 불필요한 작업입니다.
  • 캐싱을 통해 데이터를 메모리에 저장하고 필요 시 캐시를 초기화하도록 구현하면 I/O 오버헤드를 줄이고 성능을 개선할 수 있습니다.

 

캐싱 기법과 해시맵 사용 개선할 점 요약

 

1. 주제 개요

리눅스에서 시스템 정보를 수집하는 작업은 자주 사용되는 기능입니다. /proc 파일 시스템에서 CPU 사용률, 메모리 상태와 같은 정보를 읽는 작업은 간단하지만, 높은 빈도로 호출될 경우 불필요한 I/O 오버헤드가 발생할 수 있습니다. 본 주제는 캐싱 기법을 활용하여 시스템 정보 수집의 효율성을 높이고, 성능을 최적화하는 방법을 탐구합니다.

 

2. 세부 내용

2.1 CPU 정보 캐싱

  • 문제점: CPU 정보는 /proc/stat 파일을 열고 매번 데이터를 읽어오는 방식으로 수집됩니다. 높은 호출 빈도 시 I/O 부담 증가.
  • 해결 방안: 캐싱 기법을 도입하여 최근 데이터를 메모리에 저장하고, 필요 시 캐시를 갱신하는 방식으로 처리.
  • 구현 요약:
    • 구조체를 사용해 CPU 정보를 캐싱.
    • 캐시 초기화 및 무효화 함수 제공.
    • 캐시된 데이터를 우선 반환하고, 데이터 유효 기간 초과 시 캐시를 갱신.

2.2 메모리 정보 캐싱

  • 문제점: 메모리 정보 수집은 /proc/meminfo를 반복적으로 읽는 구조로, CPU 정보와 마찬가지로 I/O 부담을 초래.
  • 해결 방안: 메모리 정보를 캐싱하고, 필요한 경우에만 데이터를 갱신하도록 설계.
  • 구현 요약:
    • 메모리 정보를 저장할 구조체 정의.
    • 기존 데이터가 유효하면 캐시된 데이터를 반환하고, 그렇지 않을 경우 새로 읽어 캐시 갱신.

2.3 캐시 무효화 메커니즘

  • 캐싱 기법의 신뢰성을 유지하기 위해 데이터 변경이 발생하거나 일정 시간이 경과하면 캐시를 무효화.
  • 사용자가 직접 캐시를 무효화할 수 있는 API 제공.

3. 캐싱 적용 후 성능 개선 분석

  • 테스트 환경:
    • 일정 시간 동안 CPU 및 메모리 정보를 읽는 작업 반복.
    • 캐싱 적용 전후의 I/O 호출 횟수 및 처리 시간을 비교.
  • 결과 기대치:
    • I/O 호출 횟수 감소.
    • 파일 읽기 작업의 평균 응답 시간 단축.
    • CPU 및 메모리 사용률 개선.

4. 기대 효과

  1. 시스템 효율성 개선: 불필요한 파일 I/O 작업 감소로 CPU 리소스 및 I/O 처리량 최적화.
  2. 확장성 향상: 높은 호출 빈도에서도 안정적인 성능 유지.
  3. 응답 시간 단축: 캐시된 데이터를 활용하여 빠른 데이터 반환.

 

 

 

 

캐싱 기법과 해시맵을 활용한 최종 코드

  • top 명령어와 /proc/diskstats를 활용하여 시스템 자원을 효율적으로 관리하며, 알고리즘 최적화로 데이터를 빠르고 안정적으로 처리.
  • 배열을 사용해 수집된 데이터를 순차적으로 저장하고, 리스트와 해시맵을 활용해 동적으로 데이터를 저장하고 빠르게 조회할 수 있는 구조를 구현.
  • 캐싱 기법을 적용하여 중복 계산을 줄이고, 불필요한 연산을 최소화하여 실시간 모니터링 성능을 최적화.

 

//Linux


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <dirent.h>
#include <curses.h>
#include <ctype.h>

#define MAX_PROCESSES 1024
#define HASH_MAP_SIZE 256

// 캐싱 구조체
typedef struct CacheEntry {
    char key[128];
    char value[256];
    struct CacheEntry *next;
} CacheEntry;

// 해시맵
CacheEntry *hashMap[HASH_MAP_SIZE];

// 프로세스 정보를 저장하는 배열
typedef struct ProcessInfo {
    int pid;
    char name[256];
    char state;
} ProcessInfo;

ProcessInfo processArray[MAX_PROCESSES];
int processCount = 0;

// 해시 함수
int hash(const char *key) {
    int hash = 0;
    for (int i = 0; key[i] != '\0'; i++) {
        hash = (hash + key[i]) % HASH_MAP_SIZE;
    }
    return hash;
}

// 해시맵에 추가
void addToHashMap(const char *key, const char *value) {
    int index = hash(key);
    CacheEntry *entry = (CacheEntry *)malloc(sizeof(CacheEntry));
    strcpy(entry->key, key);
    strcpy(entry->value, value);
    entry->next = hashMap[index];
    hashMap[index] = entry;
}

// 해시맵에서 조회
char *getFromHashMap(const char *key) {
    int index = hash(key);
    CacheEntry *entry = hashMap[index];
    while (entry) {
        if (strcmp(entry->key, key) == 0) {
            return entry->value;
        }
        entry = entry->next;
    }
    return NULL;
}

// 프로세스 정보 수집
void collectProcesses() {
    DIR *dir = opendir("/proc");
    struct dirent *entry;
    processCount = 0;

    while ((entry = readdir(dir)) != NULL) {
        if (isdigit(entry->d_name[0])) {
            char path[256];
            sprintf(path, "/proc/%s/stat", entry->d_name);
            FILE *fp = fopen(path, "r");
            if (fp) {
                ProcessInfo process;
                fscanf(fp, "%d %s %c", &process.pid, process.name, &process.state);
                fclose(fp);

                processArray[processCount++] = process;
                if (processCount >= MAX_PROCESSES) {
                    break;
                }
            }
        }
    }

    closedir(dir);
}

// CPU 사용률 수집
void collectCPUUsage(char *buffer) {
    static char prevBuffer[256] = "";
    if (strlen(prevBuffer) > 0) {
        strcpy(buffer, prevBuffer);
        return;
    }

    FILE *fp = fopen("/proc/stat", "r");
    if (fp) {
        fgets(buffer, 256, fp);
        fclose(fp);
        strcpy(prevBuffer, buffer); // 캐싱
    } else {
        strcpy(buffer, "N/A");
    }
}

// 메모리 사용량 수집
void collectMemoryUsage(char *buffer) {
    char *cached = getFromHashMap("memory_usage");
    if (cached) {
        strcpy(buffer, cached);
        return;
    }

    FILE *fp = fopen("/proc/meminfo", "r");
    if (fp) {
        char line[256];
        long total, free;
        fgets(line, sizeof(line), fp);
        sscanf(line, "MemTotal: %ld kB", &total);
        fgets(line, sizeof(line), fp);
        sscanf(line, "MemFree: %ld kB", &free);
        fclose(fp);

        sprintf(buffer, "Total: %ld kB, Free: %ld kB", total, free);
        addToHashMap("memory_usage", buffer); // 캐싱
    } else {
        strcpy(buffer, "N/A");
    }
}

// 디스크 사용량 수집
/*
void collectDiskUsage(char *buffer) {
    FILE *fp = popen("df -h", "r");
    if (fp) {
        char line[256];
        char tempBuffer[1024] = "=======================[ DISK USAGE INFO ]======================\n";
        while (fgets(line, sizeof(line), fp)) {
            strcat(tempBuffer, line);
        }
        pclose(fp);
        strcpy(buffer, tempBuffer);
    } else {
        strcpy(buffer, "N/A");
    }
}
*/

// 디스크 사용량 수집 (df 대신 /proc/diskstats 사용)
void collectDiskUsage(char *buffer) {
    FILE *fp = fopen("/proc/diskstats", "r");
    if (fp) {
        char line[256];
        char tempBuffer[1024] = "=======================[ DISK I/O STATS ]======================\n";
        
        while (fgets(line, sizeof(line), fp)) {
            unsigned long long read_ios, read_merges, read_sectors, read_ticks;
            unsigned long long write_ios, write_merges, write_sectors, write_ticks;
            unsigned long long in_flight, io_ticks, time_in_queue;
            char device[32];

            // /proc/diskstats에서 각 디스크의 I/O 통계를 읽음
            int fields = sscanf(line,
                "%*d %*d %s %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu",
                device, &read_ios, &read_merges, &read_sectors, &read_ticks,
                &write_ios, &write_merges, &write_sectors, &write_ticks,
                &in_flight, &io_ticks, &time_in_queue);

            if (fields == 11) {
                // 읽은 디스크 정보 추가
                char diskInfo[256];
                snprintf(diskInfo, sizeof(diskInfo), "%-10s | Read I/O: %-10llu | Write I/O: %-10llu",
                         device, read_ios, write_ios);
                strcat(tempBuffer, diskInfo);
                strcat(tempBuffer, "\n");
            }
        }
        fclose(fp);
        strcpy(buffer, tempBuffer);
    } else {
        strcpy(buffer, "N/A");
    }
}



// 화면 갱신
void updateDisplay() {
    clear();
    mvprintw(0, 0, "==================[ Real-Time System Monitoring ]==================");

    // 프로세스 정보 출력
    mvprintw(2, 0, "Processes:");
    for (int i = 0; i < processCount; i++) {
        mvprintw(3 + i, 0, "PID: %-5d | Name: %-20s | State: %c",
                 processArray[i].pid, processArray[i].name, processArray[i].state);
    }

    // CPU 정보 출력
    char cpuBuffer[256];
    collectCPUUsage(cpuBuffer);
    mvprintw(10, 0, "=======================[ CPU USAGE INFO ]======================");
    mvprintw(11, 0, "%s", cpuBuffer);

    // 메모리 정보 출력
    char memoryBuffer[256];
    collectMemoryUsage(memoryBuffer);
    mvprintw(13, 0, "=====================[ MEMORY USAGE INFO ]=====================");
    mvprintw(14, 0, "%s", memoryBuffer);

    // 디스크 정보 출력
    char diskBuffer[1024];
    collectDiskUsage(diskBuffer);
    mvprintw(16, 0, "%s", diskBuffer);

    refresh();
}

int main() {
    initscr();

    while (1) {
        collectProcesses();
        updateDisplay();
        sleep(1);
    }

    endwin();
    return 0;
}

 

1. 캐싱 시스템 추가

  • 기존 코드에서는 CPU, 메모리, 디스크 등의 리소스 정보를 실시간으로 계속해서 조회했습니다. 이로 인해 자주 호출되는 데이터에 대해 불필요하게 I/O 작업이 반복될 수 있었습니다.
  • 개선된 코드에서는 캐싱 시스템을 도입하여, 이미 수집한 정보를 해시맵에 저장하고, 같은 정보가 필요할 경우 캐시된 값을 바로 사용하는 방식으로 성능을 최적화했습니다. 특히 메모리 사용량 같은 경우 캐시를 활용해 불필요한 I/O를 줄였습니다.

2. 해시맵을 통한 리소스 관리

  • 해시맵은 캐시된 데이터를 효율적으로 저장하고 조회할 수 있도록 하여, 데이터 조회 성능을 향상시켰습니다.
  • 예를 들어, 메모리 사용량을 getFromHashMap() 함수로 캐시된 값을 확인하고, 없으면 수집하여 캐시에 저장하는 방식으로 리소스를 관리했습니다.

3. 프로세스 정보 수집

  • 기존 코드에서는 /proc/[pid]/stat 파일을 읽어 프로세스 정보를 수집했으며, 모든 프로세스를 순차적으로 읽어들였습니다.
  • 개선된 코드에서는 프로세스 정보를 collectProcesses() 함수에서 /proc 디렉터리의 파일들을 읽어오고, 이를 processArray 배열에 저장하여 화면에 출력합니다. 이 방식은 실제 시스템에 있는 프로세스 수에 맞게 동적으로 처리됩니다.

4. 디스크 사용량 수집 방식 변경

  • 개선된 코드에서는 /proc/diskstats파일을 사용하여 디스크 사용량을 직접 읽어오는 방식으로 변경되었습니다. 이를 통해 디스크 I/O 통계를 더 세밀하게 수집하고, 시스템 성능에 미치는 영향을 최소화했습니다.
  • 또한 디스크 I/O 통계를 출력할 때 각 디스크에 대해 읽기/쓰기 I/O 작업의 상세 정보를 출력하여, 더욱 구체적인 모니터링이 가능해졌습니다.

5. 화면 갱신 최적화

  • updateDisplay() 함수에서 화면을 갱신할 때, 이전 화면을 clear()를 통해 비우고 새로운 정보를 출력하는 방식으로 화면을 갱신합니다. 이 방식은 사용자에게 실시간으로 변하는 데이터를 보기 쉽게 전달합니다.
  • 특히 프로세스 정보, CPU 사용률, 메모리 사용량, 디스크 사용량 등이 매초 갱신되므로, 실시간 시스템 모니터링 기능을 더 잘 수행할 수 있습니다.

6. 소스 코드의 구조 개선

  • 코드를 여러 개의 함수로 나누어 각 리소스 수집을 독립적으로 처리하고, 이를 화면에 출력하는 방식으로 구조를 개선했습니다. 이를 통해 코드의 가독성과 유지보수성을 높였으며, 기능 추가나 수정이 용이하도록 했습니다.

결론

이번 개선된 코드는 성능 최적화, 리소스 관리 효율성, 실시간 시스템 모니터링에 큰 향상이 있었습니다. 캐싱을 통한 불필요한 데이터 조회를 줄였고, 해시맵을 통해 리소스 관리를 효율적으로 처리하여 시스템 리소스를 아끼면서도 더 많은 데이터를 실시간으로 처리할 수 있게 되었습니다.

 

밑에는 macOS용 테스트 코드이다.

//macOS

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <sys/sysctl.h>
#include <curses.h>

#define MAX_PROCESSES 1024

// 프로세스 정보를 저장하는 배열
typedef struct ProcessInfo {
    int pid;
    char name[256];
} ProcessInfo;

ProcessInfo processArray[MAX_PROCESSES];
int processCount = 0;

// 프로세스 정보 수집 (MacOS 전용)
void collectProcesses() {
    int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0};
    struct kinfo_proc *processList = NULL;
    size_t size = 0;

    if (sysctl(mib, 4, NULL, &size, NULL, 0) == -1) {
        perror("sysctl size error");
        return;
    }

    processList = malloc(size);
    if (processList == NULL) {
        perror("malloc error");
        return;
    }

    if (sysctl(mib, 4, processList, &size, NULL, 0) == -1) {
        perror("sysctl data error");
        free(processList);
        return;
    }

    processCount = (int)(size / sizeof(struct kinfo_proc));
    for (int i = 0; i < processCount && i < MAX_PROCESSES; i++) {
        processArray[i].pid = processList[i].kp_proc.p_pid;
        strncpy(processArray[i].name, processList[i].kp_proc.p_comm, sizeof(processArray[i].name));
    }

    free(processList);
}

// CPU 사용량 수집
void collectCPUUsage(char *buffer) {
    FILE *fp = popen("top -l 1 | grep 'CPU usage'", "r");
    if (fp) {
        fgets(buffer, 256, fp);
        pclose(fp);
    } else {
        strcpy(buffer, "N/A");
    }
}

// 메모리 사용량 수집
void collectMemoryUsage(char *buffer) {
    FILE *fp = popen("vm_stat | grep 'Pages free'", "r");
    if (fp) {
        fgets(buffer, 256, fp);
        pclose(fp);
    } else {
        strcpy(buffer, "N/A");
    }
}

// 화면 갱신
void updateDisplay() {
    clear();
    mvprintw(0, 0, "==================[ Real-Time System Monitoring ]==================");

    // 프로세스 정보 출력
    mvprintw(2, 0, "Processes:");
    for (int i = 0; i < processCount && i < 20; i++) { // 최대 20개만 출력
        mvprintw(3 + i, 0, "PID: %-5d | Name: %-20s",
                 processArray[i].pid, processArray[i].name);
    }

    // CPU 정보 출력
    char cpuBuffer[256];
    collectCPUUsage(cpuBuffer);
    mvprintw(24, 0, "=======================[ CPU USAGE INFO ]======================");
    mvprintw(25, 0, "%s", cpuBuffer);

    // 메모리 정보 출력
    char memoryBuffer[256];
    collectMemoryUsage(memoryBuffer);
    mvprintw(27, 0, "=====================[ MEMORY USAGE INFO ]=====================");
    mvprintw(28, 0, "%s", memoryBuffer);

    refresh();
}

int main() {
    initscr(); // ncurses 초기화

    while (1) {
        collectProcesses(); // 프로세스 정보 수집
        updateDisplay(); // 화면 갱신
        sleep(1); // 1초마다 갱신
    }

    endwin(); // ncurses 종료
    return 0;
}

 

응용 가능성

  • 모니터링 도구 개발: 실시간 CPU, 메모리, 디스크 I/O 데이터를 수집하는 모니터링 도구에서 활용 가능.
  • 데이터베이스 캐싱: 비슷한 기법을 데이터베이스 쿼리 결과 캐싱에 적용 가능.
  • IoT 시스템 최적화: 자주 사용되는 센서 데이터 캐싱을 통해 리소스 절약.

'[CS] 컴퓨터 공학 > CS 수업 정리' 카테고리의 다른 글

[CS] 컴파일러 compiler  (0) 2023.07.05
[CS] OS 운영체제  (0) 2023.07.05
[CS] 컴퓨터구조 어셈블리 계산기  (0) 2023.02.09
[CS] 컴퓨터구조 ch.2  (0) 2022.09.15
[CS] 컴퓨터 구조 ch1  (0) 2022.09.13