Для Go-программ существует удобнейший стандартный пакадж expvar, позволяющий одной строчкой подключить вывод дебаг информации в JSON-формате. И чтобы максимально быстро и наглядно мониторить текущее состояние, была написана консольная программа Expvarmon, требующая минимум конфигурации для вывода метрик и дебаг-информации для ваших Go-сервисов.

Функции:
  • 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)


  1. coxx
    09.05.2015 13:01

    лучше бы это веб-морда была


    1. HiNeX
      09.05.2015 15:18

      Так исходники доступны же, можно и переписать


    1. divan0 Автор
      09.05.2015 16:08
      +1

      Есть веб-морды, даже в виде готовых пакаджей. К примеру, github.com/mkevac/debugcharts.

      Но я хотел максимально простое решение, чтобы даже не переключаться на браузер, не думать какой порт выбрать и так далее. Ну и да — UI там в виде интерфейса сделано, с небольшими усилиями можно и веб-морду сделать — главное фронтенд красивый и аккуратный нарисовать, а это не совсем мое. :)


  1. powerman
    09.05.2015 13:50

    если интервал опроса больше, чем время падения/рестарта сервиса, то падение сервиса программа не словит
    Может стоит ориентироваться на uptime, если он выдаётся?


    1. divan0 Автор
      09.05.2015 16:10

      В стандартном expvar, к сожалению, нет uptime, а на кастомные переменные пользователя полагаться и вводить свои стандарты/требования не очень хорошая идея. Хотя, возможно, конечно, добавить эвристику и угадывать по имени «uptime».


  1. msa
    09.05.2015 14:41

    Установка программы, как и любой другой программы на Go предельно проста:
    go get github.com/divan/termui


    Для установки самой программы наверное нужно так?
    go get github.com/divan/expvarmon


    1. divan0 Автор
      09.05.2015 16:00

      Исправил, спасибо.


  1. AterCattus
    09.05.2015 14:50
    +1

    Собираю у себя подобные статистики, но go процессы отчитываются в централизованное «хранилище» для всяких подобных графиков.

    пример с RSS паматью


    1. divan0 Автор
      09.05.2015 16:12

      Ну да, это правильный подход для продакшена.


  1. ufm
    09.05.2015 17:40

    Автору оригинальной библиотеки осталось еще чуть-чуть и будет turbo vision :)


    1. divan0 Автор
      09.05.2015 18:52
      +2

      Консольные приложения вечны :)


  1. StreetStrider
    10.05.2015 18:08

    Выглядит очень симпатично. Такая штука была бы полезна не только для Go, но и для инфраструктур на других языках (писать, естественно, можно на Go). Заходишь, такой, на машину, восстанавливаешь сеанс tmux, а у тебя там сразу все метрики видны.


  1. lucky_brick
    10.05.2015 23:33

    zabbix?


    1. nucleusv
      11.05.2015 00:50

      не говори ка,
      сделали бы лучше один путь как возврат списка всех метрик для LLD,
      а потом бы пушали активными проверками с интервалом

      на дворе 2015 год, а люди все настальгируют по консолькам :)


      1. divan0 Автор
        11.05.2015 09:48

        Вы наверное невнимательно прочитали введение :)
        Никакого ностальгирования тут нет и в помине.


    1. ufm
      11.05.2015 03:03

      Ну я заббиксовый пассивный клиент за вечер написал — там на столько всё просто, что даже не интересно. :)