From 2614ad9e4d9c6c1469ec3d48aaabadfa57eeee01 Mon Sep 17 00:00:00 2001 From: Anthony Eden Date: Mon, 9 Sep 2013 15:07:12 +0200 Subject: [PATCH] Implement a metrics HTTP endpoint. --- README.md | 8 ++++ ebin/erldns.app | 3 +- rebar.config | 1 + src/erldns.erl | 2 + src/erldns_app.erl | 6 +-- src/erldns_metrics.erl | 58 ++++++++++++++++++++++++++++ src/erldns_metrics_root_handler.erl | 59 +++++++++++++++++++++++++++++ src/erldns_sup.erl | 1 + 8 files changed, 134 insertions(+), 4 deletions(-) create mode 100644 src/erldns_metrics.erl create mode 100644 src/erldns_metrics_root_handler.erl diff --git a/README.md b/README.md index d37f5648..0d204065 100644 --- a/README.md +++ b/README.md @@ -83,3 +83,11 @@ erldns_zone_cache:put_zone({ } ]}). ``` + +## Metrics + +Folsom is used to gather runtime metrics and statistics. There is an HTTP server included that produces a JSON report containing these metrics. Here's an example script that shows how to get the output with curl and pass through Python to format it in a pretty fashion. + +```sh +curl -s http://localhost:8082/ -H "Accept: application/json" | python -mjson.tool +``` diff --git a/ebin/erldns.app b/ebin/erldns.app index 036e6124..d65ed1f9 100644 --- a/ebin/erldns.app +++ b/ebin/erldns.app @@ -1,12 +1,13 @@ {application,erldns, [{description,"Erlang Authoritative DNS Server"}, - {vsn,"5321c5a"}, + {vsn,"8bdd3db"}, {mod,{erldns_app,[]}}, {applications,[kernel,stdlib,inets,crypto,ssl,folsom]}, {start_phases,[{post_start,[]}]}, {modules,[erldns,erldns_app,erldns_axfr,erldns_config, erldns_dnssec,erldns_edns,erldns_encoder, erldns_event_logger,erldns_events,erldns_handler, + erldns_metrics,erldns_metrics_root_handler, erldns_packet_cache,erldns_query_throttle, erldns_records,erldns_resolver,erldns_server_sup, erldns_sup,erldns_tcp_server,erldns_txt, diff --git a/rebar.config b/rebar.config index 4368f4f1..3424b79c 100644 --- a/rebar.config +++ b/rebar.config @@ -14,6 +14,7 @@ {deps, [ {lager, ".*", {git, "git://github.com/basho/lager.git", "HEAD"}}, {folsom, ".*", {git, "git://github.com/boundary/folsom.git", "HEAD"}}, + {cowboy, ".*", {git, "git://github.com/extend/cowboy.git", {tag, "0.8.6"}}}, {poolboy, ".*", {git, "git://github.com/devinus/poolboy.git", "HEAD"}}, {jsx, ".*", {git, "git://github.com/talentdeficit/jsx.git", "HEAD"}}, {dns, ".*", {git, "git://github.com/aeden/dns_erlang.git", "HEAD"}}, diff --git a/src/erldns.erl b/src/erldns.erl index 29704e90..e752c139 100644 --- a/src/erldns.erl +++ b/src/erldns.erl @@ -8,4 +8,6 @@ start() -> ssl:start(), lager:start(), folsom:start(), + application:start(ranch), + application:start(cowboy), application:start(erldns). diff --git a/src/erldns_app.erl b/src/erldns_app.erl index 23f843ba..1a2792e2 100644 --- a/src/erldns_app.erl +++ b/src/erldns_app.erl @@ -4,7 +4,7 @@ % Application hooks -export([start/2, start_phase/3, stop/1]). --export([get_metrics/0, get_stats/0]). +-export([metrics/0, stats/0]). start(_Type, _Args) -> define_metrics(), @@ -45,13 +45,13 @@ stop(_State) -> lager:info("Stop erldns application"), ok. -get_metrics() -> +metrics() -> lists:map( fun(Name) -> {Name, folsom_metrics:get_metric_value(Name)} end, folsom_metrics:get_metrics()). -get_stats() -> +stats() -> Histograms = [udp_handoff_histogram, tcp_handoff_histogram, request_handled_histogram], lists:map( fun(Name) -> diff --git a/src/erldns_metrics.erl b/src/erldns_metrics.erl new file mode 100644 index 00000000..1934f364 --- /dev/null +++ b/src/erldns_metrics.erl @@ -0,0 +1,58 @@ +-module(erldns_metrics). + +-behavior(gen_server). + +-export([start_link/0]). + +-define(DEFAULT_PORT, 8082). + +% Gen server hooks +-export([init/1, + handle_call/3, + handle_cast/2, + handle_info/2, + terminate/2, + code_change/3 + ]). + +-record(state, {}). + +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). + +init([]) -> + lager:debug("Starting ~p", [?MODULE]), + + Dispatch = cowboy_router:compile( + [ + {'_', + [ + {"/", erldns_metrics_root_handler, []} + ] + } + ] + ), + + {ok, _} = cowboy:start_http(http, 10, [{port, port()}], [{env, [{dispatch, Dispatch}]}]), + + {ok, #state{}}. + +handle_call(_Message, _From, State) -> + {reply, ok, State}. +handle_cast(_, State) -> + {noreply, State}. +handle_info(_, State) -> + {noreply, State}. +terminate(_, _) -> + ok. +code_change(_PreviousVersion, State, _Extra) -> + {ok, State}. + +port() -> + proplists:get_value(port, metrics_env(), ?DEFAULT_PORT). + +metrics_env() -> + case application:get_env(erldns, metrics) of + {ok, MetricsEnv} -> MetricsEnv; + _ -> [] + end. diff --git a/src/erldns_metrics_root_handler.erl b/src/erldns_metrics_root_handler.erl new file mode 100644 index 00000000..70a0cc57 --- /dev/null +++ b/src/erldns_metrics_root_handler.erl @@ -0,0 +1,59 @@ +-module(erldns_metrics_root_handler). + +-export([init/3]). +-export([content_types_provided/2]). +-export([to_html/2, to_json/2, to_text/2]). + +-export([filter_stats/1]). + +init(_Transport, _Req, []) -> + {upgrade, protocol, cowboy_rest}. + +content_types_provided(Req, State) -> + {[ + {<<"text/html">>, to_html}, + {<<"text/plain">>, to_text}, + {<<"application/json">>, to_json} + ], Req, State}. + +to_html(Req, State) -> + {<<"erldns metrics">>, Req, State}. + +to_text(Req, State) -> + {<<"erldns metrics">>, Req, State}. + +to_json(Req, State) -> + Body = jsx:encode([{<<"erldns">>, + [ + {<<"metrics">>, erldns_app:metrics()}, + {<<"stats">>, filter_stats(erldns_app:stats())} + ] + }]), + {Body, Req, State}. + +% Functions to clean up the stats so they can be returned as JSON. +filter_stats(Stats) -> + filter_stats(Stats, []). + +filter_stats([], FilteredStats) -> + FilteredStats; +filter_stats([{Name, Stats}|Rest], FilteredStats) -> + filter_stats(Rest, FilteredStats ++ [{Name, filter_stat_set(Stats)}]). + +filter_stat_set(Stats) -> + filter_stat_set(Stats, []). + +filter_stat_set([], FilteredStatSet) -> + FilteredStatSet; +filter_stat_set([{percentile, Percentiles}|Rest], FilteredStatSet) -> + filter_stat_set(Rest, FilteredStatSet ++ [{percentile, keys_to_strings(Percentiles)}]); +filter_stat_set([{histogram, _}|Rest], FilteredStatSet) -> + filter_stat_set(Rest, FilteredStatSet); +filter_stat_set([Pair|Rest], FilteredStatSet) -> + filter_stat_set(Rest, FilteredStatSet ++ [Pair]). + +keys_to_strings(Pairs) -> + lists:map( + fun({K, V}) -> + {list_to_binary(lists:flatten(io_lib:format("~p", [K]))), V} + end, Pairs). diff --git a/src/erldns_sup.erl b/src/erldns_sup.erl index 709373e2..4f4aa4dc 100644 --- a/src/erldns_sup.erl +++ b/src/erldns_sup.erl @@ -32,6 +32,7 @@ init(_Args) -> ?CHILD(erldns_packet_cache, worker, []), ?CHILD(erldns_query_throttle, worker, []), ?CHILD(erldns_handler, worker, []), + ?CHILD(erldns_metrics, worker, []), ?CHILD(sample_custom_handler, worker, []) ],