상주 세트 크기 / 가상 크기에 대한 설명 필요


61

pidstat프로세스를 모니터링하는 데 유용한 도구 라는 것을 알았습니다 . 특정 프로세스의 평균 메모리 사용량을 계산하고 싶습니다. 다음은 예제 출력입니다.

02:34:36 PM       PID  minflt/s  majflt/s     VSZ    RSS   %MEM  Command
02:34:37 PM      7276      2.00      0.00  349212 210176   7.14  scalpel

(이 출력의 일부입니다 pidstat -r -p 7276.)

평균 메모리 소비를 계산하기 위해 RSS (Resident Set Size) 또는 VSZ (Virtual Size) 정보를 사용해야합니까? Wikipedia와 포럼에서 몇 가지 내용을 읽었지만 차이점을 완전히 이해하지는 못합니다. 또한 신뢰할 수있는 것으로 보이지 않습니다. 그렇다면 프로세스를 모니터링하여 메모리 사용량을 얻는 방법은 무엇입니까?

이 문제에 대한 도움이 도움이 될 것입니다.



답변:


63

RSS는이 프로세스가 현재 주 메모리 (RAM)에 가지고있는 메모리 양입니다. VSZ는 프로세스의 총 가상 메모리 양입니다. 여기에는 RAM 및 스왑 아웃의 모든 유형의 메모리가 포함됩니다. 이 숫자에는 공유 라이브러리 및 기타 유형의 메모리도 포함되어 있기 때문에 왜곡 될 수 있습니다. 500 개의 인스턴스를 bash실행할 수 있으며 총 메모리 크기는 RSS 또는 VSZ 값의 합계가 아닙니다.

프로세스의 메모리 풋 프린트에 대한보다 자세한 아이디어가 필요한 경우 몇 가지 옵션이 있습니다. 당신은 /proc/$PID/map당신이 좋아하지 않는 것들을 통해 걸러 낼 수 있습니다 . 공유 라이브러리 인 경우 필요에 따라 계산이 복잡해질 수 있습니다 (기억합니다).

프로세스의 힙 크기 만 신경 쓰는 경우 항상 파일 의 [heap]항목을 구문 분석 할 수 있습니다 map. 커널이 프로세스 힙에 할당 한 크기는 프로세스가 할당 하도록 요청한 정확한 바이트 수를 반영하거나 반영하지 않을 수 있습니다. 이를 막을 수있는 세부 사항, 커널 내부 및 최적화가 있습니다. 이상적인 세계에서는 프로세스 요구 사항만큼 시스템 페이지 크기의 가장 가까운 배수로 반올림됩니다 ( getconf PAGESIZEPC의 경우 4,096 바이트 일 것입니다).

프로세스가 얼마나 많은 메모리를 할당 했는지 확인 하려면 가장 좋은 방법 중 하나는 커널 측 메트릭을 포기하는 것입니다. 대신, LD_PRELOAD메커니즘 을 사용하여 C 라이브러리의 힙 메모리 (비) 할당 기능을 계측합니다 . 개인적으로, 나는 valgrind이런 종류의 것에 대한 정보를 얻기 위해 약간 남용 합니다. 계측을 적용하려면 프로세스를 다시 시작해야합니다.

런타임을 벤치마킹 할 수도 있으므로 valgrind프로그램이 약간 느려질 수 있습니다 (허용 오차 범위 내).


고마워요! 다른 옵션을 조사하겠습니다. 당신은 도움보다 더되었습니다! :)
Flanfl

"500 개의 bash 인스턴스를 실행할 수 있으며 총 메모리 크기는 RSS 또는 VSZ 값의 합계가 아닙니다." 그러나 RSS 값의 합계가 좋은 근사치입니까? statm의 상주 열 합계와 마찬가지로, 신뢰할 수있는 정확한 값은 필요하지 않지만 Java 프로세스가 사용하는 메모리 양을 알아야합니다.
iloveretards

3
우분투에서 /proc/$PID/maps그것은 오타입니까 아니면 배포판 차이입니까?
dolzenko

1

최소 실행 가능 예

이 이해하려면, 당신은 페이징의 기초를 이해해야한다 : https://stackoverflow.com/questions/18431261/how-does-x86-paging-work을 특히 OS가 페이지 테이블을 통해 가상 메모리를 할당 할 수 / 실제로 RAM 또는 디스크 (RSS 상주 메모리)에 백업 스토리지를 갖기 전에 내부 메모리 북 보관 (VSZ 가상 메모리).

이제 이것을 실제로 관찰하기 위해 다음과 같은 프로그램을 만들어 봅시다.

main.c

#define _GNU_SOURCE
#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>

typedef struct {
    unsigned long size,resident,share,text,lib,data,dt;
} ProcStatm;

/* https://stackoverflow.com/questions/1558402/memory-usage-of-current-process-in-c/7212248#7212248 */
void ProcStat_init(ProcStatm *result) {
    const char* statm_path = "/proc/self/statm";
    FILE *f = fopen(statm_path, "r");
    if(!f) {
        perror(statm_path);
        abort();
    }
    if(7 != fscanf(
        f,
        "%lu %lu %lu %lu %lu %lu %lu",
        &(result->size),
        &(result->resident),
        &(result->share),
        &(result->text),
        &(result->lib),
        &(result->data),
        &(result->dt)
    )) {
        perror(statm_path);
        abort();
    }
    fclose(f);
}

int main(int argc, char **argv) {
    ProcStatm proc_statm;
    char *base, *p;
    char system_cmd[1024];
    long page_size;
    size_t i, nbytes, print_interval, bytes_since_last_print;
    int snprintf_return;

    /* Decide how many ints to allocate. */
    if (argc < 2) {
        nbytes = 0x10000;
    } else {
        nbytes = strtoull(argv[1], NULL, 0);
    }
    if (argc < 3) {
        print_interval = 0x1000;
    } else {
        print_interval = strtoull(argv[2], NULL, 0);
    }
    page_size = sysconf(_SC_PAGESIZE);

    /* Allocate the memory. */
    base = mmap(
        NULL,
        nbytes,
        PROT_READ | PROT_WRITE,
        MAP_SHARED | MAP_ANONYMOUS,
        -1,
        0
    );
    if (base == MAP_FAILED) {
        perror("mmap");
        exit(EXIT_FAILURE);
    }

    /* Write to all the allocated pages. */
    i = 0;
    p = base;
    bytes_since_last_print = 0;
    /* Produce the ps command that lists only our VSZ and RSS. */
    snprintf_return = snprintf(
        system_cmd,
        sizeof(system_cmd),
        "ps -o pid,vsz,rss | awk '{if (NR == 1 || $1 == \"%ju\") print}'",
        (uintmax_t)getpid()
    );
    assert(snprintf_return >= 0);
    assert((size_t)snprintf_return < sizeof(system_cmd));
    bytes_since_last_print = print_interval;
    do {
        /* Modify a byte in the page. */
        *p = i;
        p += page_size;
        bytes_since_last_print += page_size;
        /* Print process memory usage every print_interval bytes.
         * We count memory using a few techniques from:
         * https://stackoverflow.com/questions/1558402/memory-usage-of-current-process-in-c */
        if (bytes_since_last_print > print_interval) {
            bytes_since_last_print -= print_interval;
            printf("extra_memory_committed %lu KiB\n", (i * page_size) / 1024);
            ProcStat_init(&proc_statm);
            /* Check /proc/self/statm */
            printf(
                "/proc/self/statm size resident %lu %lu KiB\n",
                (proc_statm.size * page_size) / 1024,
                (proc_statm.resident * page_size) / 1024
            );
            /* Check ps. */
            puts(system_cmd);
            system(system_cmd);
            puts("");
        }
        i++;
    } while (p < base + nbytes);

    /* Cleanup. */
    munmap(base, nbytes);
    return EXIT_SUCCESS;
}

GitHub의 업스트림 .

컴파일하고 실행하십시오.

gcc -ggdb3 -O0 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
echo 1 | sudo tee /proc/sys/vm/overcommit_memory
sudo dmesg -c
./main.out 0x1000000000 0x200000000
echo $?
sudo dmesg

어디:

프로그램 출력 :

extra_memory_committed 0 KiB
/proc/self/statm size resident 67111332 768 KiB
ps -o pid,vsz,rss | awk '{if (NR == 1 || $1 == "29827") print}'
  PID    VSZ   RSS
29827 67111332 1648

extra_memory_committed 8388608 KiB
/proc/self/statm size resident 67111332 8390244 KiB
ps -o pid,vsz,rss | awk '{if (NR == 1 || $1 == "29827") print}'
  PID    VSZ   RSS
29827 67111332 8390256

extra_memory_committed 16777216 KiB
/proc/self/statm size resident 67111332 16778852 KiB
ps -o pid,vsz,rss | awk '{if (NR == 1 || $1 == "29827") print}'
  PID    VSZ   RSS
29827 67111332 16778864

extra_memory_committed 25165824 KiB
/proc/self/statm size resident 67111332 25167460 KiB
ps -o pid,vsz,rss | awk '{if (NR == 1 || $1 == "29827") print}'
  PID    VSZ   RSS
29827 67111332 25167472

Killed

종료 상태 :

137

이는 의해 128 + 신호 번호 규칙 우리가 신호 수있어 의미 9, man 7 signal말한다 SIGKILL 리눅스에서 보낸, 메모리 부족 살인자 .

출력 해석 :

  • VSZ 가상 메모리는 mmap 후에도 일정하게 유지됩니다 printf '0x%X\n' 0x40009A4 KiB ~= 64GiB( ps값은 KiB에 있음).
  • RSS "실제 메모리 사용량"은 페이지를 터치 할 때만 게으르게 증가합니다. 예를 들면 다음과 같습니다.
    • 첫 번째 인쇄물 extra_memory_committed 0에는가 있습니다. 아직 페이지를 건드리지 않았습니다. RSS는 1648 KiB텍스트 영역, 전역 등과 같은 정상적인 프로그램 시작에 할당 된 작은 크기 입니다.
    • 두 번째 인쇄물에서 우리는 8388608 KiB == 8GiB가치있는 페이지를 작성했습니다 . 결과적으로 RSS는 정확히 8GIB 증가하여8390256 KiB == 8388608 KiB + 1648 KiB
    • RSS는 8GiB 단위로 계속 증가합니다. 마지막 인쇄는 약 24GiB의 메모리를 표시하며 32GiB를 인쇄하기 전에 OOM 킬러가 프로세스를 종료했습니다.

참조 : 상주 세트 크기 / 가상 크기에 대한 설명 필요

OOM 킬러 로그

우리의 dmesg명령은 OOM 킬러 로그를 보여줍니다.

이에 대한 정확한 해석은 다음과 같습니다.

로그의 첫 줄은 다음과 같습니다.

[ 7283.479087] mongod invoked oom-killer: gfp_mask=0x6200ca(GFP_HIGHUSER_MOVABLE), order=0, oom_score_adj=0

우리는 흥미롭게도 MongoDB 데몬이 항상 랩톱에서 항상 백그라운드에서 처음 OOM 킬러를 트리거 한 백그라운드에서 실행되는 것으로 나타났습니다.

그러나 OOM 킬러는 반드시 그것을 깨운 사람을 죽일 필요는 없습니다.

호출 후 커널은 oom_score다음을 포함하여 테이블 또는 프로세스를 인쇄합니다 .

[ 7283.479292] [  pid  ]   uid  tgid total_vm      rss pgtables_bytes swapents oom_score_adj name
[ 7283.479303] [    496]     0   496    16126        6   172032      484             0 systemd-journal
[ 7283.479306] [    505]     0   505     1309        0    45056       52             0 blkmapd
[ 7283.479309] [    513]     0   513    19757        0    57344       55             0 lvmetad
[ 7283.479312] [    516]     0   516     4681        1    61440      444         -1000 systemd-udevd

더 나아가서 우리 main.out는 이전의 호출에서 우리 자신의 작은 것이 실제로 죽임을당했습니다.

[ 7283.479871] Out of memory: Kill process 15665 (main.out) score 865 or sacrifice child
[ 7283.479879] Killed process 15665 (main.out) total-vm:67111332kB, anon-rss:92kB, file-rss:4kB, shmem-rss:30080832kB
[ 7283.479951] oom_reaper: reaped process 15665 (main.out), now anon-rss:0kB, file-rss:0kB, shmem-rss:30080832kB

이 로그는 score 865해당 프로세스가 가지고있는 프로세스, 아마도 가장 높은 (가장 나쁜) OOM 킬러 점수를 언급 합니다. OOM 킬러는 어떤 프로세스를 먼저 죽일 지 어떻게 결정합니까?

또한 흥미롭게도 모든 것이 너무 빨리 발생하여 해제 된 메모리가 계산되기 전에 프로세스가 oom다시 깨어났습니다 DeadlineMonitor.

[ 7283.481043] DeadlineMonitor invoked oom-killer: gfp_mask=0x6200ca(GFP_HIGHUSER_MOVABLE), order=0, oom_score_adj=0

그리고 이번에는 Chromium 프로세스를 죽였습니다. 일반적으로 내 컴퓨터의 정상적인 메모리 호그입니다.

[ 7283.481773] Out of memory: Kill process 11786 (chromium-browse) score 306 or sacrifice child
[ 7283.481833] Killed process 11786 (chromium-browse) total-vm:1813576kB, anon-rss:208804kB, file-rss:0kB, shmem-rss:8380kB
[ 7283.497847] oom_reaper: reaped process 11786 (chromium-browse), now anon-rss:0kB, file-rss:0kB, shmem-rss:8044kB

Ubuntu 19.04, Linux 커널 5.0.0에서 테스트되었습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.