ExpvarMon — консольный мониторинг сервисов на Go

    Для 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
      0
      лучше бы это веб-морда была
        0
        Так исходники доступны же, можно и переписать
          +1
          Есть веб-морды, даже в виде готовых пакаджей. К примеру, github.com/mkevac/debugcharts.

          Но я хотел максимально простое решение, чтобы даже не переключаться на браузер, не думать какой порт выбрать и так далее. Ну и да — UI там в виде интерфейса сделано, с небольшими усилиями можно и веб-морду сделать — главное фронтенд красивый и аккуратный нарисовать, а это не совсем мое. :)
          0
          если интервал опроса больше, чем время падения/рестарта сервиса, то падение сервиса программа не словит
          Может стоит ориентироваться на uptime, если он выдаётся?
            0
            В стандартном expvar, к сожалению, нет uptime, а на кастомные переменные пользователя полагаться и вводить свои стандарты/требования не очень хорошая идея. Хотя, возможно, конечно, добавить эвристику и угадывать по имени «uptime».
            0
            Установка программы, как и любой другой программы на Go предельно проста:
            go get github.com/divan/termui


            Для установки самой программы наверное нужно так?
            go get github.com/divan/expvarmon
              0
              Исправил, спасибо.
              +1
              Собираю у себя подобные статистики, но go процессы отчитываются в централизованное «хранилище» для всяких подобных графиков.
              пример с RSS паматью

              Машин с go пара дюжин, метрики нужно хранить…

              Память оказалось лучше собирать из /proc/self, точно соответствует интересному мне RSS.
              net/http используется тоже, но выдает для локального коннекта информацию из net/http/pprof, чтобы локальная проверялка могла детектить проблему быстрее, чем централизованная система.
                0
                Ну да, это правильный подход для продакшена.
                0
                Автору оригинальной библиотеки осталось еще чуть-чуть и будет turbo vision :)
                  +2
                  Консольные приложения вечны :)
                  0
                  Выглядит очень симпатично. Такая штука была бы полезна не только для Go, но и для инфраструктур на других языках (писать, естественно, можно на Go). Заходишь, такой, на машину, восстанавливаешь сеанс tmux, а у тебя там сразу все метрики видны.
                    0
                    zabbix?
                      0
                      не говори ка,
                      сделали бы лучше один путь как возврат списка всех метрик для LLD,
                      а потом бы пушали активными проверками с интервалом

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

                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                      Самое читаемое