thread-sampling

1 개의 포스트

.NET Continuous Profiler: CPU and wall time profiling (새 탭에서 열림)

Datadog의 .NET 컨티뉴어스 프로파일러는 CPU 사용량과 Wall Time(실행 시간)을 효과적으로 수집하기 위해 저수준 스레드 샘플링 방식을 채택하고 있습니다. 운영 환경의 부하를 최소화하면서도 정확한 데이터를 확보하기 위해 관리되는 스레드(Managed Threads)를 정밀하게 추적하며, 가비지 컬렉션(GC)과 같은 네이티브 스레드의 영향까지 함께 분석합니다. 이를 통해 개발자는 연산 집약적인 코드뿐만 아니라 I/O 대기 등으로 인한 지연 원인까지 심층적으로 파악할 수 있습니다. ### CPU와 Wall Time 프로파일링의 개념적 차이 * **CPU 프로파일링**: 스레드가 CPU 코어에서 실제로 실행되는 동안 소모한 사이클을 측정하여 연산량이 많은 코드 블록을 찾는 데 집중합니다. * **Wall Time 프로파일링**: I/O 대기나 락(Lock) 경합 등 스레드가 중단된 시간까지 포함하여 메서드 실행에 걸린 전체 시간을 측정하며, 요청 지연의 근본 원인을 파악하는 데 유용합니다. * **샘플링 방식 채택**: ETW(Windows)나 perf(Linux) 같은 도구는 높은 권한과 시스템 부하 문제로 운영 환경에 부적합하므로, 특정 주기로 스레드 스택을 관찰하는 샘플링 방식을 사용하여 성능 영향을 최소화합니다. ### 효율적인 스레드 모니터링 구조 * **관리되는 스레드 추적**: `ICorProfilerCallback`의 메서드들을 활용해 .NET 런타임이 관리하는 스레드의 생성 및 파괴를 실시간으로 모니터링하고 `ManagedThreadList`에 보관합니다. * **네이티브 스레드 오탐 방지**: 초기 구현에서는 C#을 사용했으나, 네이티브 스레드가 관리되는 메서드를 호출할 때 발생하는 예외적인 상황을 방지하기 위해 전체 구조를 C++로 작성하여 프로파일러 자체 스레드가 샘플링되는 문제를 해결했습니다. * **공용 익스포터 활용**: 수집된 샘플 데이터는 Rust로 작성된 고성능 익스포터를 통해 Datadog 백엔드로 전송되며, 이 모듈은 PHP, Ruby 등 다른 언어 프로파일러와 공유되어 안정성을 확보했습니다. ### OS 수준의 CPU 프로파일링 최적화 * **상태 확인 메커니즘**: 10ms마다 실행 가능한 스레드를 검사하며, Windows는 `NtQueryInformationThread`를, Linux는 `/proc/self/task/<tid>/stat` 파일을 파싱하여 CPU 소비량을 확인합니다. * **저수준 C 구현을 통한 성능 개선**: Linux 환경에서 `std::ifstream` 등 고수준 C++ 클래스를 사용할 때 발생하는 메모리 할당 오버헤드를 줄이기 위해, 할당이 없는 저수준 C API로 교체하여 전체 메모리 할당량의 8%와 CPU 사용량의 2%를 절감했습니다. * **GC 스레드 가시화**: .NET 5 이상의 환경에서는 프로파일링 API가 감지하지 못하는 서버 GC 및 배경 GC 스레드의 CPU 소비량을 별도로 계산하여 플레임 그래프에 표시함으로써 성능 간섭 현상을 명확히 보여줍니다. ### 분산 추적과 연동된 Wall Time 분석 * **Code Hotspots 기능**: 분산 트레이서와 연동하여 특정 요청(Span)을 처리 중인 스레드를 우선적으로 샘플링하며, 이를 통해 느린 요청의 원인이 되는 코드 경로를 정확히 짚어냅니다. * **P/Invoke 비용 최소화**: 트레이서가 프로파일러를 호출할 때 발생하는 오버헤드를 줄이기 위해, 스팬 ID가 기록되는 메모리 위치를 직접 공유하여 추가적인 API 호출 없이 데이터를 실시간으로 읽어옵니다. * **동적 샘플링**: 실행 중인 스레드가 많아질수록 샘플링 간격을 조절하여 데이터의 정확도와 시스템 부하 사이의 균형을 유지합니다. 이 프로파일러는 고성능 환경에서 안정적으로 동작하기 위해 C++와 Rust를 기반으로 저수준 OS API를 직접 제어하도록 설계되었습니다. 특히 Linux 환경에서의 파일 파싱 최적화나 트레이서와의 메모리 공유 방식은 대규모 트래픽을 처리하는 서비스에서 프로파일러 자체의 오버헤드를 극단적으로 줄여야 하는 개발자들에게 유용한 참고 사례가 됩니다.