서버 리소스 사용량 관측하기

Posted by yunki kim on January 15, 2023

  프로젝트를 하던 도중 세부적인 CPU 사용량과 context switching에 소모되는 리소스를 관측해야 하는 일이 생겨 이를 도와줄 수 있는 툴에 대해 찾아보게 되었다.

vmstat

  메모리, 시스템 프로세스, 페이징, 인터럽트, I/O, CPU 스케줄링에 대한 정보를 수집해 전체 호스트 수준에서 보여주는 툴이다.

1
2
3
Bprocs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 0  0   4608 1216236  76632 12827516    0    0     0     5    1    1  0  0 99  0  0
cs

  위와 같은 정보를 확인할 수 있으며, 이 중 in(interrupt), cs(Context Switching)이 Context Switching 비용과 관련 있다. CPU 사용량 단위는 ‘%’이고 메모리 사용 단위는 ‘KB’이다.

Vmstat가 제공한는 각 지표의 의미는 다음과 같다

  • process
    • r: 실행 가능한 프로세스
    • b: 블로킹된 프로세스
  • memory
    • swpd: 스왑 메모리
    • free: 미사용 메모리
    • buff: 버퍼로 사용한 메모리
    • cache: 캐로 사용한 메모리
  • swap
    • si: 디스크로 교체된 들어간 메모리(swap-in)
    • so: 디스크에서 교체되 빠져나온 메모리(swap-out)
  • io
    • bi(block-in): input 장치에서 받은 512 바이트 블록 개수
    • bo(block-out): output 장치로 보낸 512 바이트 블록 개수
  • system
    • in: interrupt 횟수
    • cs: context switching 횟수
  • cpu(단위: %)
    • us: 유저 시간
    • sy: 커널 시간(system time)
    • id: 유휴 시간
    • wa: 대기 시간
    • st: 도둑 맞은 시간(가상 머신에 할애된 시간)

특정 기간을 두고 특정 횟수만큼 측정을 하고 싶다면 다음과 같이 vmstat를 사용하면 된다.

1
vmstat 5 10 # 5초의 간격을 두고 10번 캡쳐한다
cs

1초에 한번 관측되는 수치 변화를 계속 보고 싶다면 다음과 같이 사용하면 된다.

주의해야 할
 
 

지표

만약 위 지표 중 cpu의 us(유저 모드에서 CPU 사용률)가 100% 근처에 도달하지 못했는데 어떤 프로세스에서 콘텍스트 스위칭 비율(system 지표에서 cs)이 높게 나왔다면, 이는 I/O에서 블로킹이 발생했서나 스레드 락 경합(thread lock contention) 상황이 발생했을 확률이 크다.

cperf

복합적인 시스템 퍼포먼스 통계를 내주는 툴이다.

설치:

1
2
sudo apt-get install linux-tools-common linux-tools-generic
sudo apt install linux-tools-5.15.0-52-generic linux-cloud-tools-5.15.0-52-generic
cs

다음과 같이 임의의 시간 동안 데이터를 수집해 통계를 낼 수 있다

1
sudo perf stat -a sleep 20 # 20초 동안 데이터를 수집한다
cs

통계 결과:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Performance counter stats for 'system wide':
 
         80,007.09 msec cpu-clock                 #    4.000 CPUs utilized
             9,832      context-switches          #  122.889 /sec
               202      cpu-migrations            #    2.525 /sec
             2,393      page-faults               #   29.910 /sec
     3,338,969,186      cycles                    #    0.042 GHz                      (83.32%)
     3,830,939,977      stalled-cycles-frontend   #  114.73% frontend cycles idle     (83.32%)
     3,454,468,148      stalled-cycles-backend    #  103.46% backend cycles idle      (66.69%)
       482,245,519      instructions              #    0.14  insn per cycle
                                                  #    7.94  stalled cycles per insn  (83.34%)
        98,889,967      branches                  #    1.236 M/sec                    (83.34%)
        14,826,643      branch-misses             #   14.99% of all branches          (83.33%)
 
      20.001630108 seconds time elapsed
cs

특정 프로세스의 동작을 특정 시간동안 확인하고 싶다면, 다음과 같은 명령어를 사용하면 된다

1
sudo perf record -99 -p PID sleep 10
cs

이렇게 수집된 특정 프로세스의 데이터는 .data 확장자를 가진 바이너리 파일로 저장된다. 이 파일 수치를 확인하기 위해선 다음과 같은 명령어를 사용하면 된다.

1
sudo perf report
cs

Context Switching 테스트

위에서 소개한 툴들의 정상 동작 여부를 확인하기 위해 일부러 콘텍스트 스위칭을 많이 발생시키는 코드를 만들고 돌려보자.

time.csv:

1
2
3
4
5
6
7
8
9
past_date,future_date 
2006-12-28,2030-03-23 
2001-11-20,2026-10-30 
2006-12-28,2035-03-23 
2004-11-20,2024-01-30 
2003-12-28,2035-03-23 
2002-11-20,2024-08-30 
2005-12-28,2033-03-23 
2005-07-20,2051-01-30
cs

contextSwitch.go:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
gpackage main 
 
import (
    "encoding/csv"
    "fmt"
    "log"
    "os"
    "sync"
    "time"
)
 
func f(from string) {
 
    var wg sync.WaitGroup
    sliceLength := 1000000
    wg.Add(sliceLength)
    for i := 0; i < sliceLength; i++ {
        records := readCsvFile("time.csv")
 
        go func(i int) {
            defer wg.Done()
            for _, record := range records {
                layout := "2006-01-02"
                past_date, _ := time.Parse(layout, record[0])
                future_date, _ := time.Parse(layout, record[1])
                past_date.Date()
                future_date.Date()
 
            }
        }(i)
    }
    wg.Wait()
 
}
 
func readCsvFile(filePath string) [][]string {
      f, err := os.Open(filePath)
     if err != nil { 
        log.Fatal("Unable to read input file "+filePath, err)
    defer f.Close() 
 
    csvReader := csv.NewReader(f)
    records, err := csvReader.ReadAll() 
    if err != nil { 
       log.Fatal("Unable to parse file as CSV for "+filePath, err) 
}
 
    return records 
func main() { 
    f("direct"
    fmt.Println("done"
}
cs

이제 위 코드를 구동시켜보자

1
go run contextSwitch.go
cs

이제 vmstat와 pref를 이용해 위 코드가 구동되는 사이의 수치를 관측하면 된다.

vmstat 결과 (사용 명령어: vmstat 1)

콘텍스트 스위칭 발생 시 system에 속하는 in(interrupt)와 cs(context switching) 수치가 월등히 높아진 것을 확인할 수 있다.

contextSwitch.go를 실행한 상태:

1
2
3
4
5
6
7
8
9
10
11
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 4  0   4608 1189036  93012 12857336    0    0     0     5    1    1  0  0 99  0  0
 4  0   4608 1188004  93012 12857336    0    0     0     0 12955 127689 38 13 49  0  0
 4  0   4608 1187104  93012 12857336    0    0     0     0 13228 125807 35 16 48  0  0
 2  0   4608 1185200  93012 12857336    0    0     0     0 13595 123887 37 14 49  0  0
 3  0   4608 1184500  93012 12857336    0    0     0     0 13349 126304 35 14 50  0  0
 4  0   4608 1184500  93020 12857328    0    0     0    92 13183 128388 34 16 49  0  0
 2  0   4608 1182736  93020 12857336    0    0     0     0 13236 126502 35 16 49  0  0
 1  0   4608 1182876  93020 12857336    0    0     0     0 13284 125777 35 16 48  0  0
 4  0   4608 1181980  93020 12857336    0    0     0     0 14028 121666 35 15 50  0  0
cs

일반 상황:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 0  0   4608 1175292  93216 12856044    0    0     0     5    1    1  0  0 99  0  0
 0  0   4608 1175180  93216 12856044    0    0     0     0  295  477  0  0 100  0  0
 0  0   4608 1174704  93224 12856044    0    0     0    44  284  491  0  0 99  0  0
 0  0   4608 1175208  93224 12856044    0    0     0     0  277  486  1  0 99  0  0
 0  0   4608 1174956  93224 12856044    0    0     0     0  258  472  0  0 100  0  0
 1  0   4608 1174792  93224 12856044    0    0     0     0  299  516  1  0 99  0  0
 0  0   4608 1174452  93224 12856044    0    0     0     0  284  484  1  1 99  0  0
 0  0   4608 1174928  93224 12856044    0    0     0     0  259  458  0  0 100  0  0
 0  0   4608 1174452  93224 12856044    0    0     0    84  300  497  1  0 99  0  0
 0  0   4608 1174956  93224 12856044    0    0     0     0  290  488  0  0 100  0  0
 0  0   4608 1174452  93224 12856044    0    0     0     0  297  510  1  0 99  0  0
 0  0   4608 1175208  93224 12856044    0    0     0     0  290  520  1  0 99  0  0
cs

perf 결과 (사용 명령어: sudo perf stat -a sleep 20)

contextSwitch.go를 실행한 상태:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Performance counter stats for 'system wide':
 
         80,011.25 msec cpu-clock                 #    4.000 CPUs utilized
         2,279,586      context-switches          #   28.491 K/sec
            13,291      cpu-migrations            #  166.114 /sec
           123,884      page-faults               #    1.548 K/sec
   114,869,775,771      cycles                    #    1.436 GHz                      (83.32%)
   122,889,824,804      stalled-cycles-frontend   #  106.98% frontend cycles idle     (83.32%)
   103,631,665,515      stalled-cycles-backend    #   90.22% backend cycles idle      (66.68%)
    92,521,223,777      instructions              #    0.81  insn per cycle
                                                  #    1.33  stalled cycles per insn  (83.34%)
    19,027,317,534      branches                  #  237.808 M/sec                    (83.34%)
       170,918,900      branch-misses             #    0.90% of all branches          (83.33%)
 
      20.002279774 seconds time elapsed
cs

일반 상황:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Performance counter stats for 'system wide':
 
         80,007.03 msec cpu-clock                 #    4.000 CPUs utilized
             9,509      context-switches          #  118.852 /sec
               202      cpu-migrations            #    2.525 /sec
             2,329      page-faults               #   29.110 /sec
     3,287,813,081      cycles                    #    0.041 GHz                      (83.32%)
     3,728,859,681      stalled-cycles-frontend   #  113.41% frontend cycles idle     (83.32%)
     3,400,931,749      stalled-cycles-backend    #  103.44% backend cycles idle      (66.69%)
       472,904,185      instructions              #    0.14  insn per cycle
                                                  #    7.89  stalled cycles per insn  (83.34%)
        96,356,371      branches                  #    1.204 M/sec                    (83.34%)
        14,636,573      branch-misses             #   15.19% of all branches          (83.34%)
 
      20.001863836 seconds time elapsed
cs

 

참고 자료:

OREILLY 자바 최적화

High context switch and interrupt rates: How to diagnose and fix in linux