Запись метрик в Graphite, Ganglia и StatsD на Haskell
17 февраля 2014
Ранее мы выяснили, как на Haskell работать с базами данных, писать REST API, генерировать и читать JSON-стримы, работать с конфигами и писать логи. Для полного счастья осталось разве что научится ходить в Graphite. После этого можно спокойно протаскивать Haskell в продакшн, пописывая на нем небольшие RESTful сервисы. Так чего же мы ждем?
В этом деле нам поможет пакет network-metrics. Он позволяет писать метрики в Graphite, а также в Ganglia и StatsD. Основные функции и типы, экспортируемые пакетом, следующие.
Тип-сумма, определяющий тип «стока», то есть, места, куда стекают метрики. Другими словами, этот тип нужен для задания того, куда именно мы пишем метрики.
type HostName = String
newtype PortNumber = PortNum GHC.Word.Word16
instance Num PortNumber
open :: SinkType -> Host -> HostName -> PortNumber -> IO AnySink
Функция open создает новый сток. Первым аргументом принимает тип стока, вторым — имя машины, на которой работает наше приложение, третьим и четвертым — хост и номер порта, на котором крутится Graphite (точнее — Carbon) или иной демон, собирающий метрики. Объявление типа AnySink находится в модуле Network.Metric.Internal и является экземпляром класса типов Sink. О его внутреннем устройстве нам думать не нужно.
Закрывает сток.
type Bucket = BS.ByteString
data Metric
= Counter Group Bucket Integer
| Timer Group Bucket Double
| Gauge Group Bucket Double
instance Measurable Metric
Здесь перечисляются поддерживаемые типы метрик — счетчик, таймер и мера/размер (не уверен, как в данном контексте точно переводится gauge).
Пишет метрики в заданный сток.
Как видите, если не считать пары заморочек с классами типов, все довольно просто. Код небольшого тестового приложения выглядит так:
import Network.Metric
import Control.Concurrent
import System.Random
import Data.Pool
import Network.HostName
import qualified Data.ByteString.Char8 as BS
import Control.Exception
import Control.Monad
pushLoop pool n =
when (n > 0) $ do
threadDelay 100000 -- 100 ms
writeMetrics pool `catch` reportIOException
print n
pushLoop pool $ n - 1
writeMetrics pool = do
counter <- randomRIO (50, 150)
timer <- randomRIO (50.0, 150.0)
gauge <- randomRIO (50.0, 150.0)
withResource pool $ \s -> do
push s $ Counter "example.store" "counter" counter
push s $ Timer "example.store" "timer" timer
push s $ Gauge "example.store" "gauge" gauge
reportIOException :: IOException -> IO ()
reportIOException e =
putStrLn $ "Something is wrong: " ++ show e
createSink = do
host <- getHostName
open Graphite (BS.pack host) "localhost" 2003
main = do
pool <- createPool createSink close 1 120 5
pushLoop pool 1000
Эта программа раз в 100 мс пишет случайные метрики всех трех поддерживаемых типов в работающий локально Carbon. Для того, чтобы программа переживала временную недоступность Carbon’а, используется уже знакомый нам пакет resource-pool. Для получения имени локальной машины я воспользовался пакетом hostname. Он экспортирует единственную функцию getHostName :: IO String
. Пакет работает как под Windows (используется GetComputerNameExW), так и под *nix (вызывается gethostname).
Не уверен на счет Ganglia и StatsD, но в случае с Graphite никаких различий между метриками типа Counter, Timer и Gauge на данный момент пакет не делает. В этом несложно убедиться, посмотрев на рисуемые графики, или, например, сказав:
Видно, что на каждый вызов функции push пакет тупо посылает в Carbon что-то типа:
Другими словами, никакой агрегации данных не производится. Если вам хочется отправлять метрики один раз в минуту и при этом подсчитывать суммарное количество загрузок определенной страницы или, например, максимальное время выполнения запроса к базе данных, придется написать соответствующий код самостоятельно. Готового пакета для решения этой проблемы на Hackage мне найти не удалось.
Вот и все, о чем я хотел вам сегодня поведать. Исходники к этой заметке вы найдете в этом архиве. Инструкция по их сборке находится здесь.
Дополнение: В последней версии EKG появилась возможность записи метрик в statsd, а следовательно и во все поддерживаемые им бэкенды, в том числе Graphite. Кроме того, чтобы интегрировать EKG с другими сервисами, например, напрямую с Graphite, нужно написать всего лишь ~120 строк кода.
Метки: Haskell, Функциональное программирование.
Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.