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 사용법