Skip to content

Commit 7b14667

Browse files
committed
feat: add dogstatsd exporter for metrics
1 parent 8997c90 commit 7b14667

File tree

5 files changed

+185
-15
lines changed

5 files changed

+185
-15
lines changed

README.md

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,55 @@
11
oc_datadog
22
=====
33

4-
An OTP library
4+
[Opencensus][oc] integration to [DataDog][dd] traces and metrics (via dogstatsd).
55

6-
Build
7-
-----
6+
## Installation
87

9-
$ rebar3 compile
8+
Rebar3:
9+
10+
```erlang
11+
{deps, [{oc_datadog, "~> 0.1.0"}]}.
12+
```
13+
14+
Mix:
15+
16+
```elixir
17+
def deps do
18+
[
19+
{:oc_datadog, "~> 0.1.0"}
20+
]
21+
end
22+
```
23+
24+
## Configuration
25+
26+
`sys.conf`:
27+
28+
```erlang
29+
[
30+
{opencensus, [
31+
{reporter, {oc_reporter_datadog, []}},
32+
{stat, [
33+
{exporters, [
34+
{oc_stat_exporter_datadog, []}
35+
]}
36+
}]
37+
]}
38+
].
39+
```
40+
41+
Elixir:
42+
43+
```elixir
44+
config :opencensus, :reporter, {:oc_reporter_datadog, []}
45+
46+
config :opencensus, :stat,
47+
exporters: [{:oc_stat_exporter_datadog, []}]
48+
```
49+
50+
## License
51+
52+
See [LICENSE](LICENSE) file.
53+
54+
[oc]: https://github.com/census-instrumentation/opencensus-erlang "Opencensus Erlang"
55+
[dd]: https://datadog.com "DataDog"

config/dev.conf

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
[
2-
{opencensus, [
3-
{reporter, {oc_reporter_datadog, []}}
4-
]}
2+
{opencensus, [
3+
{reporter, {oc_reporter_datadog, []}},
4+
{stat, [
5+
{exporters, [{oc_stat_exporter_datadog, []}]}
6+
]}
7+
]}
58
].

src/oc_datadog.app.src

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
{applications,
66
[kernel,
77
stdlib,
8-
opencensus
8+
opencensus,
9+
inets
910
]},
1011
{env,[]},
1112
{modules, []},

src/oc_reporter_datadog.erl

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
%% @author Łukasz Niemier <lukasz+opensource@niemier.pl>
2+
%%
3+
%% @doc Trace reporter for DataDog ([https://datadog.com]).
4+
%%
5+
%% == Configuration ==
6+
%%
7+
%% <ul>
8+
%% <li>`host' - address where DataDog Agent lives (default `"localhost"')</li>
9+
%% <li>`port' - port on which Agent listens for traces (default `8126')</li>
10+
%% <li>`service' - service name (default `opencensus-app')</li>
11+
%% </ul>
112
-module(oc_reporter_datadog).
213

314
-behaviour(oc_reporter).
@@ -8,47 +19,69 @@
819
-include_lib("opencensus/include/opencensus.hrl").
920
-include_lib("kernel/include/logger.hrl").
1021

22+
-define(TRACER_VERSION, "OC/0.1.0").
23+
1124
-define(DEFAULT_HOST, "localhost").
1225
-define(DEFAULT_PORT, 8126).
1326
-define(DEFAULT_SERVICE, <<"opencensus-app">>).
1427
-define(DEFAULT_TYPE, <<"custom">>).
1528

29+
-spec init(term()) -> oc_reporter:opts().
1630
init(Options) ->
1731
Host = proplists:get_value(host, Options, ?DEFAULT_HOST),
1832
Port = proplists:get_value(port, Options, ?DEFAULT_PORT),
1933
Service = proplists:get_value(service, Options, ?DEFAULT_SERVICE),
2034
Type = proplists:get_value(type, Options, ?DEFAULT_TYPE),
2135
#{host => Host, port => Port, service => Service, type => Type}.
2236

23-
report(Spans, #{service := Service, host := Host, port := Port, type := Type}) ->
37+
-spec report(nonempty_list(opencensus:span()), oc_reporter:opts()) -> ok.
38+
report(Spans, #{
39+
service := Service,
40+
host := Host,
41+
port := Port,
42+
type := Type}) ->
2443
Sorted = lists:sort(fun(A, B) ->
2544
A#span.trace_id =< B#span.trace_id
2645
end, Spans),
2746
Grouped = group(Sorted),
2847
DSpans = [[build_span(S, Service, Type) || S <- Trace] || Trace <- Grouped],
2948

30-
3149
try jsx:encode(DSpans) of
3250
JSON ->
3351
Address = io_lib:format('http://~s:~B/v0.3/traces', [Host, Port]),
52+
Headers = [
53+
{"Datadog-Meta-Lang", "erlang"},
54+
{"Datadog-Meta-Lang-Version", lang_version()},
55+
{"Datadog-Meta-Lang-Interpreter", interpreter_version()},
56+
{"Datadog-Meta-Tracer-Version", ?TRACER_VERSION}
57+
],
3458
case httpc:request(
3559
put,
36-
{Address, [], "application/json", JSON},
60+
{Address, Headers, "application/json", JSON},
3761
[],
3862
[]
3963
) of
4064
{ok, {{_, Code, _}, _, _}} when Code >= 200; Code =< 299 ->
4165
ok;
4266
{ok, {{_, Code, _}, _, Message}} ->
43-
?LOG_ERROR("DD: Unable to send spans, DD reported an error: ~p : ~p",
67+
?LOG_ERROR("DD: Unable to send spans, DD reported an error: ~p: ~p",
4468
[Code, Message]);
4569
{error, Reason} ->
46-
?LOG_ERROR("DD: Unable to send spans, client error: ~p", [Reason])
70+
?LOG_ERROR("DD: Unable to send spans, client error: ~p",
71+
[Reason])
4772
end
4873
catch
49-
error:_ -> throw(datadog_json_encode_error)
74+
error:Error ->
75+
?LOG_ERROR("DD: Can't spans encode to json: ~p", [Error])
5076
end.
5177

78+
lang_version() ->
79+
erlang:system_info(otp_version).
80+
81+
interpreter_version() ->
82+
io_lib:format('~s-~s', [erlang:system_info(version),
83+
erlang:system_info(system_architecture)]).
84+
5285
group([]) -> [];
5386
group([First | Spans]) -> group(Spans, [[First]]).
5487

@@ -66,7 +99,8 @@ build_span(Span, Service, Type) ->
6699
<<"resource">> => Span#span.name,
67100
<<"service">> => to_tag(Service),
68101
<<"start">> => wts:to_absolute(Span#span.start_time) * 1000,
69-
<<"duration">> => wts:duration(Span#span.start_time, Span#span.end_time) * 1000,
102+
<<"duration">> =>
103+
wts:duration(Span#span.start_time, Span#span.end_time) * 1000,
70104
<<"type">> => to_tag(Type),
71105
<<"meta">> => to_meta(Span#span.attributes)}.
72106

src/oc_stat_exporter_datadog.erl

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
%% @author Łukasz Niemier <lukasz+opensource@niemier.pl>
2+
%%
3+
%% @doc Trace reporter for DataDog ([https://datadog.com]).
4+
%%
5+
%% == Configuration ==
6+
%%
7+
%% <ul>
8+
%% <li>`host' - address where DataDog Agent lives (default `"localhost"')</li>
9+
%% <li>`port' - port on which Agent listens for statsd metrics (default `8125')</li>
10+
%% </ul>
11+
-module(oc_stat_exporter_datadog).
12+
13+
-behaviour(oc_stat_exporter).
14+
15+
-export([export/2]).
16+
17+
-define(DEFAULT_HOST, "localhost").
18+
-define(DEFAULT_PORT, 8125).
19+
20+
-include_lib("kernel/include/logger.hrl").
21+
22+
export(ViewData, Options) ->
23+
Host = proplists:get_value(host, Options, ?DEFAULT_HOST),
24+
Port = proplists:get_value(port, Options, ?DEFAULT_PORT),
25+
{ok, Socket} = gen_udp:open(0, [{active, false}]),
26+
Packet = build_packet(ViewData),
27+
ok = gen_udp:send(Socket, Host, Port, Packet),
28+
ok = gen_udp:close(Socket),
29+
ok.
30+
31+
build_packet(Data) when is_list(Data) ->
32+
List = [build_packet(Entry) || Entry <- Data],
33+
lists:join($\n, List);
34+
build_packet(#{name := Name,
35+
ctags := CTags,
36+
tags := Tags,
37+
data := #{type := Type,
38+
rows := Rows}}) ->
39+
Key = to_key(Name),
40+
List = [ build_rows(Key, Type, CTags, Tags, Row) || Row <- Rows ],
41+
lists:join($\n, List).
42+
43+
build_tags(Tags, TagsV, CTags) ->
44+
build_tags(maps:merge(CTags, maps:from_list(lists:zip(Tags, TagsV)))).
45+
46+
build_tags(Map) when is_map(Map) -> build_tags(maps:to_list(Map));
47+
build_tags([]) -> [];
48+
build_tags(Tags) ->
49+
List = [[to_key(Key), $:, Value]
50+
|| {Key, Value} <- Tags],
51+
["|#", lists:join($,, List)].
52+
53+
to_key(Atom) when is_atom(Atom) ->
54+
erlang:atom_to_binary(Atom, utf8);
55+
to_key(Value) -> Value.
56+
57+
build_rows(Name, Type, CTags, Tags, #{tags := TagsV, value := Value}) ->
58+
TagList = build_tags(Tags, TagsV, CTags),
59+
Metrics = [[Name, Stat] || Stat <- build_row(Type, Value, TagList) ],
60+
lists:join($\n, Metrics).
61+
62+
build_row(sum, #{count := Count,
63+
mean := Mean,
64+
sum := Sum}, Tags) ->
65+
[
66+
[".count:", format_num(Count), "|g", Tags],
67+
[".mean:", format_num(Mean), "|g", Tags],
68+
[".sum:", format_num(Sum), "|g", Tags]
69+
];
70+
build_row(distribution, #{buckets := Buckets} = Data, Tags) ->
71+
BucketRows = [ bucket_row(Bucket, Tags) || Bucket <- Buckets ],
72+
build_row(sum, Data, Tags) ++ BucketRows;
73+
build_row(_Type, Value, Tags) ->
74+
[[$:, format_num(Value), "|g", Tags]].
75+
76+
bucket_row({Bound, Count}, Tags) when is_integer(Count) ->
77+
[$:, format_num(Count), "|g", tags_append(Tags, ["le:", format_num(Bound)])].
78+
79+
tags_append([], Value) -> ["|#", Value];
80+
tags_append(List, Value) -> [List, $,, Value].
81+
82+
format_num(infinity) -> <<"infinity">>;
83+
format_num(Integer) when is_integer(Integer) ->
84+
erlang:integer_to_binary(Integer);
85+
format_num(Float) when is_float(Float) ->
86+
erlang:float_to_binary(Float, [{decimal, 5}, compact]).

0 commit comments

Comments
 (0)