System의 Performance를 측정할 때 가장 먼저 확인하는 것은 CPU와 Memory Usage 입니다. 이 중에서 오늘은 CPU load를 측정하는 명령어와 명령어를 통해 출력된 값들을 파악하는 방법에 대해 얘기해보려고 합니다.
/proc/loadavg or uptime
Android의 경우 Linux Kernel을 사용하기 때문에 Linux의 CPU Usage 명령어를 이용할 수 있습니다.
이 중 CPU Load를 확인할 수 있는 방법은 /proc/loadavg를 읽어보는 것입니다. 그런데 adb shell로 진입해서 "cat /proc/loadavg" 이나 "uptime"을 실행하면 아래와 같이 6개의 숫자가 출력됩니다. 이를 해석할 수 있어야 현재 CPU 상태를 파악할 수 있습니다. uptime은 /proc/loadavg 파일을 열어서 그 파일의 내용을 화면에 출력해주는 명령어입니다.
adb shell:/# cat /proc/loadavg 1.59 1.94 1.27 1/2886 13458 |
adb shell:/# uptime 22:25:11 up 10 min, 0 users, load average: 1.59, 1.94, 1.27 |
/proc/loadavg 는 얼마나 많은 process가 R(Running)혹은 D(Uninterruptible waiting) 상태인지를 1분, 5분, 15분 마다 평균값으로 보여줍니다. 일반적으로 1분보다는 5분과 15분 값이 더 유의미한 값입니다.
R(Running) : CPU에서 실행 중이거나 실행가능한 상태. 즉, run queue에 있는 R상태의 Process들의 개수.
D(Uninterruptible waiting) : I/O 대기하는 상태로 다른 어떤 일도 할 수 없는 상태. 즉, wait queue에 있는 D상태의 Process들의 개수.
Kernel에서 loadavg를 계산하는 부분은 calc_gloabal_load() 함수 인데, kernel 4.14 기준 kernel/sched/loadavg.c 파일에 존재합니다. 이 함수를 살펴보면 nr_active 값을 읽어오도록 되어 있는데 이는 run queue에서 nr_running과 nr_uninterruptible값을 읽어와서 더한 값입니다. 즉, Active Task는 이 둘의 합을 의미하며 Linux 커널은 이 값을 주기적으로 계산해서 평균을 내고 그것을 1분, 5분, 15분 기준으로 표현한 값을 Load Average로 나타내고 있으며 /proc/loadavg 파일로 출력하고 있습니다.
void calc_global_load(unsigned long ticks)
{
unsigned long sample_window;
long active, delta;
sample_window = READ_ONCE(calc_load_update);
if (time_before(jiffies, sample_window + 10))
return;
/*
* Fold the 'old' NO_HZ-delta to include all NO_HZ cpus.
*/
delta = calc_load_nohz_fold();
if (delta)
atomic_long_add(delta, &calc_load_tasks);
active = atomic_long_read(&calc_load_tasks);
active = active > 0 ? active * FIXED_1 : 0;
avenrun[0] = calc_load(avenrun[0], EXP_1, active);
avenrun[1] = calc_load(avenrun[1], EXP_5, active);
avenrun[2] = calc_load(avenrun[2], EXP_15, active);
WRITE_ONCE(calc_load_update, sample_window + LOAD_FREQ);
/*
* In case we went to NO_HZ for multiple LOAD_FREQ intervals
* catch up in bulk.
*/
calc_global_nohz();
}
/proc/loadavg 값을 보여주는 부분은 fs/proc/loadavg.c 파일의 loadavg_proc_show() 함수를 보면 확인할 수 있습니다.
static int loadavg_proc_show(struct seq_file *m, void *v)
{
unsigned long avnrun[3];
get_avenrun(avnrun, FIXED_1/200, 0);
seq_printf(m, "%lu.%02lu %lu.%02lu %lu.%02lu %ld/%d %d\n",
LOAD_INT(avnrun[0]), LOAD_FRAC(avnrun[0]),
LOAD_INT(avnrun[1]), LOAD_FRAC(avnrun[1]),
LOAD_INT(avnrun[2]), LOAD_FRAC(avnrun[2]),
nr_running(), nr_threads,
task_active_pid_ns(current)->last_pid);
return 0;
}
이 코드를 기준으로 cat /proc/loadavg 를 이용해 출력된 결과값을 어떻게 해석해야 하는 지 설명하도록 하겠습니다.
1.59 1.94 1.27 1/2886 13458
- 첫번째 값 : 1분 동안의 평균 CPU load
- 두번째 값 : 5분 동안의 평균 CPU load
- 세번째 값 : 15분 동안의 평균 CPU load
- 네번째 값 : Process Scheduler가 Scheduling한 전체 task 의 개수(nr_threads) 중 현재 실행 중인 Task의 수(nr_running())
- 다섯번째 값 : 최근에 실행된 Task의 PID
Load Average의 의미
위에서 설명했듯이 Load Average는 Active Task의 평균 값을 의미합니다. 그러면 Load Average값을 이용해서 System의 성능을 검토할 때 CPU core 1개 기준으로 1.00 인 경우 문제가 없다고 판단합니다.
하지만 Active Task에는 TASK_UNINTERRUPTIBLE이 있기 때문에 주의해야 합니다. 특정 Process에서 장시간 I/O를 점유하고 있으면 같은 자원을 사용하려는 다른 프로세스의 I/O 처리를 위한 대기시간에 크게 영향을 미치기 때문에 Load Average가 높지 않더라도 실제 System에서 I/O 및 Buffer 를 거치는 작업 등에서 Delay가 발생할 수 있습니다. 반대로 Load Average가 높아도 다른 Process와 동일한 자원을 사용하지 않는 I/O 처리 대기나 Process간에 Context Switching이 원활할 경우 Performance 저하가 느껴지지 않을 수도 있습니다.
절대적인 기준이 되지는 않지만 System 운영시 권장되는 load avg는 70%인 0.7 이하이며 그 이상일 경우에 System Performance 를 해치는 부분이 있는 지 확인이 필요한 용도로 /proc/loadavg 값을 이해하면 될 것 같습니다. 물론 load average만으로 판단해서는 안되며 CPU Usage, Memory Usage 도 함께 확인해서 판단해야 합니다. 만약 load average는 높은데 Memory Usage가 낮다면 CPU Upgrade도 고려가 필요합니다.
추가로 /proc/loadavg의 CPU load 값은 CPU가 몇 개의 Core로 구성되어 있는 지에 따라 그 의미가 달라집니다. 즉, 각 CPU core별로 100% load된 상태의 load average 값은 아래와 같습니다.
- 1 core : 1.00
- 2 core : 2.00
- 3 core : 3.00
※ 참고로 "grep -c processor /proc/cpuinfo" 명령어를 이용하시면 CPU core의 전체 개수를 확인할 수 있습니다.
참고사이트
https://fmyson.tistory.com/398 (가장 자세히 설명되어 있습니다.)
https://scoutapm.com/blog/understanding-load-averages
https://people.cs.rutgers.edu/~pxk/416/notes/07-scheduling.html
https://kim-dragon.tistory.com/45
https://m.blog.naver.com/PostView.naver?blogId=skddms&logNo=221459071377&targetKeyword=&targetRecommendationCode=1
https://fmyson.tistory.com/398