Telemetry
Telemetry
성능을 살펴볼 때 "지금 가상 머신이 어떤 상태인지" 한 조각으로 찍어두는 게 유용할 때가 많아요. Raku의 Telemetry 클래스는 바로 그런 가상 머신 상태의 스냅샷을 담는 도구예요.
본문
참고: 이 클래스는 Rakudo 전용 기능이라 표준 Raku에는 포함되지 않아요.
Telemetry 객체는 생성되는 순간 가상 머신의 다양한 상태를 스냅샷으로 담아요. 스냅샷 하나만으로도 유용하지만, 보통은 두 개를 찍고 그 차이를 구하게 되죠. 그 차이는 Telemetry::Period 객체가 돼요.
Telemetry 객체는 사실 여러 "계측기(instrument)"들이 찍은 스냅샷을 모아둔 거예요. 기본적으로는 Telemetry::Instrument::Usage와 Telemetry::Instrument::ThreadPool 계측기가 활성화돼 있어요.
Telemetry(와 Telemetry::Period) 객체는 Associative 역할도 해요. 그래서 Telemetry 객체를 읽기 전용 Hash처럼 취급할 수 있고, 계측기들의 데이터 값이 전부 키가 됩니다.
어떤 계측기를 쓸지는 $*SAMPLER 동적 변수로 정할 수 있어요. 이 변수는 Telemetry::Sampler 객체예요.
현재 Rakudo 코어가 지원하는 계측기는 다음과 같아요.
-
Telemetry::Instrument::Usage — 알파벳 순서로
cpu,cpu-sys,cpu-user,cpus,id-rss,inb,invcsw,is-rss,ix-rss,majf,max-rss,minf,mrcv,msnd,nsig,nswp,volcsw,outb,util%,wallclock값을 제공해요. 각 값의 의미는Telemetry::Instrument::Usage문서에서 확인할 수 있어요. -
Telemetry::Instrument::Thread — 알파벳 순서로
tad,tcd,thid,tjd,tsd,tys값을 제공해요. 의미는Telemetry::Instrument::Thread문서 참고. -
Telemetry::Instrument::ThreadPool — 알파벳 순서로
atc,atq,aw,gtc,gtq,gw,s,ttc,ttq,tw값을 제공해요. 의미는Telemetry::Instrument::ThreadPool문서 참고. -
Telemetry::Instrument::AdHoc — 스스로는 데이터를 제공하지 않아요. 어떤 변수를 감시할지 직접 지정해야 하고, 지정한 변수는 계측기에서 같은 이름의 메서드로 접근할 수 있게 돼요.
routine T
sub T()
Telemetry.new의 줄임말이에요. 기본으로 내보내집니다. Telemetry 클래스가 Associative 인터페이스를 제공하기 때문에, 한 문장 안에서 여러 값을 쉽게 끼워 넣을 수 있어요.
use Telemetry;
say "Used {T<max-rss cpu>} (KiB CPU) so far";
routine snap
multi snap(--> Nil)
multi snap(Str:D $message --> Nil)
multi snap(Str $message = "taking heap snapshot...", :$heap!)
multi snap(@s --> Nil)
snap 서브루틴은 새 Telemetry 객체를 만들어 나중에 처리할 배열에 밀어 넣는 걸 줄여 쓰는 함수예요. 기본으로 내보내집니다.
2017.11 릴리스부터는 밀어 넣을 배열 @s를 직접 지정할 수 있어요.
use Telemetry;
my @s;
for ^5 {
snap(@s);
# do some stuff
LAST snap(@s);
}
배열을 지정하지 않으면 편의상 내부 배열을 사용해요.
2019.07 릴리스부터 snap은 Str 타입의 위치 인자 $message를 받을 수 있고, 이 값이 새 Telemetry 객체의 message 속성 값이 돼요.
또한 2019.07 릴리스부터는 :heap 이름 인자를 넘겨 힙 스냅샷을 파일로 쓰도록 컴파일러에 지시할 수 있어요. 2021.12 릴리스부터 snap은 그 파일 이름을 돌려줘요. :heap 인자는 Bool, Str, 또는 IO::Path 타입이 될 수 있고, 동작이 다음과 같이 달라져요.
-
IO::Path, 예:snap( heap => 'outputfile'.IO )— 스냅샷을 정확히 이 경로에 써요. 돌려받는 파일 이름은 항상 절대 경로예요. -
Str, 예:snap(:heap<outputfile>)— 아직 그런 파일이 없다면 그 경로에 써요. 이미 같은 파일이 있다면outputfile-$($*PID)-$($i).mvmheap에 써요(여기서$i는 1부터 시작해 매번 커져요). 돌려받는 파일 이름은:heap의 형태에 따라 상대 경로가 될 수도 절대 경로가 될 수도 있어요. -
불리언
True, 예:snap(:heap)—heapsnapshot-$($*PID)-$($i).mvmheap파일에 써요. 돌려받는 파일 이름은 항상 상대 경로예요.
그리고 이 모든 경우에, 일반 Telemetry 객체 두 개가 내부 배열에 밀어 넣어져요. 하나는 스냅샷을 쓰기 직전에 만들어지고 $message를 message 속성으로 가지며, 다른 하나는 스냅샷을 쓴 직후에 만들어지고 파일 이름을 message 속성으로 가져요.
마지막으로 :heap에는 이런 옵션도 있어요.
- 불리언
False, 예:snap(:!heap)— 힙 스냅샷을 찍지 않고 파일도 쓰지 않아요. 대신snap()을 호출하거나(사용자 지정$message가 있으면snap($message)) 동작해요.
routine snapper
sub snapper($sleep = 0.1, :$stop, :$reset --> Nil)
snapper 루틴은 프로그램이 끝날 때까지 snap을 반복해서 부르는 별도 스레드를 시작해요. 기본으로 내보내집니다.
기본적으로 0.1초마다 snap을 호출해요. 유일한 위치 인자는 snap 사이의 지연 시간으로 취급돼요.
코드를 바꾸지 않고 외부에서 snapper를 시작하려면 snapper 모듈을 쓰면 돼요. 명령줄 인자에 -Msnapper만 추가하면 snapper가 자동으로 시작됩니다.
routine periods
multi periods( --> Seq)
multi periods(@s --> Seq)
periods 서브루틴은 Telemetry 객체 배열을 처리해서 그로부터 Telemetry::Period 객체들의 Seq를 만들어요. 기본으로 내보내집니다.
.<cpu wallclock>.say for periods(@t);
# OUTPUT:
# ====================
# (164 / 160)
# (23 / 21)
# (17 / 17)
# (15 / 16)
# (29 / 28)
배열을 지정하지 않으면 인자 없는 snap의 내부 배열을 사용하는데, 완료 시 그 배열을 재설정해서 새 snap을 다시 추가할 수 있게 해요.
use Telemetry;
for ^5 {
snap;
LAST snap;
}
say .<cpu wallclock>.join(" / ") for periods;
# OUTPUT:
# ====================
# 172 / 168
# 24 / 21
# 17 / 18
# 17 / 16
# 27 / 27
snap이 딱 한 번만 수행됐다면, Telemetry::Period 객체를 최소한 하나 만들기 위해 snap을 한 번 더 수행해요.
routine report
multi report(:@columns, :$legend, :$header-repeat, :$csv, :@format)
report 서브루틴은 Telemetry 객체 배열에 대한 보고서를 만들어요. 기본으로 내보내집니다. 이 배열은 정기적으로 snap을 호출해서 만들 수도 있고, snapper를 실행해서 만들 수도 있어요. 위치 인자를 쓰지 않으면 인자 없는 snap이 밀어 넣는 내부 배열을 가정해요.
다음은 report의 추가 이름 인자들이에요.
:columns— 보고서에 포함할 열의 이름을 지정해요. 열 이름(예:gw)으로 지정할 수 있어요. 지정하지 않으면RAKUDO_REPORT_COLUMNS환경 변수에 지정된 값을 쓰고, 그것도 없으면 다음이 기본값이 돼요.
wallclock util% max-rss gw gtc tw ttc aw atc
-
:header-repeat— 보고서에서 몇 줄마다 헤더를 반복할지를 지정해요. 지정하지 않으면RAKUDO_REPORT_HEADER_REPEAT환경 변수 값을, 그것도 없으면 기본값 32를 써요. -
:legend— 보고서에 범례(legend)를 추가할지 여부를 지정해요. 지정하지 않으면RAKUDO_REPORT_LEGEND환경 변수 값을, 그것도 없으면 기본값True를 써요.
프로그램이 끝날 때 내부 배열에 사용 가능한 snap이 있으면, report가 자동으로 생성되어 STDERR에 출력돼요.
module snapper
반복적으로 시스템 상태 스냅샷을 찍는 스레드를 시작하는 모듈이에요.
이 모듈은 서브루틴이나 메서드 같은 것을 전혀 포함하지 않아요. Telemetry 모듈의 snapper 서브루틴을 시작하는 단축키 역할을 하죠. 프로그램을 바꾸지 않고도 실행 스냅샷을 찍을 수 있게 해주는 거예요. -Msnapper로 모듈을 로드하기만 하면 snapper를 시작하기에 충분하고, 프로그램이 끝나면 STDERR에 보고서가 출력돼요.
RAKUDO_SNAPPER 환경 변수로 스냅샷 사이의 시간을 지정할 수 있어요. 지정하지 않으면 기본값 0.1초를 써요.
snapper 모듈은 프로세스가 정상적으로 종료되는 상황을 가정해요. 프로세스를 죽이면(예: Control-c를 누르면) 보고서가 생성되지 않아요.
module safe-snapper
2021.09 릴리스의 Rakudo 컴파일러부터 사용할 수 있어요.
이 모듈은 snapper 모듈의 안전한 대안을 제공해요. Control-c로 프로세스를 죽여도 보고서가 생성되죠. 이를 위해 신호 처리기(signal handler)를 설치하는데, 이 처리기가 대화형 프로그램의 정상적인 동작을 방해할 수 있어요.
다른 어떤 방식으로 프로세스를 죽이면 보고서가 생성되지 않아요.