cgroups: Memory Threshold Notifier 구현

cgroups Notification API를 통해 cgroup의 상태 변경에 대한 알림을 받을 수 있다. 이를 활용하면 메모리 Threshold를 등록하고 도달 시 알림을 받을 수 있다.

메모리 Threshold 등록 방법

메모리 Threshold를 등록하기 위해서는 다음이 필요하다.(참조: Documentation/admin-guide/cgroup-v1/memory.rst)

  • eventfd(2)를 사용하여 eventfd를 생성한다.
  • memory.usage_in_bytes 또는 memory.memsw.usage_in_bytes 를 연다.
  • “<eventfd> <memory.usage_in_bytes의 fd> <threshold>” 같은 문자열을 cgroup.event_control에 write한다.

메모리 사용량이 등록한 Threshold 값을 넘게되면 eventfd를 통해 notify된다.

epoll() 사용법

여러 Threshold를 등록하여 사용할때는 각 Threshold에 해당하는 여러 fd를 감시하기 위해 epoll() 을 사용할 수 있다.

epoll 인스턴스 생성과 관리를 위한 다음 시스템 호출들이 제공된다.

  • epoll_create(2)에서는 새 epoll 인스턴스를 만들고 그 인스턴스를 가리키는 파일 디스크립터를 반환한다. (더 나중에 나온 epoll_create1(2)은 epoll_create(2)의 기능을 확장한 것이다.)
  • 다음으로 epoll_ctl(2)을 통해 관심 있는 파일 디스크립터를 등록한다. 그러면 epoll 인스턴스의 관심 목록에 항목이 추가된다.
  • epoll_wait(2)에서는 I/O 이벤트를 기다린다. 현재 가용 이벤트가 없으면 호출 스레드를 블록시킨다. (epoll 인스턴스의 준비 목록에서 항목을 가져오는 것으로 생각할 수 있다.)

cgroups Notification 방법과 epoll() 사용법을 통해 메모리 Threshold 별 Notifier를 구현할 수 있다.

Define Memory Thresholds

memory.usage_in_bytes 에 설정할 threshold를 정의

enum {
        THRESHOLD_MIN = 0,
        THRESHOLD_LOW,
        THRESHOLD_HIGH,
        THRESHOLD_NUM,
};
typedef unsigned int threshold_level;

unsigned long threshold[] = { 100, 300, 500 };

static char threshold_str[3][8] = { "min", "low", "high" };

Register Memory Threshold

등록할 event threshold level에 대한 자료구조 선언 및 설정 함수

typedef struct {
    int fd;                 // memory.usage_in_bytes
    int efd;                // eventfd
    threshold_level level;
    unsigned long threshold;
} event_level_t;

int set_event_level(event_level_t *event, threshold_level level)
{
    if ((event->fd = open(usage_path, O_RDONLY)) < 0) {
        perror("open()");
        return -1;
    }

    if((event->efd = eventfd(0, EFD_NONBLOCK)) < 0) {
        perror("eventfd()");
        close(event->fd);
        return -1;
    }

    event->level = level;
    event->threshold = threshold[level];

    return 0;
}

epoll fd에 eventfd 추가 및 제거 함수

int add_event_level(int epfd, event_level_t *event)
{
    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.ptr = event;

    return epoll_ctl(epfd, EPOLL_CTL_ADD, event->efd, &ev);
}

int del_event_level(int epfd, event_level_t *event)
{
    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.ptr = event;

    return epoll_ctl(epfd, EPOLL_CTL_DEL, event->efd, &ev);
}

cgroup.event_control 에 “<efd> <fd> <threshold>” 등록

static int _cgroup_write(const char *root, const char *file, char *buf, size_t size)
{
    char path[PATH_MAX];
    int fd;
    ssize_t wlen;

    snprintf(path, PATH_MAX, "%s/%s", root, file);
    if ((fd = open(path, O_WRONLY)) < 0) {
        perror("open()");
        return -1;
    }

    printf("cgroup_write: %s %s %d\n", path, buf, size);
    while (size > 0) {
        if ((wlen = write(fd, buf, size)) < 0) {
            perror("write()");
            close(fd);
            return -1;
        }

        size -= wlen;
        buf += wlen;
    }

    close(fd);

    return 0;
}

int register_threshold(int epfd, event_level_t *event, threshold_level level)
{
    char buf[64];
    int fd, n;

    printf("register threshold (%s, %ld)\n", threshold_str[level], threshold[level]);

    set_event_level(event, level);

    n = snprintf(buf, sizeof(buf), "%d %d %ld", event->efd, event->fd, MB(threshold[level]));
    _cgroup_write("/sys/fs/cgroup/memory", "cgroup.event_control", buf, n);

    add_event_level(epfd, event);
    return 0;
}

Handle threshold event

event 수신 처리부

    while (1) {
        struct epoll_event ep_events[MAX_EVENTS];

        if ((nfds = epoll_wait(epfd, ep_events, MAX_EVENTS, 1000)) < 0) {
            perror("epoll_wait()");
            if (errno == EINTR) {
                continue;
            }
            break;
        }

        if (nfds == 0) {
            continue;
        }

        for (i = 0; i < nfds; i++) {
            event_level_t *data = (event_level_t *)(ep_events[i].data.ptr);
            read(data->efd, &r, sizeof(uint64_t));
            if (current != data->level) {
                current = data->level;
                fprintf(stderr, "memory threshold notifiled: %s [%ld][%lld]\n",
                        threshold_str[data->level], data->threshold, r);
            }
        }
    }

Example Code

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <sys/eventfd.h>
#include <errno.h>
#include <linux/limits.h>

#define KB(x)                       ((x) * 1024)
#define MB(x)                       (KB((x) * 1024))
#define MAX_EVENTS                   5

enum {
    THRESHOLD_MIN = 0,
    THRESHOLD_LOW,
    THRESHOLD_HIGH,
    THRESHOLD_NUM,
};
typedef unsigned int threshold_level;

unsigned long threshold[] = { 100, 300, 500 };

static char threshold_str[3][8] = { "min", "low", "high" };
static char usage_path[] = "/sys/fs/cgroup/memory/memory.usage_in_bytes";
static char event_ctrl_path[] = "/sys/fs/cgroup/memory/cgroup.event_control";

typedef struct {
    int fd;                 // memory.usage_in_bytes
    int efd;                // eventfd
    threshold_level level;
    unsigned long threshold;
} event_level_t;


int add_event_level(int epfd, event_level_t *event)
{
    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.ptr = event;

    return epoll_ctl(epfd, EPOLL_CTL_ADD, event->efd, &ev);
}

int del_event_level(int epfd, event_level_t *event)
{
    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.ptr = event;

    return epoll_ctl(epfd, EPOLL_CTL_DEL, event->efd, &ev);
}

int set_event_level(event_level_t *event, threshold_level level)
{
    if ((event->fd = open(usage_path, O_RDONLY)) < 0) {
        perror("open()");
        return -1;
    }

    if((event->efd = eventfd(0, EFD_NONBLOCK)) < 0) {
        perror("eventfd()");
        close(event->fd);
        return -1;
    }

    event->level = level;
    event->threshold = threshold[level];

    return 0;
}

void close_event_level(event_level_t *event)
{
    close(event->efd);
    close(event->fd);
}

static int _cgroup_write(const char *root, const char *file, char *buf, size_t size)
{
    char path[PATH_MAX];
    int fd;
    ssize_t wlen;

    snprintf(path, PATH_MAX, "%s/%s", root, file);
    if ((fd = open(path, O_WRONLY)) < 0) {
        perror("open()");
        return -1;
    }

    printf("cgroup_write: %s %s %d\n", path, buf, size);
    while (size > 0) {
        if ((wlen = write(fd, buf, size)) < 0) {
            perror("write()");
            close(fd);
            return -1;
        }

        size -= wlen;
        buf += wlen;
    }

    close(fd);

    return 0;
}

int register_threshold(int epfd, event_level_t *event, threshold_level level)
{
    char buf[64];
    int fd, n;

    printf("register threshold (%s, %ld)\n", threshold_str[level], threshold[level]);

    set_event_level(event, level);

    n = snprintf(buf, sizeof(buf), "%d %d %ld", event->efd, event->fd, MB(threshold[level]));
    _cgroup_write("/sys/fs/cgroup/memory", "cgroup.event_control", buf, n);

    add_event_level(epfd, event);
    return 0;
}

void unregister_threshold(event_level_t *event)
{
    close_event_level(event);
}

int main(int argc, const char *argv[])
{
    static int epfd = -1;
    event_level_t event[THRESHOLD_NUM];
    event_level_t *data;
    threshold_level current = THRESHOLD_NUM;
    uint64_t r;
    int i, nfds;

    // create epoll
    epfd = epoll_create(THRESHOLD_NUM);

    // register thresholds
    for (i = 0; i < THRESHOLD_NUM; i++) {
        register_threshold(epfd, &event[i], i);
    }

    while (1) {
        struct epoll_event ep_events[MAX_EVENTS];

        if ((nfds = epoll_wait(epfd, ep_events, MAX_EVENTS, 1000)) < 0) {
            perror("epoll_wait()");
            if (errno == EINTR) {
                continue;
            }
            break;
        }

        if (nfds == 0) {
            continue;
        }

        for (i = 0; i < nfds; i++) {
            event_level_t *data = (event_level_t *)(ep_events[i].data.ptr);
            read(data->efd, &r, sizeof(uint64_t));
            if (current != data->level) {
                current = data->level;
                fprintf(stderr, "memory threshold notifiled: %s [%ld][%lld]\n",
                        threshold_str[data->level], data->threshold, r);
            }
        }
    }

    // unregister thresholds
    for (i = 0; i < THRESHOLD_NUM; i++) {
        unregister_threshold(&event[i]);
    }

    return 0;
}

실행 결과:

~# ./a.out
register threshold (min, 100)
cgroup_write: /sys/fs/cgroup/memory/cgroup.event_control 5 4 104857600 13
register threshold (low, 300)
cgroup_write: /sys/fs/cgroup/memory/cgroup.event_control 7 6 314572800 13
register threshold (high, 500)
cgroup_write: /sys/fs/cgroup/memory/cgroup.event_control 9 8 524288000 13

...
memory threshold notifiled: high [500]
memory threshold notifiled: low [300]
memory threshold notifiled: high [500]
memory threshold notifiled: low [300]
...

cgroups: 프로세스 그룹의 자원 관리
cgroup: cpu 서브시스템
cgroup: memory 서브시스템
cgroups: Memory Threshold Notifier 구현
cgruops v1 사용법

답글 남기기