← Documents Documentation/admin-guide/cpu-load.rst GitHub 원문 ↗

Linux 6.18.37 · Administration / CPU

CPU load accounting

Timer interrupt sampling으로 /proc/stat CPU state counter를 계산하는 방식과 짧은 workload가 누락돼 실제 load와 크게 달라질 수 있는 이유를 설명합니다.

Source pathDocumentation/admin-guide/cpu-load.rst
Source versionLinux v6.18.37
TranslationDUJINLABS 전문 번역 + 해설

요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.

1. 요약·해설

원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.

Tick 기반 CPU accounting

cpu-load.rst:1-52

`/proc/stat`과 `/proc/uptime`의 평균 CPU state가 timer interrupt 순간의 sample에 의존해 phase-aligned workload를 놓칠 수 있음을 보여 줍니다.

오차 재현 program

cpu-load.rst:53-117

Microsecond timer와 CPU-burning loop를 조합한 C program, 관련 discussion과 contributor를 제공합니다.

2. 영어 원문 전체

번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.

원문 전체 펼치기
1 ========
2 CPU load
3 ========
4
5 Linux exports various bits of information via ``/proc/stat`` and
6 ``/proc/uptime`` that userland tools, such as top(1), use to calculate
7 the average time system spent in a particular state, for example::
8
9 $ iostat
10 Linux 2.6.18.3-exp (linmac) 02/20/2007
11
12 avg-cpu: %user %nice %system %iowait %steal %idle
13 10.01 0.00 2.92 5.44 0.00 81.63
14
15 ...
16
17 Here the system thinks that over the default sampling period the
18 system spent 10.01% of the time doing work in user space, 2.92% in the
19 kernel, and was overall 81.63% of the time idle.
20
21 In most cases the ``/proc/stat`` information reflects the reality quite
22 closely, however due to the nature of how/when the kernel collects
23 this data sometimes it can not be trusted at all.
24
25 So how is this information collected? Whenever timer interrupt is
26 signalled the kernel looks what kind of task was running at this
27 moment and increments the counter that corresponds to this tasks
28 kind/state. The problem with this is that the system could have
29 switched between various states multiple times between two timer
30 interrupts yet the counter is incremented only for the last state.
31
32
33 Example
34 -------
35
36 If we imagine the system with one task that periodically burns cycles
37 in the following manner::
38
39 time line between two timer interrupts
40 |--------------------------------------|
41 ^ ^
42 |_ something begins working |
43 |_ something goes to sleep
44 (only to be awaken quite soon)
45
46 In the above situation the system will be 0% loaded according to the
47 ``/proc/stat`` (since the timer interrupt will always happen when the
48 system is executing the idle handler), but in reality the load is
49 closer to 99%.
50
51 One can imagine many more situations where this behavior of the kernel
52 will lead to quite erratic information inside ``/proc/stat``::
53
54
55 /* gcc -o hog smallhog.c */
56 #include <time.h>
57 #include <limits.h>
58 #include <signal.h>
59 #include <sys/time.h>
60 #define HIST 10
61
62 static volatile sig_atomic_t stop;
63
64 static void sighandler(int signr)
65 {
66 (void) signr;
67 stop = 1;
68 }
69
70 static unsigned long hog (unsigned long niters)
71 {
72 stop = 0;
73 while (!stop && --niters);
74 return niters;
75 }
76
77 int main (void)
78 {
79 int i;
80 struct itimerval it = {
81 .it_interval = { .tv_sec = 0, .tv_usec = 1 },
82 .it_value = { .tv_sec = 0, .tv_usec = 1 } };
83 sigset_t set;
84 unsigned long v[HIST];
85 double tmp = 0.0;
86 unsigned long n;
87 signal(SIGALRM, &sighandler);
88 setitimer(ITIMER_REAL, &it, NULL);
89
90 hog (ULONG_MAX);
91 for (i = 0; i < HIST; ++i) v[i] = ULONG_MAX - hog(ULONG_MAX);
92 for (i = 0; i < HIST; ++i) tmp += v[i];
93 tmp /= HIST;
94 n = tmp - (tmp / 3.0);
95
96 sigemptyset(&set);
97 sigaddset(&set, SIGALRM);
98
99 for (;;) {
100 hog(n);
101 sigwait(&set, &i);
102 }
103 return 0;
104 }
105
106
107 References
108 ----------
109
110 - https://lore.kernel.org/r/[email protected]
111 - Documentation/filesystems/proc.rst (1.8)
112
113
114 Thanks
115 ------
116
117 Con Kolivas, Pavel Machek
118

3. 한국어 전문 번역

영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.

Procfs CPU time과 sampling model

1-30

Linux는 `/proc/stat`과 `/proc/uptime`을 통해 여러 정보를 export합니다. `top(1)` 같은 userland tool은 이 값을 사용해 system이 특정 state에서 보낸 평균 시간을 계산합니다. 원문의 `iostat` 예시는 다음과 같습니다.

    $ iostat
    Linux 2.6.18.3-exp (linmac)     02/20/2007

    avg-cpu:  %user   %nice %system %iowait  %steal   %idle
              10.01    0.00    2.92    5.44    0.00   81.63

    ...

이 출력에서 system은 기본 sampling period 동안 user space work에 10.01%, kernel work에 2.92%를 썼고 전체 시간의 81.63%는 idle이었다고 판단합니다.

대부분 `/proc/stat` 정보는 실제 상태를 상당히 가깝게 반영하지만, kernel이 data를 수집하는 방식과 시점 때문에 전혀 신뢰할 수 없는 경우도 있습니다.

Timer interrupt가 발생할 때마다 kernel은 그 순간 실행 중인 task의 종류와 state를 보고 대응 counter를 증가시킵니다. 두 timer interrupt 사이에서 system state가 여러 번 바뀌어도 마지막 순간의 state counter만 증가한다는 것이 문제입니다.

iostat sample 해석
StatePercent해석
`%user`10.01User-space work
`%system`2.92Kernel work
`%iowait`5.44I/O wait
`%idle`81.63Observed idle

원문 예시의 주요 CPU state 비율입니다.

Timer tick 기반 accounting
Timer interruptInspect currently running taskClassify task stateIncrement one counterExport through /proc/stat

Counter는 tick 사이의 전체 history가 아니라 interrupt 순간의 state를 sample합니다.

Tick 사이 work가 사라지는 예

31-52

한 task가 두 timer interrupt 사이에서 주기적으로 CPU cycle을 소모한다고 가정합니다. 첫 tick 직후 work를 시작하고 다음 tick 직전에 sleep에 들어가며 곧 다시 깨어납니다.

이 경우 timer interrupt는 항상 system이 idle handler를 실행할 때 발생하므로 `/proc/stat`에는 load가 0%로 보입니다. 하지만 실제 load는 99%에 가깝습니다.

즉 periodic sampling과 workload phase가 맞물리면 kernel accounting data가 심하게 왜곡될 수 있습니다.

두 timer interrupt 사이의 실제 work
Timer interrupt
ticktick
두 tick 모두 idle handler에서 관찰
Task
workworkworkworksleep
Tick 사이 대부분의 시간을 실행
time
Observed load = 0%, actual load ≈ 99%

Source의 ASCII timeline을 동일한 의미의 시간축으로 다시 그렸습니다.

Sampling 오차 재현 program

53-105

원문은 `/proc/stat`에 불규칙한 정보가 나타나는 상황을 재현하는 `smallhog.c` program을 제공합니다. Source는 다음과 같으며 compile command도 그대로 보존합니다.

	/* gcc -o hog smallhog.c */
	#include <time.h>
	#include <limits.h>
	#include <signal.h>
	#include <sys/time.h>
	#define HIST 10

	static volatile sig_atomic_t stop;

	static void sighandler(int signr)
	{
		(void) signr;
		stop = 1;
	}

	static unsigned long hog (unsigned long niters)
	{
		stop = 0;
		while (!stop && --niters);
		return niters;
	}

	int main (void)
	{
		int i;
		struct itimerval it = {
			.it_interval = { .tv_sec = 0, .tv_usec = 1 },
			.it_value    = { .tv_sec = 0, .tv_usec = 1 } };
		sigset_t set;
		unsigned long v[HIST];
		double tmp = 0.0;
		unsigned long n;
		signal(SIGALRM, &sighandler);
		setitimer(ITIMER_REAL, &it, NULL);

		hog (ULONG_MAX);
		for (i = 0; i < HIST; ++i) v[i] = ULONG_MAX - hog(ULONG_MAX);
		for (i = 0; i < HIST; ++i) tmp += v[i];
		tmp /= HIST;
		n = tmp - (tmp / 3.0);

		sigemptyset(&set);
		sigaddset(&set, SIGALRM);

		for (;;) {
			hog(n);
			sigwait(&set, &i);
		}
		return 0;
	}

Program은 1 microsecond 간격의 `ITIMER_REAL`을 설정하고 `SIGALRM` handler에서 `stop`을 세웁니다. 먼저 `hog(ULONG_MAX)` loop를 여러 번 측정해 interrupt 사이에 실행할 iteration 수를 추정하고 평균값의 약 3분의 2를 `n`으로 정합니다.

그 뒤 무한 loop에서 `hog(n)`으로 CPU를 사용하고 `sigwait()`로 다음 `SIGALRM`을 기다립니다. Work와 signal wait의 phase가 accounting tick과 관계를 맺으면서 실제 CPU 사용과 sampled counter 사이의 차이를 보여 줍니다.

smallhog.c 실행 흐름
Install SIGALRM handlerStart 1 us interval timerMeasure hog iterationsCompute n = average - average/3hog(n)sigwait(SIGALRM)Repeat

Calibration 뒤 CPU-burning loop와 signal wait를 반복합니다.

참고 자료와 감사

106-117

관련 논의는 `https://lore.kernel.org/r/[email protected]`과 `Documentation/filesystems/proc.rst`의 1.8절을 참조하십시오.

이 문서는 Con Kolivas와 Pavel Machek에게 감사를 표합니다.