Wednesday, May 4, 2011

golang prof pprof

В общем задался тут задачей понять как можно профилировать проги на Go, в итоге сначала попался мне prof причём если там пишут, что чтобы выполнить профайлинг нужно выполнить 6prof, то верьте им. В общем-то печалит то, что вывод, который генерирует эта прога довольно бессмысленный:

$ 6prof -p `pidof your_program`
72 samples (avg 5 threads)
400.00% runtime.futex
 91.67% syscall.Syscall6
 12.50% runtime.clone
  6.94% syscall.Syscall
  1.39% scanblock

Ну а дальше в интернетах говорят про гугловский pprof, собственно для начала нужно поставить его с их сайта (я лично ставил из deb пакета, так же там есть и rpm сборка). Дальше возникает вопрос, а как же подружить свою свежую прогу с этим произведением человечества. Я гуглил долго в гугле и просто "golang pprof" и "golang profiling", долго мне это не давалось, пока я не попал на вот это сообщение в рассылке go-nuts. То есть разгадка оказалось очень простой, нужно сделать ровно 2 вещи:
1) подключить пакет http/pprof
import _ "http/pprof"
если символ _ кажется для вас удивительным, то это нормально, я тоже не знал что это такое :) судя по спекам это специальная декларация, которая с одной стороны как бы не включает пакет в область видимости (то есть не будет ругани про то, что пакет не используется внутри программы), но при этом запускает некую "инициализацию", в общем-то просто функцию init(), но суть, что эту функцию нельзя вызвать как pprof.init() потому что она не экспортирована (пишется с маленькой буквы).
2) запустить http-сервер. В целом для меня это кажется немного не очевидным, что для простого профилирования нам приходится городить http-сервер, но суть что пакет http/pprof вешает обработчики url'ов (это видно из исходного кода той самой функции init): /debug/pprof/cmdline /debug/pprof/profile /debug/pprof/heap /debug/pprof/symbol. Собственно я пока ещё думаю над тем, как именно включить сервер, но пока обхожусь примерно таким способом:
        go func() {
                err := http.ListenAndServe(":6060", nil)
                if err != nil {
                        panic("ListenAndServe: " + err.String())
                }
        }()
добавляю этот код прямо таки в начало функции main(). Ну соответственно в production этот код (и подключение http/pprof тоже) нужно или вовсе выкидывать или хотя бы запускать только если запустили приложение с каким-нибудь флагом, ну и опять же возможно стоит позволить задавать порт произвольным, но это уже в каждом случае решается конкретно. Ну и сейчас если что-то запустится не так, то приложение сразу же весело падает, а возможно стоит сделать всё по правилам defer, panic, recover.

Но самое-то главное! Теперь мы можем помучить наше приложение и посмотреть где оно мучилось больше всего. Делается это очень просто:
$ pprof --web http://localhost:6060/debug/pprof/heap

И в браузере появится svg-схема того что откуда куда, например такая.

Ну и подводя итоги скажу, что хотя способ с pprof чуть более сложный чем хотелось бы, зато он даёт больше информации, а главное не так сильно тормозит работу приложения, как это делает 6prof. То есть при профилировании под 6prof у меня скажем были моменты, когда сервер на секунду приостанавливался, явно думая в этот момент не о том, что нужно было мне, а о том, что хотел от него профайлер. А при этом pprof не только не подвисает, но и вовсе не сказать чтобы заметно тормозит приложение.