출처 : http://sunjinyang.wordpress.com/2009/06/18/debugging-memory-leaks-with-tcmalloc-google-perftools/


리눅스에서 메모리 침범이나 메모리 누수, 혹은 복잡한 메모리 접근 관련 오류를 디버깅할때는 대부분 Valgrind 도구를 이용합니다. 하지만 Valgrind는 많은 메모리를 사용하고 실행 속도가 현저하게 느려지기 때문에, 별도의 타겟 장비에서 제한된 조건으로 동작하는 어플리케이션에는 조금 무리가 있습니다. 게다가 Valgrind의 메모리 검사 도구는 프로그램이 종료된 시점에서 누수된 메모리만 찾기 때문에, 실행 중에는 엄청나게 메모리를 사용하다가 정상적인 객체 해제 루틴이 호출되면 모든 메모리가 정리되어 찾을 수 없는 경우는 발견하지 못합니다.

구글 성능 도구를 다시 검토하고 사용하게 된 이유도 바로 여기에 있습니다. 현재 디버깅 중인 프로그램의 문제가 대략 이렇습니다. 몇날 며칠이고 문제없이 가동해야 하는 프로그램이 특정 설정을 적용한 후에는, 자고 일어나면 10~20메가씩 메모리 점유율이 한꺼번에 올라갑니다. 하지만 Valgrind 도구를 사용해도 어느 부분이 문제인지 찾을 수가 없습니다. 지난 글에서 TCMalloc 메모리 할당자로 교체한 후 문제가 해결된 줄 알았던 바로 그 패턴이기도 합니다. 그래서 이번에는 프로파일 기능을 이용해 직접 메모리 누수를 디버깅 해보고, 나중을 위해 그 과정을 정리해 보았습니다.

디버깅 환경

디버깅 환경은 우분투 9.04 x86_32 플랫폼입니다. x86_64 플랫폼에서는 이상하게 호출 그래프가 그려지지 않아서 일단 무시했습니다. 사용한 버전은 공식 홈페이지에서 다운로드 받은 1.2 버전입니다. 1.3 버전은 이상하게 프로파일 기능이 동작하지 않아 역시 무시했습니다.

TCMalloc 도구 설치 및 연결

호출 그래프를 생성하기 위해 dot 프로그램이 필요한데 이 프로그램은 graphviz 패키지에 들어있으므로 설치해야 합니다.

$ sudo apt-get install graphviz

우선, 공식 홈페이지에서 google-perftools 압축 파일을 다운로드 한 뒤 다음과 같이 빌드하고 설치합니다. [2011.05.02 추가]INSTALL 문서에도 명시되어 있듯이, x86_64 환경에서는 libunwind 라이브러리를 미리 설치한 뒤 빌드해야 정상적으로 동작합니다.

$ cd google-perftools*
$ ./configure --prefix=/usr
$ make
$ sudo make install

TCMalloc 라이브러리를 연결하는 방법은 공식 문서에도 나와 있듯이 디버깅할 프로그램 링크 마지막에 ‘-ltcmalloc‘ 옵션을 추가하거나, 실행할때 프로그램 앞에 ‘LD_PRELOAD=/usr/lib/libtcmalloc.so execute-file‘ 처럼 라이브러리를 먼저 로드해주면 됩니다. 저는 첫번째 방법을 사용했습니다. [2011.05.02 갱신] 언제부터인지는 확실치 않지만 첫번째 방법은 동작하지 않고 두번째 방법으로 해야 메모리 프로파일이 정상적으로 동작합니다.

참고로, 디버깅할 프로그램을 컴파일할때는 디버깅 심볼 옵션(-g)이 있어야 호출 그래프에서 정확한 함수 이름이 표시됩니다. 또한 최적화 옵션(-O2 등)을 사용 안하면 더 정확한 함수 호출 그래프를 얻을 수 있습니다.

프로파일 데이터 얻기

TCMalloc 라이브러리를 연결해도 기본적으로 프로파일 기능은 동작하지 않습니다. HEAPPROFILE 환경변수에 프로파일 정보를 주기적으로 덤프할 파일 이름 접두사(prefix)를 지정해야만 동작합니다.

$ HEAPPROFILE=/tmp/profile execute-file

접두사는 파일 절대 경로로 디렉토리를 포함할 수 있습니다.

만일 덤프 파일이 너무 자주 생성되거나 반대로 너무 드물게 생성된다면 환경 변수를 통해 간격을 조절할 수 있습니다.

$ HEAPPROFILE=/tmp/profile 
  HEAP_PROFILE_ALLOCATION_INTERVAL=107374182400 
  execute-file

더 자세한 옵션은 공식 문서를 확인해 보시기 바랍니다.

위 예제에서 지정한 방식대로 프로그램을 실행하면 /tmp/profile.0001.heap/tmp/profile.0002.heap/tmp/profile.0003.heap 등과 같은 프로파일 덤프 파일이 실행 도중 계속 생성됩니다.

결과 그래프 얻기

포스트스크립트(PostScript) 파일 형식으로 메모리 프로파일 정보를 포함한 함수 호출 그래프를 얻으려면 다음과 같이 형식으로 pprof 프로그램을 실행하면 됩니다.

$ pprof --ps --lines 
    execute-file 
    /tmp/profile.0001.heap 
    > profile-0001.ps

프로파일 덤프 파일에 대하여 하나씩 그래프를 생성해 직접 눈과 손으로 비교하는 것도 나쁘지 않지만, 두 프로파일의 차이점만 그래프로 만들어주는 옵션이 있으므로 이를 사용하면 더 편리합니다. 즉, 시간대별 메모리 사용량의 달라진 부분이 정확하게 어느 함수 호출 때문인지 알 수 있게 해줍니다.

$ pprof --ps --lines 
    --base /tmp/profile.0001.heap 
    execute-file 
    /tmp/profile.0002.heap 
    > profile-0002-diff.ps

물론, PDF(--pdf), GIF(--gif) 등과 같은 다른 형식으로 그래프 파일을 얻을 수도 있습니다. 더 자세한 옵션은 ‘pprof --help‘를 참고하시면 됩니다.

결론

며칠간 디버깅에 적용해보니 문제가 발생한 패턴이 무엇이었는지도 찾아내고, 간과했던 작은 메모리 누수 버그들도 함께 발견할 수 있었습니다. 하지만, 언제나 그렇듯이, 디버깅 사태까지 오기 전에 더 튼튼하게 설계하고, 더 꼼꼼하게 프로그래밍하고, 더 철저하게 코드 리뷰와 테스트를 거치는 게 정도임을 새삼 깨닫습니다.

+ Recent posts