Функции:
- single- и multi-services режимы
- мониторинг локальных и удаленных программ
- произвольное количество сервисов и переменных
- поддержка значений для памяти, временных интервалов, bool и произвольных чисел/строк
- sparkline-графики
- отображение максимальных значений
- отображение упавших/рестартовавших сервисов
- авто-ресайз при изменении размеров шрифта или окна
Введение
В стандартной библиотеке Go есть очень полезный пакадж expvar, который позволяет одной строчкой добавить вывод дебаг информации в json-формате по адресу /debug/vars. По-умолчанию выводятся данные об использовании памяти и работе сборщика мусора (GC), и легко добавляются любые свои метрики/счетчики/переменные. Обычно эти данные собирает отдельный процесс, который кладет в какую-нибудь time-series базу данных, и затем это превращается в удобные и красивые дашборды.
Но зачастую, даже во время разработки или отладочных сессий, хочется просто мониторить состояние программы, быть уверенным, что ресурсы используются адекватно нагрузкам, нет утечек памяти или горутин и так далее. Поднимать для этого целую инфраструктуру для мониторинга — часто накладно и неоправданно, а сырой json не сильно презентабелен.
Именно для таких случаев и была за пару выходных написана программа для мониторинга переменных expvar прямо в терминале, которая требует почти нулевую конфигурацию и не использует никаких сторонних баз и ресурсов. Программа использует отличнейший пакадж TermUI от любителя терминалов gizak (посмотрите его домашнюю страничку!).
Установка
Установка программы, как и любой другой программы на Go предельно проста:
go get github.com/divan/expvarmon
Надеюсь, $GOPATH/bin у вас прописан в $PATH.
Использование
expvar
Короткое объяснение
import _ "expvar"
Длинное объяснение
Если вы еще не знакомы с пакетом expvar, то краткое объяснение и инструкция.
В функции init() этого пакета написано следующее:
func init() {
http.HandleFunc("/debug/vars", expvarHandler)
Publish("cmdline", Func(cmdline))
Publish("memstats", Func(memstats))
}
Первая строка регистрирует хендлер для обработки URL "/debug/vars" для стандартного http.DefaultServeMux из стандартного пакета net/http. Если вы пока не знаете, как устроен net/http, возможно это будет отдельной статьей, но сейчас вам достаточно знать, что если ваша программа стартанет стандартный http-сервер (скажем, http.ListenAndServe(":1234", nil)), то у нее автоматически появится обработчик GET-запроса по адресу /debug/vars. И ответ этого запроса будет по умолчанию содержать примерно следующий JSON:
$ curl -s http://localhost:1234/debug/vars | json_pp | head
{
"cmdline" : [
"./expvar.demo",
"-bind=:1234",
],
"memstats" : {
"NumGC" : 5,
"Alloc" : 114016,
"DebugGC" : false,
"HeapObjects" : 519,
"HeapSys" : 868352,
"StackInuse" : 180224,
Это JSON-репрезентация двух переменных, определенных следующими двумя строчками — командная строка и текущие значения runtime.Memstats. Последняя содержит массу подробностей про текущее использование памяти и работу сборщика мусора, большая часть из которых ну уж слишком подробная. Обычно для мониторинга используются значения Alloc, Sys, HeapAlloc — реально используемая память, запрошенная у OS, используемая память в куче соответственно.
Поскольку init() вызывает автоматически при импорте пакаджа, в программе достаточно добавить одну строчку:
import _ "expvar"
expvarmon
Данная же программа предельно проста — она с указанным интервалом вычитывает этот JSON для указанного сервиса или сервисов, и отображает его в удобном для мониторинга виде, при этом показывает sparkline-графики для числовых значений. Все что ей нужно для запуска, это порт или «хост: порт» сервиса(-ов) которые вы хотите мониторить. К примеру:
expvarmon -ports="80"
expvarmon -ports="23000-23010,80"
expvarmon -ports="80,remoteapp.corp.local:80-82"
Можно указывать как одну, так и 30+ портов/сервисов — на сколько у вас хватит размеров терминала.
Программа может также мониторить саму себя:
expvarmon -self
Интервал по умолчанию — 5 секунд, но можно указать меньше или больше. Имейте ввиду, что слишком короткий интервал не рекомендован, так как даже обновление memstats влияет на сборщик мусора и увеличивает паузы. Если ваша аппликация бежит под большой нагрузкой, сильно короткий интервал (100ms) может повлиять на продуктивность.
expvarmon -self -i 5m
expvarmon -self -i 30s
По умолчанию мониторятся следующие переменные:
- mem:memstats.Alloc
- mem:memstats.Sys
- mem:memstats.HeapAlloc
- mem:memstats.HeapInuse
- memstats.EnableGC
- memstats.NumGC
- duration:memstats.PauseTotalNs
Значения переменных берутся в том виде, в каком они есть в JSON, с записью через точку. Модификаторы «mem:», «duration:», «str:» — опциональны, и влияют на форматирование/отображение. Если указывать переменные без модификатора, то они будут отображаться как есть. Модификатор «mem:» будет конвертировать значения в «KB, MB, etc» представление, а «duration:» — конвертировать int64 значение в time.Duration(«ns, ms, s, m, etc»). Модификатор «str:» просто говорит, что значение не цифровое, и sparkline-график для этой переменной рисовать не нужно.
К примеру:
expvarmon -ports="80" -vars="mem:memstats.Alloc,duration:Response.Mean,Goroutines,str:Uptime"
Опять же, можно указать как одну переменную, так и пару десятков, насколько у вас хватит размера терминала.
В программе есть два режима — для одного сервиса и для нескольких сервисов. В первом случае графики отображаются для всех переменных, во втором — только для первых двух, в том порядке, в котором они указаны. Для графиков отображаются максимальные значения, которые наблюдались за сессию мониторинга.
Дополнительно
Expvarmon отображает иконками сервисы, которые падали и которые лежат в данный момент. К сожалению, если интервал опроса больше, чем время падения/рестарта сервиса, то падение сервиса программа не словит.
Также важно понимать, что данные никуда не записываются и не сохраняются. На графиках показываются последние значения — и масштаб графиков зависит от интервала и размера терминала. Ни зума, ни истории, ни поиска по времени тут нет. Это решение для более простой задачи — мгновенного мониторинга текущих значений.
Благодаря возможностям TermUI, программа динамически меняет размеры всех виджетов при изменении размера шрифта или окна терминала.
Дополнительные переменные
Лично мне в стандартном перечне переменных expvar не хватает двух вещей — количества запущенных горутин и аптайм сервиса. Вот демо-враппер, который экспортит три дополнительные переменные. Просто подключаете его в свою программу, одним импортом.
package myexpvars
import (
"expvar"
"math/rand"
"runtime"
"time"
)
var (
startTime = time.Now().UTC()
)
// goroutines is an expvar.Func compliant wrapper for runtime.NumGoroutine function.
func goroutines() interface{} {
return runtime.NumGoroutine()
}
// uptime is an expvar.Func compliant wrapper for uptime info.
func uptime() interface{} {
uptime := time.Since(startTime)
return int64(uptime)
}
// responseTime fake var.
func responseTime() interface{} {
resp := time.Duration(rand.Intn(1000)) * time.Millisecond
return int64(resp)
}
func init() {
expvar.Publish("Goroutines", expvar.Func(goroutines))
expvar.Publish("Uptime", expvar.Func(uptime))
expvar.Publish("MeanResponse", expvar.Func(responseTime))
}
Что делать, если используется сторонний http-роутер, вместо стандартного
Многие веб-сервисы на Go пишутся с использованием дополнительных веб-фреймворков или более продвинутых http-роутеров. expvar из коробки с ними работать не будет. Для него вам нужно будет таки стартануть стандартный http.ListenAndServer() на другом порту. А это даже лучше, так как открывать наружу /debug/vars крайне не рекомендуется, если речь идет о публичных веб-сервисах.
Если же вы используете стандартный net/http, но хотите, чтобы expvar был на другом порту, проще всего сделать так. «Основной» ServeMux запустить следующим образом:
mux := http.NewServerMux()
server := &http.Server{Addr: “:80”, Handler: mux}
server.ListenAndServe()
а /debug/vars повесить на стандартный
http.ListenAndServe(":1234", nil)
Скриншоты
Ссылки
Github: github.com/divan/expvarmon
Expvar docs: golang.org/pkg/expvar
runtime.Memstats: golang.org/pkg/runtime/#MemStats
Комментарии (16)
powerman
09.05.2015 13:50если интервал опроса больше, чем время падения/рестарта сервиса, то падение сервиса программа не словит
Может стоит ориентироваться на uptime, если он выдаётся?divan0 Автор
09.05.2015 16:10В стандартном expvar, к сожалению, нет uptime, а на кастомные переменные пользователя полагаться и вводить свои стандарты/требования не очень хорошая идея. Хотя, возможно, конечно, добавить эвристику и угадывать по имени «uptime».
AterCattus
09.05.2015 14:50+1Собираю у себя подобные статистики, но go процессы отчитываются в централизованное «хранилище» для всяких подобных графиков.
пример с RSS паматьюStreetStrider
10.05.2015 18:08Выглядит очень симпатично. Такая штука была бы полезна не только для Go, но и для инфраструктур на других языках (писать, естественно, можно на Go). Заходишь, такой, на машину, восстанавливаешь сеанс tmux, а у тебя там сразу все метрики видны.
lucky_brick
10.05.2015 23:33zabbix?
nucleusv
11.05.2015 00:50не говори ка,
сделали бы лучше один путь как возврат списка всех метрик для LLD,
а потом бы пушали активными проверками с интервалом
на дворе 2015 год, а люди все настальгируют по консолькам :)divan0 Автор
11.05.2015 09:48Вы наверное невнимательно прочитали введение :)
Никакого ностальгирования тут нет и в помине.
ufm
11.05.2015 03:03Ну я заббиксовый пассивный клиент за вечер написал — там на столько всё просто, что даже не интересно. :)
coxx
лучше бы это веб-морда была
HiNeX
Так исходники доступны же, можно и переписать
divan0 Автор
Есть веб-морды, даже в виде готовых пакаджей. К примеру, github.com/mkevac/debugcharts.
Но я хотел максимально простое решение, чтобы даже не переключаться на браузер, не думать какой порт выбрать и так далее. Ну и да — UI там в виде интерфейса сделано, с небольшими усилиями можно и веб-морду сделать — главное фронтенд красивый и аккуратный нарисовать, а это не совсем мое. :)