44. 디버깅 및 프로파일링 지원

44.1. GDB를 사용해서 디버깅 하기

빌드 과정에서 나오는 서버 실행 스크립트를 이용해서 GDB를 연결한 상태로 실행하거나, 실행 중인 게임 서버 프로세스에 GDB를 연결할 수 있습니다.

다음과 같은 명령을 이용합니다. (프로젝트는 hello, flavor는 game 인 경우)

$ ./hello.game-local --gdb

이 때 이미 게임 서버가 실행 중이라면 실행 중인 프로세스에 연결하고, 그렇지 않은 경우에는 GDB 상에서 게임 서버를 실행합니다. 서버가 이미 시작된 상태가 아닌 경우, GDB 상에서 아래와 같이 디버깅을 시작할 수 있습니다.

 Please type `run` to start debugging.
 If you want to check the arguments, please type `show args`.


 GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
 Copyright (C) 2016 Free Software Foundation, Inc.
 ... (생략)
 Reading symbols from /usr/bin/funapi_runner...(no debugging symbols found)...done.
 (gdb) run

Tip

Linux 보안 설정으로 인해서 같은 유저 계정으로 띄운 게임 서버에 디버거를 연결할 수 없는 경우가 있습니다. 이 경우 sudo 를 사용하거나, 아래와 같은 명령을 실행한 후 연결할 수 있습니다. 아래 명령은 실제 서비스 환경에서는 설정해서는 안됩니다.

$ echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope

해당 명령으로 바꾼 설정은, 머신을 재시작하면 다시 원래 상태가 됩니다.

이미 떠 있는 게임 서버에 연결하려는데, 해당 게임 서버의 같은 flavor 가 여러 대 떠 있다면, 아래와 같이 process id 를 명시적으로 지정할 수 있습니다.

# {PID} 자리에 process id에 해당하는 숫자 지정
$ ./hello.game-local --gdb --pid {PID}

44.2. 덤프파일 디버깅하기

iFun Engine 은 게임 서버가 크래시할 때 Google Breakpad 를 활용해 덤프 디렉토리에 minidump 파일을 (.dmp) 생성합니다. 미니덤프는 코어덤프보다 훨씬 적은 크기의 덤프 파일을 생성합니다. 해당 파일을 이용해서 간편하게 크래시한 스레드의 스택을 확인할 수 있습니다.

44.2.1. 미니덤프 생성 위치

개발 중인 경우에는 빌드 디렉터리의 logs/dumps/ 디렉터리에 해당 파일을 저장합니다. 라이브 서버에서 게임 서버를 데몬으로 실행한 경우에는 /var/crash/funapi/{{project-name}}/ 디렉터리에 덤프 파일을 저장합니다.

44.2.2. 미니덤프 내용 보기

생성된 미니덤프 파일은 다음 명령어를 통해 스택을 확인할 수 있습니다.

Note

아래 설명은 라이브 서버에서 데몬으로 실행할 때를 가정합니다. 개발 중인 경우는 /var/crash/funapi/{{project-name}}/logs/dumps/ 로 바꿔주면 됩니다.

$ funapi_stackwalk /var/crash/funapi/{{project-name}}/{{dump-uuid}}.dmp \
    /usr/share/{{project-name}}/symbols

아래는 실행 예시입니다.

 $ funapi_stackwalk /var/crash/funapi/example/08835639-ff4e-e778-6c14c453-5147e752.dmp /usr/share/example/symbols
 OS Linux 0.0.0 Linux 3.10.0-123.20.1.el7.x86_64 #1 SMP Thu Jan 29 18:05:33 UTC 2015 x86_64
 CPU amd64 family 6 model 42 stepping 7 8
 Crash SIGSEGV 0x0 31

 0 libexample.so: /build/example-source/src/event_handlers.cc:155 (+0x4) - example::OnTick
 1 libexample.so: /usr/include/boost/function/function_template.hpp:112 (+0x14) - ...
 2 funapi_runner: ...
 ...
 7 libboost_thread-mt.so.1.53.0 (+0xd24a)
 8 libpthread-2.17.so (+0x7df5)
 9 libc-2.17.so (+0xf61ad)

44.2.3. 미니덤프를 GDB 로 디버깅하기

Note

아래 설명은 라이브 서버에서 데몬으로 실행할 때를 가정합니다. 개발 중인 경우는 /var/crash/funapi/{{project-name}}/logs/dumps/ 로 바꿔주면 됩니다.

아래 명령어를 이용하면 GDB 로 미니덤프 파일을 디버깅할 수 있습니다.

$ funapi_core_analyze /usr/lib/lib{{project-name}}.so \
    /var/crash/funapi/{{project-name}}/{{dump-uuid}}.dmp
 $ funapi_core_analyze /usr/lib/libhello.so /var/crash/funapi/hello/3acd4cbf-5654-695b-0dd043c9-3c70e30d.dmp
 Finding game server .so file
 Converting dumps/3acd4cbf-5654-695b-0dd043c9-3c70e30d.dmp into coredump
 Launching gdb to inspect generated coredump (hello.23945.core).
 Please press 'y' to load required symbols

 ...

 Failed to read a valid object file image from memory.
 Core was generated by `/usr/bin/funapi_runner --main_program_name=hello_server.game --app_flavor=game --'.
 #0  0x00007fc18d76c2de in ?? ()
 add symbol table from file "src/libhello.so" at
   .text_addr = 0x7fc18d0ceaf0
 Reading symbols from src/libhello.so...done.
 (gdb) bt
 #0  0x00007fc18d76c2de in hello::TestFilters () at ../src/filter.cc:367
 #1  0x00007fc18d43094f in (anonymous namespace)::HelloServer::Install (arguments=...) at ../src/hello_server.cc:113
 #2  0x00007fc18d430f18 in HelloServer_install (arguments=...) at ../src/hello_server.cc:182
 #3  0x00000000007328d3 in ?? ()
 #4  0x0000000000601b5c in ?? ()
 #5  0x00007fc1962d2ec5 in ?? ()
 #6  0x0000000000000000 in ?? ()
 (gdb) info locals
 x = 0x0
 (gdb)

44.2.4. 미니덤프 대신 코어덤프 생성하기

미니덤프의 경우, 디스크 사용량을 최소화하기 위해 스택에 있는 값과 여기서 직접 참조하는 값만 확인하실 수 있습니다. 이보다 많은 정보가 디버깅에 필요한 경우, 미니덤프 대신 코어덤프를 남기고, 이를 디버깅할 수 있습니다.

Warning

코어덤프는 프로세스가 사용하는 전체 메모리를 디스크에 저장하기 때문에, 메모리를 많이 사용하는 경우, 디스크 사용량도 이에 따라 증가합니다.

Important

OS 설정에서 코어덤프를 남길 수 있는지 우선 확인하셔야 합니다.

# 아래와 같이 0이 나오는 경우 코어덤프가 디스크에 남지 않습니다.
$ ulimit -c
0

# 남길 수 있는 코어덤프 용량 제한을 해제
$ ulimit -c unlimited

다음과 같은 옵션으로 런처를 실행합니다.

$ ./hello-local --enable_breakpad=false

OS 설정을 변경하지 않은 경우, 크래시 한 후에 core 파일이 현재 디렉터리에 생성됩니다. 해당 파일은 다음과 같이 디버깅 할 수 있습니다.

$ gdb /usr/bin/funapi_runner core
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.04) 7.11.1
...
Core was generated by `/usr/bin/funapi_runner --main_program_name=hello_server.default --app_flavor=de'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x00007f28715bad44 in (anonymous namespace)::HelloServer::Start () at ../src/hello_server.cc:71
71      *x = 0;
[Current thread is 1 (Thread 0x7f287e5ec800 (LWP 21649))]
(gdb) bt
#0  0x00007f28715bad44 in (anonymous namespace)::HelloServer::Start () at ../src/hello_server.cc:71
#1  0x00007f28715bad8a in HelloServer_start () at ../src/hello_server.cc:83
#2  0x0000000000845c03 in fun::StartComponents() ()
#3  0x000000000065eabe in main ()
(gdb)

44.3. CPU 프로파일링

Note

이 섹션은 일반적인 CPU 프로파일링에 대해서 다룹니다. 아이펀 엔진에 특화된 프로파일링은 이벤트 프로파일링 및 디버깅 을 참고해주세요.

Note

C# 버전 프로파일링은 추후 지원 예정입니다.

게임 서버를 다시 빌드하지 않고도 다음과 같은 과정을 통해 CPU 병목을 찾을 수 있습니다.

44.3.1. 필요한 프로그램 설치

44.3.1.1. perf 유틸리티

Ubuntu

$ sudo apt-get install linux-tools-generic

Important

generic 이 아닌 커널을 사용하는 경우, 해당 커널에 해당하는 linux-tools 패키지가 필요합니다. 즉, linux-tools-<커널이름> 이 됩니다.

CentOS

$ sudo yum install perf

44.3.1.2. FlameGraph

$ sudo wget -O /usr/local/bin/flamegraph.pl \
    https://raw.githubusercontent.com/brendangregg/FlameGraph/master/flamegraph.pl

$ sudo wget -O /usr/local/bin/stackcollapse-perf.pl \
    https://raw.githubusercontent.com/brendangregg/FlameGraph/master/stackcollapse-perf.pl

$ sudo chmod a+x /usr/local/bin/flamegraph.pl /usr/local/bin/stackcollapse-perf.pl

44.3.2. 프로파일링 결과 추출

Step 1. 게임 서버를 실행합니다. (이미 실행한 경우는 제외)

$ ./hello-local
  1. 다음처럼 프로파일링 명령을 실행합니다.

    $ sudo /usr/bin/funapi_profile {{ProjectName}}
    

    Tip

    만약 Flavor: 역할에 따라 서버 구분하기 를 쓰는 경우 아래처럼 flavor 를 지정합니다.

    $ sudo funapi_profile {{ProjectName}} {{FlavorName}}
    

    예시

    $ sudo /usr/bin/funapi_profile hello
    Profiling server for activity (for 60 seconds)
    [sudo] password for user:
    [ perf record: Woken up 7 times to write data ]
    [ perf record: Captured and wrote 1.626 MB perf.23428.data (~71057 samples) ]
    Generating flamegraph
    [kernel.kallsyms] with build id d7fccc433a6b698638325b755df2772c15636d04 not found, continuing without symbols
    Generated: perf.hello_server.default.23428.svg
    
  2. 출력 마지막 줄에 나와있는 SVG 파일을 브라우저로 엽니다. 위의 예에서는 perf.hello_server.default.23428.svg 입니다.

이 예제에서는 hello::OnTick() 함수가 병목인 경우인데, flamegraph 에서 가로로 길게 표시됩니다. 이런 함수를 찾아 최적화하시면 됩니다.

Tip

SVG 파일은 해당 함수를 클릭하면 확대되어 더 자세한 정보를 표시합니다.

Tip

SVG 에서 함수 위에 마우스를 갖다대면 좌측 하단에 해당 함수 및 하위 함수의 CPU 점유 시간 비율을 표시합니다.

Tip

SVG 파일의 우측 상단 Search 기능을 이용하면 프로젝트에서 사용한 코드 중에 어느 부분에서 CPU를 많이 쓰는지 쉽게 확인할 수 있습니다.

가령 hello 프로젝트는 Search^hello:: 를 입력하면 프로젝트의 함수들만 보라색으로 표시합니다.