디버깅 및 프로파일링 지원

Caution

리눅스에서만 사용 가능합니다. 윈도우즈의 경우 Visual Studio 2019 기능을 활용하실 수 있습니다.

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}

미니덤프(.dmp) 디버깅하기

아이펀 엔진은 게임 서버가 크래시할 때 Google Breakpad 를 통해서 덤프 디렉토리에 미니덤프(Minidump, .dmp) 파일을 생성합니다.

미니덤프는 코어덤프(Core dump)보다 훨씬 작은 크기의 덤프 파일을 생성하기 때문에 간편하게 크래시한 스레드의 스택을 확인할 수 있습니다.

미니덤프 생성 위치

빌드 디렉터리에서 -local 파일로 실행한 경우에는 빌드 디렉터리의 dumps/ 디렉터리에 미니덤프 파일을 저장합니다.

$ ls dumps/
1d06e877-d3af-4586-1baaa8bf-e04ffd34.dmp
f381eb15-448d-4ce3-8fd76783-1e3d33b9.dmp
report-20200616_104219.json
report-20200616_104237.json

서버 패키지를 설치해서 서비스로 실행한 경우에는 /var/crash/funapi/{{project-name}}/ 디렉터리에 덤프 파일을 저장합니다.

미니덤프 내용 보기

생성된 미니덤프 파일의 내용을 보기 위해서 아이펀 엔진에서 제공하는 funapi_stackwalk 명령을 사용하며 다음의 인자를 입력받습니다.

$ funapi_stackwalk {덤프파일 경로} {심볼 디렉터리 경로} ...
  • 덤프파일 경로: 미니덤프가 저장된 경로이며 미니덤프 파일 이름을 포함합니다. :ref:`debugging-minidump-location`_ 을 참고해 주세요.

  • 심볼 디렉터리 경로: 미니덤프 파일 정보를 출력하기 위해 디버깅 심볼이 저장된 디렉터리 경로입니다. 빌드 리렉터리에 symbols 라는 이름으로 생성되며 패키지로 만들어서 설치하면 /usr/share/{{project-name}} 디렉터리 아래에 같은 이름으로 생성됩니다.

# -local 파일로 실행한 경우: 빌드 디렉터리에서
$ funapi_stackwalk dumps/{{dump-uuid}}.dmp symbols/

# 서비스르 실행한 경우:
$ 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)

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

Note

아래 설명은 라이브 서버에서 데몬으로 실행할 때를 가정합니다. 개발 중인 경우는 /var/crash/funapi/{{project-name}}/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)

코어덤프(core) 디버깅하기

미니덤프는 디스크 사용량을 최소화하기 위해 스택에 있는 값과 여기서 직접 참조하는 값만 확인하실 수 있기 때문에 디버깅에 필요한 정보가 충분하지 않을 수 있습니다.

반면에 코어덤프는 미니덤프에 비해 상대적으로 많은 정보를 제공하지만 프로세스가 사용하는 전체 메모리를 디스크에 저장하기 때문에 메모리 사용량에 따라서 디스크 사용량이 증가합니다.

코어덤프 활성화

리눅스 기본 설정에서 코어덤프는 남지 않도록 설정되어 있기 때문에 개발 환경과 서비스 환경 각각에서 코어덤프를 남기도록 설정을 변경해야 합니다.

  • 개발 환경의 코어덤프 활성화 (-local 파일 실행 시)

    일반적으로 디버깅 시에는 빌드 디렉터리에서 -local 파일을 실행하는 경우가 많은데, 이 경우 서버를 실행하는 사용자의 코어덤프 설정의 영향을 받기 때문에 아래와 같이 설정을 변경합니다.

    # 아래와 같이 0이 나오는 경우 코어덤프가 디스크에 남지 않습니다.
    $ ulimit -c
    0
    
    # 코어덤프 사이즈를 unlimited 로 설정함으로써 코어덤프를 활성화.
    $ ulimit -c unlimited
    

    위와 같이 설정한 경우 터미널을 새로 연결하면 설정이 초기화됩니다. 만약, 매번 변경하는 것이 번거로우시다면 /etc/security/limits.conf 파일에 다음 행을 추가해주세요.

    # '*' 는 모든 사용자에게 적용한다는 뜻으로 특정 사용자를 지정할 수 있음.
    *        soft        core        unlimited
    *        hard        core        unlimited
    

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

    $ ./hello-local --enable_breakpad=false
    

    Note

    enable_breakpad 옵션과는 무관하게 운영체제의 기본 동작에 의해서 코어덤프가 남을 수 있습니다.

  • 서비스 환경에서 코어덤프 활성화

    서버 패키지를 설치해서 서비스로 실행할 때는 코어덤프 활성화 여부가 사용자 설정의 영향을 받지 않고, 서비스 유닛 파일의 영향을 받습니다.

    /lib/systemd/system 디렉터리에 있는 서버의 서비스 유닛 파일(.service) 을 다음과 괕이 수정해 주시기 바랍니다.

    [Service]
    # If this limit is not enough, you SHOULD look after
    # "net.netfilter.nf_conntrack_tcp_timeout_time_wait" (using sysctl)
    LimitNOFILE=999999
    LimitCORE=infinity
    

코어덤프 저장 경로

코어덤프는 아이펀 엔진 설정이 아니고, 시스템 설정에 따라서 저장 경로가 결정됩니다.

기본적으로는 /proc/sys/kernel/core_pattern 파일의 내용을 따르고, 리눅스 기본설정에서는 core 입니다. 이는 코어덤프 파일 이름을 core 라고 저장하며 저장 위치는 어플리케이션을 실행한 디렉터리이거나 서비스인 경우는 / 디렉터리입니다.

Note

CentOS8 에서는 systemd-coredump 라는 코어덤프를 관리하는 데몬을 사용하도록 되어 있어서 기본 설정에서 core 파일이 남지 않습니다. 대신 coredumpctl 명령으로 코어덤프 목록과 내용을 볼 수 있습니다.

자세한 내용은 coredumpctl Man Page(영문) 를 참고하시기 바랍니다.

/proc/sys/kernel/core_pattern 파일의 내용을 변경하면 코어덤프가 저장되는 위치과 이름을 바꿀수 있습니다.

다음 절차를 따라서 저장 위치를 변경할 수 있습니다.

  1. core_pattern 파일 내용 확인합니다.

  • core: 기본 설정입니다. 2번으로 진행하시면 됩니다.

  • |/usr/lib/systemd/systemd-coredump ...: systemd-coredump 데몬이 활성화되어 있지만, 2번으로 진행하시면 됩니다.

  • |/usr/share/apport/apport ...: apport 프로세스를 비활성화해야 합니다.

    sudo systemctl stop apport
    sudo systemctl disable apport
    
  • |/usr/libexec/abrt-hook-ccpp ...: abrt 프로세스를 비활성화해야 합니다.

    sudo systemctl stop abrt-ccpp
    sudo systemctl stop abrtd
    
  1. /etc/sysctl.conf 파일에 kernel.core_pattern 설정을 추가해 주세요. 여기서는 /tmp/crash 디렉터리 아래에 core-{실행파일명}-{프로세스ID}-{덤프시각} 형식으로 파일을 저장하도록 설정합니다.

Note

파일 이름형식에 대한 자세한 내용은 core Man Page(영문)Naming of core dump files 항목을 참고하시기 바랍니다.

#/etc/sysctl.conf
kernel.core_pattern = /tmp/crash/core-%e-%p-%t
  1. 다음 명령을 실행해서 수정한 내용을 반영해 주세요.

$ sudo sysctl -p

코어덤프 내용 보기

코어덤프 파일을 GDB 실행인자로 전달해서 열 수 있으며, GDBbt 명령으로 콜스택을 볼수 있습니다.

$ gdb {실행 프로그램 경로} {코어파일 경로}

현재 디렉터리에 저장돼 있는 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)

CPU 프로파일링

Note

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

Note

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

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

필요한 프로그램 설치

perf 유틸리티

Ubuntu

$ sudo apt-get install linux-tools-generic

Important

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

CentOS

$ sudo yum install perf

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

프로파일링 결과 추출

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 입니다.

    _images/profiling-sample.svg

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

Tip

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

Tip

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

Tip

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

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