Ранее мы выяснили, как на 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 строк кода .