Stackprof 개요
Stackprof은 2013년 Aman Carmani가 개발하고 현재 Aaron Patterson이 유지보수하는 Ruby 프로파일러 Gem입니다. 2,000라인 미만의 C 코드로 구현되어 낮은 오버헤드를 자랑하며, CPU 시간, Wall 시간, 객체 할당 등 다양한 지표를 프로파일링할 수 있습니다. 특히 Flame Graph를 포함한 여러 출력 형식을 지원하며, DataDog Ruby 프로파일러와 Rack Mini Profiler 등 다른 도구들에게 영감을 주었습니다.
샘플링 (Sampling) 기법
Stackprof과 같은 낮은 오버헤드 프로파일러의 핵심은 ‘샘플링’입니다. 애플리케이션에서 발생하는 모든 이벤트를 상세히 기록하는 대신, 주기적으로 애플리케이션의 상태를 확인하여 오버헤드를 최소화합니다. 예를 들어, 모든 메서드 호출을 세는 대신 10ms 또는 1ms마다 Ruby가 무엇을 하는지 확인하여 대략적인 CPU 사용 비율을 파악합니다.
프로파일러의 4가지 주요 구성 요소
Stackprof은 다음과 같은 네 가지 주요 구성 요소로 작동합니다.
1. 샘플링 시점 결정 (When to Sample)
-
객체 할당 (Object Allocations): Ruby의
TracePoint클래스를 활용합니다. 특히 C 코드에서만 접근 가능한Ruby internal event new object이벤트를 사용하여 새로운 객체가 생성될 때마다 특정 C 함수를 호출하도록 설정합니다. -
CPU 및 Wall Time: Unix/Linux 운영체제의
interval timerAPI를 사용합니다.setitimer함수를 통해 운영체제에 주기적으로 신호를 발생시키도록 요청하고,sigaction함수로 해당 신호(예:SIGALRMfor Wall time,SIGPROFfor CPU time)를 처리할 함수를 등록합니다.- CPU Time vs. Wall Time: CPU 시간은 애플리케이션이 실제 CPU를 사용하여 코드를 실행하는 시간이며, Wall 시간은 실제 세계에서 경과하는 시간입니다. Sleep이나 데이터베이스 호출과 같이 CPU를 사용하지 않는 대기 시간은 Wall Time에는 포함되지만 CPU Time에는 포함되지 않습니다. 두 지표 모두 애플리케이션 최적화 및 지연 시간 분석에 중요합니다.
2. 샘플 수집 (Taking a Sample)
신호 핸들러(sigprof_signal_handler)나 객체 할당 이벤트 발생 시, buffer_sample 함수가 호출됩니다. 이 함수는 현재 타임스탬프를 기록하고, RB_PROFILE_FRAMES API를 사용하여 현재 스레드의 백트레이스(호출 스택)를 가져옵니다. 가비지 컬렉션(GC) 중일 경우 stackprof_job_record_gc 함수를 통해 GC 관련 정보를 기록합니다.
3. 샘플 저장 (Storing a Sample)
샘플 수집 후, postponed jobs API를 통해 안전한 시점에 샘플을 저장합니다. 이 API는 운영체제 신호 핸들러 내에서 메모리 할당이나 대부분의 Ruby VM API 호출이 안전하지 않기 때문에 필요합니다. Ruby VM이 ‘안전 지점(safe point)’에 도달했을 때(예: 전역 VM 락을 확보하고 GC 중이 아닐 때) 저장 작업을 수행하도록 지연시킵니다. Stackprof은 두 가지 방식으로 데이터를 저장합니다.
-
Raw Samples: 백트레이스와 해당 샘플이 발생한 타임스탬프를 별도의 배열에 저장합니다. 중복되는 백트레이스가 연속적으로 나타나면 카운트를 증가시킵니다.
-
Aggregate Samples: 타임스탬프 없이 백트레이스만 압축하여 저장합니다. 이는 메모리 사용량을 절감하고 반복을 줄여 효율성을 높입니다.
4. 결과 포맷팅 (Formatting the Results)
저장된 샘플 데이터는 다양한 형식으로 변환될 수 있습니다. Stackprof은 특히 stack collapse 형식을 지원하는데, 이는 각 라인에 백트레이스와 해당 백트레이스가 관찰된 횟수를 기록하는 텍스트 파일입니다. 이 파일을 speedscope.app과 같은 웹사이트에 업로드하면 Flame Graph로 시각화하여 애플리케이션의 성능 병목 지점을 직관적으로 파악할 수 있습니다.
- Flame Graph 해석: 좌우 축은 시간 순서가 아닌 정렬된 순서를 나타내며, 상하 축은 메서드 호출 스택을 의미합니다. 각 블록의 너비는 해당 메서드가 프로파일러에 의해 관찰된 빈도(또는 시간)를 나타내므로, 넓은 블록이 성능 최적화의 잠재적 대상임을 시각적으로 보여줍니다.