├── .github
└── workflows
│ └── erlang.yml
├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── bin
└── rebar3
├── doc
├── edoc-info
├── statsderl.md
├── statsderl_app.md
├── statsderl_pool.md
├── statsderl_protocol.md
├── statsderl_server.md
├── statsderl_sup.md
├── statsderl_transform.md
├── statsderl_udp.md
└── statsderl_utils.md
├── include
└── statsderl.hrl
├── rebar.config
├── rebar.config.script
├── rebar.lock
├── src
├── statsderl.app.src
├── statsderl.erl
├── statsderl_app.erl
├── statsderl_client.erl
├── statsderl_protocol.erl
├── statsderl_sample.erl
├── statsderl_sup.erl
├── statsderl_transform.erl
└── statsderl_utils.erl
└── test
├── statsderl_profile.erl
├── statsderl_tests.erl
├── statsderl_transform_tests.erl
└── statsderl_utils_tests.erl
/.github/workflows/erlang.yml:
--------------------------------------------------------------------------------
1 | name: Erlang CI
2 | on: [push]
3 | jobs:
4 | build:
5 | runs-on: ubuntu-latest
6 | strategy:
7 | matrix:
8 | erlang: [21, 22, 23, 24, 25]
9 |
10 | container:
11 | image: erlang:${{ matrix.erlang }}
12 |
13 | steps:
14 | - uses: actions/checkout@v1
15 | - name: Compile
16 | run: make compile
17 | - name: Run xref
18 | run: make xref
19 | - name: Run eunit
20 | run: make eunit
21 | - name: Run dialyzer
22 | run: make dialyzer
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | doc/README.md
2 | erl_crash.dump
3 | fprofx.*
4 | _build
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2011-2020 Louis-Philippe Gauthier
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | CACHEGRIND=qcachegrind
2 | REBAR3=$(shell which rebar3)
3 | ifeq ($(REBAR3),)
4 | REBAR3=./bin/rebar3
5 | endif
6 |
7 | all: compile
8 |
9 | clean:
10 | @echo "Running rebar3 clean..."
11 | @$(REBAR3) clean -a
12 |
13 | compile:
14 | @echo "Running rebar3 compile..."
15 | @$(REBAR3) as compile compile
16 |
17 | dialyzer:
18 | @echo "Running rebar3 dialyze..."
19 | @$(REBAR3) dialyzer
20 |
21 | edoc:
22 | @echo "Running rebar3 edoc..."
23 | @$(REBAR3) as edoc edoc
24 |
25 | eunit:
26 | @echo "Running rebar3 eunit..."
27 | @$(REBAR3) do eunit -cv, cover -v
28 |
29 | profile:
30 | @echo "Profiling..."
31 | @$(REBAR3) as test compile
32 | @erl -noshell \
33 | -pa _build/test/lib/*/ebin \
34 | -pa _build/test/lib/*/test \
35 | -eval 'statsderl_profile:fprofx()' \
36 | -eval 'init:stop()'
37 | @_build/test/lib/fprofx/erlgrindx -p fprofx.analysis
38 | @$(CACHEGRIND) fprofx.cgrind
39 |
40 | test: xref eunit dialyzer
41 |
42 | xref:
43 | @echo "Running rebar3 xref..."
44 | @$(REBAR3) xref
45 |
46 | .PHONY: clean compile dialyzer edoc eunit profile xref
47 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # statsderl
2 |
3 | High Performance Erlang StatsD Client
4 |
5 | 
6 |
7 | ### Requirements
8 |
9 | * Erlang 16.0 +
10 |
11 | ### Features
12 |
13 | * Performance optimized
14 | * Parse transform
15 |
16 | ## API
17 | Function Index
18 |
19 | #### Environment variables:
20 |
21 |
22 |
23 | Name |
24 | Type |
25 | Default |
26 | Description |
27 |
28 |
29 | base_key |
30 | base_key() |
31 | undefined |
32 | key prefix |
33 |
34 |
35 | hostname |
36 | inet:ip_address() | inet:hostname() | binary() |
37 | {127,0,0,1} |
38 | server hostname |
39 |
40 |
41 | port |
42 | inet:port_number() |
43 | 8125 |
44 | server port |
45 |
46 |
47 | pool_size |
48 | pos_integer() |
49 | 4 |
50 | pool size |
51 |
52 |
53 |
54 | #### base_key options:
55 |
56 |
57 |
58 | Name |
59 | Source |
60 | Description |
61 | Example |
62 |
63 |
64 | hostname |
65 | inet:gethostname/0 |
66 | use the hostname |
67 | "h033" |
68 |
69 |
70 | name |
71 | erl -name |
72 | use the long node name |
73 | "nonode@nohost" |
74 |
75 |
76 | sname |
77 | erl -sname |
78 | use the short node name |
79 | "nonode" |
80 |
81 |
82 |
83 | ## Parse Transform
84 |
85 | `statsderl_transform` is used to pre-compute (at compile time) the scaled sample rate and `StatsD` packet.
86 |
87 | ```erlang
88 | -compile({parse_transform, statsderl_transform}).
89 | ```
90 | ## Examples
91 |
92 | ```erlang
93 | 1> statsderl_app:start().
94 | ok.
95 |
96 | 2> statsderl:counter(["test", $., "counter"], 1, 0.23).
97 | ok.
98 |
99 | 3> statsderl:decrement("test.decrement", 1, 0.5).
100 | ok.
101 |
102 | 4> statsderl:gauge([<<"test">>, $., "gauge"], 333, 1.0).
103 | ok.
104 |
105 | 5> statsderl:gauge_decrement([<<"test.gauge_decrement">>], 15, 0.001).
106 | ok.
107 |
108 | 6> statsderl:gauge_increment(<<"test.gauge_increment">>, 32, 1).
109 | ok.
110 |
111 | 7> statsderl:increment(<<"test.increment">>, 1, 1).
112 | ok.
113 |
114 | 8> statsderl:timing("test.timing", 5, 0.5).
115 | ok.
116 |
117 | 9> statsderl:timing_fun(<<"test.timing_fun">>, fun() -> timer:sleep(100) end, 0.5).
118 | ok.
119 |
120 | 10> Timestamp = os:timestamp().
121 | {1448,591778,258983}
122 |
123 | 11> statsderl:timing_now("test.timing_now", Timestamp, 0.15).
124 | ok.
125 |
126 | 12> statsderl_app:stop().
127 | ok.
128 | ```
129 |
130 | ## Tests
131 |
132 | ```makefile
133 | make test
134 | ```
135 |
136 | ## License
137 |
138 | ```license
139 | The MIT License (MIT)
140 |
141 | Copyright (c) 2011-2020 Louis-Philippe Gauthier
142 |
143 | Permission is hereby granted, free of charge, to any person obtaining a copy
144 | of this software and associated documentation files (the "Software"), to deal
145 | in the Software without restriction, including without limitation the rights
146 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
147 | copies of the Software, and to permit persons to whom the Software is
148 | furnished to do so, subject to the following conditions:
149 |
150 | The above copyright notice and this permission notice shall be included in all
151 | copies or substantial portions of the Software.
152 |
153 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
154 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
155 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
156 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
157 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
158 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
159 | SOFTWARE.
160 | ```
161 |
--------------------------------------------------------------------------------
/bin/rebar3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lpgauth/statsderl/50722505b991ca6da3d220c5006c23c6837a0a32/bin/rebar3
--------------------------------------------------------------------------------
/doc/edoc-info:
--------------------------------------------------------------------------------
1 | %% encoding: UTF-8
2 | {application,statsderl}.
3 | {modules,[statsderl,statsderl_app,statsderl_pool,statsderl_protocol,
4 | statsderl_server,statsderl_sup,statsderl_transform,statsderl_utils]}.
5 |
--------------------------------------------------------------------------------
/doc/statsderl.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module statsderl #
4 | * [Data Types](#types)
5 | * [Function Index](#index)
6 | * [Function Details](#functions)
7 |
8 |
9 |
10 | ## Data Types ##
11 |
12 |
13 |
14 |
15 | ### key() ###
16 |
17 |
18 |
19 | key() = iodata()
20 |
21 |
22 |
23 |
24 |
25 | ### sample_rate() ###
26 |
27 |
28 |
29 | sample_rate() = number()
30 |
31 |
32 |
33 |
34 |
35 | ### value() ###
36 |
37 |
38 |
39 | value() = number()
40 |
41 |
42 |
43 |
44 | ## Function Index ##
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | ## Function Details ##
53 |
54 |
55 |
56 | ### counter/3 ###
57 |
58 |
59 | counter(Key::key(), Value::value(), Rate::sample_rate()) -> ok
60 |
61 |
62 |
63 |
64 |
65 | ### decrement/3 ###
66 |
67 |
68 | decrement(Key::key(), Value::value(), Rate::sample_rate()) -> ok
69 |
70 |
71 |
72 |
73 |
74 | ### gauge/3 ###
75 |
76 |
77 | gauge(Key::key(), Value::value(), Rate::sample_rate()) -> ok
78 |
79 |
80 |
81 |
82 |
83 | ### gauge_decrement/3 ###
84 |
85 |
86 | gauge_decrement(Key::key(), Value::value(), Rate::sample_rate()) -> ok
87 |
88 |
89 |
90 |
91 |
92 | ### gauge_increment/3 ###
93 |
94 |
95 | gauge_increment(Key::key(), Value::value(), Rate::sample_rate()) -> ok
96 |
97 |
98 |
99 |
100 |
101 | ### increment/3 ###
102 |
103 |
104 | increment(Key::key(), Value::value(), Rate::sample_rate()) -> ok
105 |
106 |
107 |
108 |
109 |
110 | ### timing/3 ###
111 |
112 |
113 | timing(Key::key(), Value::value(), Rate::sample_rate()) -> ok
114 |
115 |
116 |
117 |
118 |
119 | ### timing_fun/3 ###
120 |
121 |
122 | timing_fun(Key::key(), Fun::function(), Rate::sample_rate()) -> any()
123 |
124 |
125 |
126 |
127 |
128 | ### timing_now/3 ###
129 |
130 |
131 | timing_now(Key::key(), Timestamp::erlang:timestamp(), Rate::sample_rate()) -> ok
132 |
133 |
134 |
135 |
136 |
137 | ### timing_now_us/3 ###
138 |
139 |
140 | timing_now_us(Key::key(), Timestamp::erlang:timestamp(), Rate::sample_rate()) -> ok
141 |
142 |
143 |
144 |
--------------------------------------------------------------------------------
/doc/statsderl_app.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module statsderl_app #
4 | * [Function Index](#index)
5 | * [Function Details](#functions)
6 |
7 | __Behaviours:__ [`application`](application.md).
8 |
9 |
10 |
11 | ## Function Index ##
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | ## Function Details ##
20 |
21 |
22 |
23 | ### start/0 ###
24 |
25 |
26 | start() -> {ok, [atom()]} | {error, term()}
27 |
28 |
29 |
30 |
31 |
32 | ### start/2 ###
33 |
34 |
35 | start(StartType::application:start_type(), StartArgs::term()) -> {ok, pid()}
36 |
37 |
38 |
39 |
40 |
41 | ### stop/0 ###
42 |
43 |
44 | stop() -> ok | {error, {not_started, statsderl}}
45 |
46 |
47 |
48 |
49 |
50 | ### stop/1 ###
51 |
52 |
53 | stop(State::term()) -> ok
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/doc/statsderl_pool.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module statsderl_pool #
4 | * [Data Types](#types)
5 | * [Function Index](#index)
6 | * [Function Details](#functions)
7 |
8 |
9 |
10 | ## Data Types ##
11 |
12 |
13 |
14 |
15 | ### key() ###
16 |
17 |
18 |
19 | key() = iodata()
20 |
21 |
22 |
23 |
24 |
25 | ### operation() ###
26 |
27 |
28 |
29 | operation() = {cast, iodata()} | {counter, key(), value(), sample_rate()} | {gauge, key(), value()} | {gauge_decrement, key(), value()} | {gauge_increment, key(), value()} | {timing, key(), value()} | {timing_now, key(), erlang:timestamp()} | {timing_now_us, key(), erlang:timestamp()}
30 |
31 |
32 |
33 |
34 |
35 | ### pool_size() ###
36 |
37 |
38 |
39 | pool_size() = pos_integer()
40 |
41 |
42 |
43 |
44 |
45 | ### sample_rate() ###
46 |
47 |
48 |
49 | sample_rate() = number()
50 |
51 |
52 |
53 |
54 |
55 | ### value() ###
56 |
57 |
58 |
59 | value() = number()
60 |
61 |
62 |
63 |
64 | ## Function Index ##
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | ## Function Details ##
73 |
74 |
75 |
76 | ### init/0 ###
77 |
78 |
79 | init() -> ok
80 |
81 |
82 |
83 |
84 |
85 | ### sample/2 ###
86 |
87 |
88 | sample(Rate::sample_rate(), Operation::operation()) -> ok
89 |
90 |
91 |
92 |
93 |
94 | ### sample_scaled/2 ###
95 |
96 |
97 | sample_scaled(RateInt::non_neg_integer(), Operation::operation()) -> ok
98 |
99 |
100 |
101 |
102 |
103 | ### server_name/1 ###
104 |
105 |
106 | server_name(N::pos_integer()) -> atom()
107 |
108 |
109 |
110 |
111 |
112 | ### size/0 ###
113 |
114 |
115 | size() -> pool_size()
116 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/doc/statsderl_protocol.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module statsderl_protocol #
4 | * [Data Types](#types)
5 | * [Function Index](#index)
6 | * [Function Details](#functions)
7 |
8 |
9 |
10 | ## Data Types ##
11 |
12 |
13 |
14 |
15 | ### key() ###
16 |
17 |
18 |
19 | key() = iodata()
20 |
21 |
22 |
23 |
24 |
25 | ### operation() ###
26 |
27 |
28 |
29 | operation() = {cast, iodata()} | {counter, key(), value(), sample_rate()} | {gauge, key(), value()} | {gauge_decrement, key(), value()} | {gauge_increment, key(), value()} | {timing, key(), value()} | {timing_now, key(), erlang:timestamp()} | {timing_now_us, key(), erlang:timestamp()}
30 |
31 |
32 |
33 |
34 |
35 | ### sample_rate() ###
36 |
37 |
38 |
39 | sample_rate() = number()
40 |
41 |
42 |
43 |
44 |
45 | ### value() ###
46 |
47 |
48 |
49 | value() = number()
50 |
51 |
52 |
53 |
54 | ## Function Index ##
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | ## Function Details ##
63 |
64 |
65 |
66 | ### encode/1 ###
67 |
68 |
69 | encode(X1::operation()) -> iodata()
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/doc/statsderl_server.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module statsderl_server #
4 | * [Function Index](#index)
5 | * [Function Details](#functions)
6 |
7 | __Behaviours:__ [`metal`](metal.md).
8 |
9 |
10 |
11 | ## Function Index ##
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | ## Function Details ##
20 |
21 |
22 |
23 | ### handle_msg/2 ###
24 |
25 |
26 | handle_msg(X1::term(), State::term()) -> {ok, term()}
27 |
28 |
29 |
30 |
31 |
32 | ### init/3 ###
33 |
34 |
35 | init(Name::atom(), Parent::pid(), Opts::term()) -> {ok, term()} | {stop, atom()}
36 |
37 |
38 |
39 |
40 |
41 | ### start_link/1 ###
42 |
43 |
44 | start_link(Name::atom()) -> {ok, pid()}
45 |
46 |
47 |
48 |
49 |
50 | ### terminate/2 ###
51 |
52 |
53 | terminate(Reason::term(), State::term()) -> ok
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/doc/statsderl_sup.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module statsderl_sup #
4 | * [Function Index](#index)
5 | * [Function Details](#functions)
6 |
7 | __Behaviours:__ [`supervisor`](supervisor.md).
8 |
9 |
10 |
11 | ## Function Index ##
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | ## Function Details ##
20 |
21 |
22 |
23 | ### init/1 ###
24 |
25 |
26 | init(Args::[]) -> {ok, {{one_for_one, 5, 10}, [supervisor:child_spec()]}}
27 |
28 |
29 |
30 |
31 |
32 | ### start_link/0 ###
33 |
34 |
35 | start_link() -> {ok, pid()}
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/doc/statsderl_transform.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module statsderl_transform #
4 | * [Data Types](#types)
5 | * [Function Index](#index)
6 | * [Function Details](#functions)
7 |
8 |
9 |
10 | ## Data Types ##
11 |
12 |
13 |
14 |
15 | ### forms() ###
16 |
17 |
18 |
19 | forms() = [erl_parse:abstract_form() | erl_parse:form_info()]
20 |
21 |
22 |
23 |
24 | ## Function Index ##
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | ## Function Details ##
33 |
34 |
35 |
36 | ### parse_transform/2 ###
37 |
38 |
39 | parse_transform(Forms::forms(), Options::[compile:option()]) -> forms()
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/doc/statsderl_udp.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module statsderl_udp #
4 | * [Function Index](#index)
5 | * [Function Details](#functions)
6 |
7 |
8 |
9 | ## Function Index ##
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | ## Function Details ##
18 |
19 |
20 |
21 | ### header/2 ###
22 |
23 |
24 | header(IP::inet:ip_address(), Port::inet:port_number()) -> iodata()
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/doc/statsderl_utils.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module statsderl_utils #
4 | * [Data Types](#types)
5 | * [Function Index](#index)
6 | * [Function Details](#functions)
7 |
8 |
9 |
10 | ## Data Types ##
11 |
12 |
13 |
14 |
15 | ### base_key() ###
16 |
17 |
18 |
19 | base_key() = base_key_part() | [base_key_part()]
20 |
21 |
22 |
23 |
24 |
25 | ### base_key_part() ###
26 |
27 |
28 |
29 | base_key_part() = hostname | name | sname | undefined | iodata()
30 |
31 |
32 |
33 |
34 | ## Function Index ##
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | ## Function Details ##
43 |
44 |
45 |
46 | ### base_key/1 ###
47 |
48 |
49 | base_key(Key::base_key()) -> iodata()
50 |
51 |
52 |
53 |
54 |
55 | ### error_msg/2 ###
56 |
57 |
58 | error_msg(Format::string(), Data::[term()]) -> ok
59 |
60 |
61 |
62 |
63 |
64 | ### timestamp/0 ###
65 |
66 |
67 | timestamp() -> erlang:timestamp()
68 |
69 |
70 |
71 |
72 |
73 | ### timing_now/1 ###
74 |
75 |
76 | timing_now(Timestamp::erlang:timestamp()) -> non_neg_integer()
77 |
78 |
79 |
80 |
81 |
82 | ### timing_now_us/1 ###
83 |
84 |
85 | timing_now_us(Timestamp::erlang:timestamp()) -> non_neg_integer()
86 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/include/statsderl.hrl:
--------------------------------------------------------------------------------
1 | %% macros
2 | -define(APP, statsderl).
3 | -define(CHILD(Name, Mod), {Name, {Mod, start_link, [Name]}, permanent, 5000, worker, [Mod]}).
4 | -define(CLIENT, statsderl_client).
5 | -define(MAX_UNSIGNED_INT_32, 4294967295).
6 |
7 | %% env vars
8 | -define(ENV(Key, Default), application:get_env(?APP, Key, Default)).
9 | -define(ENV_BASEKEY, base_key).
10 | -define(ENV_HOSTNAME, hostname).
11 | -define(ENV_PORT, port).
12 | -define(ENV_VARS, [?ENV_BASEKEY, ?ENV_HOSTNAME, ?ENV_PORT]).
13 |
14 | %% defaults
15 | -define(DEFAULT_BACKLOG_SIZE, 4096).
16 | -define(DEFAULT_BASEKEY, undefined).
17 | -define(DEFAULT_HOSTNAME, "127.0.0.1").
18 | -define(DEFAULT_POOL_SIZE, 4).
19 | -define(DEFAULT_PORT, 8125).
20 |
21 | %% types
22 | -type base_key() :: base_key_part() | [base_key_part()].
23 | -type base_key_part() :: hostname | name | sname | undefined | iodata().
24 | -type key() :: iodata().
25 | -type operation() :: {cast, iodata()} |
26 | {counter, key(), value(), sample_rate()} |
27 | {gauge, key(), value()} |
28 | {gauge_decrement, key(), value()} |
29 | {gauge_increment, key(), value()} |
30 | {timing, key(), value()} |
31 | {timing_now, key(), erlang:timestamp()} |
32 | {timing_now_us, key(), erlang:timestamp()}.
33 | -type pool_size() :: pos_integer().
34 | -type sample_rate() :: number().
35 | -type value() :: number().
36 |
--------------------------------------------------------------------------------
/rebar.config:
--------------------------------------------------------------------------------
1 | {cover_export_enabled, true}.
2 |
3 | {deps, [
4 | {granderl, "0.1.5"},
5 | {parse_trans, "3.0.0"},
6 | {shackle, {git, "https://github.com/lpgauth/shackle.git", {tag, "0.6.16"}}}
7 | ]}.
8 |
9 | {edoc_opts, [
10 | {app_default, "http://www.erlang.org/doc/man"},
11 | {doclet, edown_doclet},
12 | {image, ""},
13 | {includes, ["include"]},
14 | {preprocess, true},
15 | {stylesheet, ""},
16 | {title, "statsderl"}
17 | ]}.
18 |
19 | {erl_opts, [
20 | debug_info
21 | ]}.
22 |
23 | {plugins, [rebar3_hex]}.
24 |
25 | {profiles, [
26 | {compile, [
27 | {erl_opts, [
28 | warnings_as_errors,
29 | warn_export_all,
30 | warn_export_vars,
31 | warn_missing_spec,
32 | warn_obsolete_guard,
33 | warn_shadow_vars,
34 | warn_untyped_record,
35 | warn_unused_import,
36 | warn_unused_vars
37 | ]}
38 | ]},
39 | {edoc, [
40 | {deps, [
41 | {edown,
42 | {git, "https://github.com/uwiger/edown.git", {tag, "0.7"}}}
43 | ]}
44 | ]},
45 | {test, [
46 | {deps, [
47 | {meck,
48 | {git, "https://github.com/eproxus/meck.git", {tag, "0.8.7"}}},
49 | {fprofx,
50 | {git, "https://github.com/ransomr/fprofx.git", {branch, "master"}}}
51 | ]}
52 | ]}
53 | ]}.
54 |
55 | {xref_checks, [
56 | deprecated_functions,
57 | deprecated_function_calls,
58 | locals_not_used,
59 | undefined_function_calls
60 | ]}.
61 |
--------------------------------------------------------------------------------
/rebar.config.script:
--------------------------------------------------------------------------------
1 | case erlang:function_exported(rebar3, main, 1) of
2 | true ->
3 | CONFIG;
4 | false ->
5 | [{deps, [
6 | {granderl, ".*",
7 | {git, "https://github.com/tokenrove/granderl.git", {tag, "v0.1.5"}}},
8 | {parse_trans, ".*",
9 | {git, "https://github.com/uwiger/parse_trans.git", {tag, "3.0.0"}}},
10 | {shackle, ".*",
11 | {git, "https://github.com/lpgauth/shackle.git", {tag, "0.6.16"}}}
12 | ]} | lists:keydelete(deps, 1, CONFIG)]
13 | end.
14 |
--------------------------------------------------------------------------------
/rebar.lock:
--------------------------------------------------------------------------------
1 | {"1.2.0",
2 | [{<<"foil">>,{pkg,<<"foil">>,<<"0.1.0">>},0},
3 | {<<"granderl">>,{pkg,<<"granderl">>,<<"0.1.5">>},0},
4 | {<<"metal">>,{pkg,<<"metal">>,<<"0.1.1">>},0},
5 | {<<"parse_trans">>,{pkg,<<"parse_trans">>,<<"3.0.0">>},0},
6 | {<<"shackle">>,{pkg,<<"shackle">>,<<"0.6.11">>},0}]}.
7 | [
8 | {pkg_hash,[
9 | {<<"foil">>, <<"16406493144743F633505E51406F7BE6BF17A3772DFD62BCDE3107C654704BEE">>},
10 | {<<"granderl">>, <<"F20077A68BD80B8D8783BD15A052813C6483771DEC1A5B837D307CBE92F14122">>},
11 | {<<"metal">>, <<"5D3D1322DA7BCD34B94FED5486F577973685298883954F7A3E517EF5EF6953F5">>},
12 | {<<"parse_trans">>, <<"9E96B1C9C3A0DF54E7B76F8F685D38BFA1EB21B31E042B1D1A5A70258E4DB1E3">>},
13 | {<<"shackle">>, <<"949245E1AE690DECAD887B046CCCA595E5F3FC798A317131466985F5BBA64298">>}]},
14 | {pkg_hash_ext,[
15 | {<<"foil">>, <<"BA8A1773D1AF12BD24E06657C87547AA81872950F7C503F39E0F31E39BB2BC39">>},
16 | {<<"granderl">>, <<"0641473F29BC3211C832A6DD3ADAA04544A5DFFC1C62372556946F236DF2DAD6">>},
17 | {<<"metal">>, <<"88B82B634998A1A768DEDCD372C2F7E657B19445325C0AF5CCBAC62C77210F1D">>},
18 | {<<"parse_trans">>, <<"5495A3309051DF7F510BC1FDABDA92DFF5417186A8F66D4419AAD6BA0AF5F0CB">>},
19 | {<<"shackle">>, <<"BAF9443E210309594BC683CDE056FFA66AF85FC1D26779164F855E0818ABEC28">>}]}
20 | ].
21 |
--------------------------------------------------------------------------------
/src/statsderl.app.src:
--------------------------------------------------------------------------------
1 | {application, statsderl, [
2 | {applications, [kernel, stdlib, granderl, shackle]},
3 | {description, "High Performance Erlang StatsD Client"},
4 | {env, []},
5 | {licenses, ["MIT"]},
6 | {links, [{"GitHub", "https://github.com/lpgauth/statsderl"}]},
7 | {mod, {statsderl_app, []}},
8 | {registered, []},
9 | {vsn, git}
10 | ]}.
11 |
--------------------------------------------------------------------------------
/src/statsderl.erl:
--------------------------------------------------------------------------------
1 | -module(statsderl).
2 | -include("statsderl.hrl").
3 |
4 | -compile(inline).
5 | -compile({inline_size, 512}).
6 |
7 | %% public
8 | -export([
9 | counter/3,
10 | decrement/3,
11 | gauge/3,
12 | gauge_decrement/3,
13 | gauge_increment/3,
14 | increment/3,
15 | timing/3,
16 | timing_fun/3,
17 | timing_now/3,
18 | timing_now_us/3
19 | ]).
20 |
21 | %% public
22 | -spec counter(key(), value(), sample_rate()) ->
23 | ok.
24 |
25 | counter(Key, Value, Rate) ->
26 | statsderl_sample:rate(Rate, {counter, Key, Value, Rate}).
27 |
28 | -spec decrement(key(), value(), sample_rate()) ->
29 | ok.
30 |
31 | decrement(Key, Value, Rate) when Value >= 0 ->
32 | statsderl_sample:rate(Rate, {counter, Key, -Value, Rate}).
33 |
34 | -spec gauge(key(), value(), sample_rate()) ->
35 | ok.
36 |
37 | gauge(Key, Value, Rate) when Value >= 0 ->
38 | statsderl_sample:rate(Rate, {gauge, Key, Value}).
39 |
40 | -spec gauge_decrement(key(), value(), sample_rate()) ->
41 | ok.
42 |
43 | gauge_decrement(Key, Value, Rate) when Value >= 0 ->
44 | statsderl_sample:rate(Rate, {gauge_decrement, Key, Value}).
45 |
46 | -spec gauge_increment(key(), value(), sample_rate()) ->
47 | ok.
48 |
49 | gauge_increment(Key, Value, Rate) when Value >= 0 ->
50 | statsderl_sample:rate(Rate, {gauge_increment, Key, Value}).
51 |
52 | -spec increment(key(), value(), sample_rate()) ->
53 | ok.
54 |
55 | increment(Key, Value, Rate) when Value >= 0 ->
56 | statsderl_sample:rate(Rate, {counter, Key, Value, Rate}).
57 |
58 | -spec timing(key(), value(), sample_rate()) ->
59 | ok.
60 |
61 | timing(Key, Value, Rate) ->
62 | statsderl_sample:rate(Rate, {timing, Key, Value}).
63 |
64 | -spec timing_fun(key(), fun(), sample_rate()) ->
65 | any().
66 |
67 | timing_fun(Key, Fun, Rate) ->
68 | Timestamp = statsderl_utils:timestamp(),
69 | Result = Fun(),
70 | timing_now(Key, Timestamp, Rate),
71 | Result.
72 |
73 | -spec timing_now(key(), erlang:timestamp(), sample_rate()) ->
74 | ok.
75 |
76 | timing_now(Key, Timestamp, Rate) ->
77 | statsderl_sample:rate(Rate, {timing_now, Key, Timestamp}).
78 |
79 | -spec timing_now_us(key(), erlang:timestamp(), sample_rate()) ->
80 | ok.
81 |
82 | timing_now_us(Key, Timestamp, Rate) ->
83 | statsderl_sample:rate(Rate, {timing_now_us, Key, Timestamp}).
84 |
--------------------------------------------------------------------------------
/src/statsderl_app.erl:
--------------------------------------------------------------------------------
1 | -module(statsderl_app).
2 | -include("statsderl.hrl").
3 |
4 | -export([
5 | start/0,
6 | stop/0
7 | ]).
8 |
9 | -behaviour(application).
10 | -export([
11 | start/2,
12 | stop/1
13 | ]).
14 |
15 | %% public
16 | -spec start() ->
17 | {ok, [atom()]} | {error, term()}.
18 |
19 | start() ->
20 | application:ensure_all_started(?APP).
21 |
22 | -spec stop() ->
23 | ok | {error, {not_started, ?APP}}.
24 |
25 | stop() ->
26 | application:stop(?APP).
27 |
28 | %% application callbacks
29 | -spec start(application:start_type(), term()) ->
30 | {ok, pid()}.
31 |
32 | start(_StartType, _StartArgs) ->
33 | statsderl_sup:start_link().
34 |
35 | -spec stop(term()) ->
36 | ok.
37 |
38 | stop(_State) ->
39 | shackle_pool:stop(?APP),
40 | ok.
41 |
--------------------------------------------------------------------------------
/src/statsderl_client.erl:
--------------------------------------------------------------------------------
1 | -module(statsderl_client).
2 | -include("statsderl.hrl").
3 |
4 | -compile(inline).
5 | -compile({inline_size, 512}).
6 |
7 | -behavior(shackle_client).
8 | -export([
9 | init/1,
10 | setup/2,
11 | handle_request/2,
12 | handle_data/2,
13 | terminate/1
14 | ]).
15 |
16 | -record(state, {
17 | base_key :: iodata()
18 | }).
19 |
20 | -type state() :: #state {}.
21 |
22 | %% shackle_server callbacks
23 | -spec init(undefined) ->
24 | {ok, state()}.
25 |
26 | init(_Opts) ->
27 | BaseKey = ?ENV(?ENV_BASEKEY, ?DEFAULT_BASEKEY),
28 |
29 | {ok, #state {
30 | base_key = statsderl_utils:base_key(BaseKey)
31 | }}.
32 |
33 | -spec setup(inet:socket(), state()) ->
34 | {ok, state()}.
35 |
36 | setup(_Socket, State) ->
37 | {ok, State}.
38 |
39 | -spec handle_request(term(), state()) ->
40 | {ok, undefined, iolist(), state()}.
41 |
42 | handle_request({cast, Data}, #state {
43 | base_key = BaseKey
44 | } = State) ->
45 |
46 | {ok, undefined, [BaseKey, Data], State};
47 | handle_request(Request, #state {
48 | base_key = BaseKey
49 | } = State) ->
50 |
51 | Data = statsderl_protocol:encode(Request),
52 |
53 | {ok, undefined, [BaseKey, Data], State}.
54 |
55 | -spec handle_data(binary(), state()) ->
56 | {ok, [], state()}.
57 |
58 | handle_data(_Data, State) ->
59 | {ok, [], State}.
60 |
61 | -spec terminate(state()) -> ok.
62 |
63 | terminate(_State) ->
64 | ok.
65 |
--------------------------------------------------------------------------------
/src/statsderl_protocol.erl:
--------------------------------------------------------------------------------
1 | -module(statsderl_protocol).
2 | -include("statsderl.hrl").
3 |
4 | -compile(inline).
5 | -compile({inline_size, 512}).
6 |
7 | -export([
8 | encode/1
9 | ]).
10 |
11 | %% public
12 | -spec encode(operation()) -> iodata().
13 |
14 | encode({counter, Key, Value, SampleRate}) ->
15 | [Key, <<":">>, format_value(Value), <<"|c">>,
16 | format_sample_rate(SampleRate)];
17 | encode({gauge, Key, Value}) ->
18 | [Key, <<":">>, format_value(Value), <<"|g">>];
19 | encode({gauge_decrement, Key, Value}) ->
20 | [Key, <<":-">>, format_value(Value), <<"|g">>];
21 | encode({gauge_increment, Key, Value}) ->
22 | [Key, <<":+">>, format_value(Value), <<"|g">>];
23 | encode({timing, Key, Value}) ->
24 | [Key, <<":">>, format_value(Value), <<"|ms">>].
25 |
26 | %% private
27 | format_sample_rate(SampleRate) when SampleRate >= 1 ->
28 | <<>>;
29 | format_sample_rate(SampleRate) ->
30 | [<<"|@">>, float_to_list(SampleRate, [compact, {decimals, 6}])].
31 |
32 | format_value(Value) when is_integer(Value) ->
33 | integer_to_list(Value);
34 | format_value(Value) when is_float(Value) ->
35 | float_to_list(Value, [{decimals, 2}]).
36 |
--------------------------------------------------------------------------------
/src/statsderl_sample.erl:
--------------------------------------------------------------------------------
1 | -module(statsderl_sample).
2 | -include("statsderl.hrl").
3 |
4 | -compile(inline).
5 | -compile({inline_size, 512}).
6 |
7 | -export([
8 | rate/2,
9 | rate_scaled/2
10 | ]).
11 |
12 | %% public
13 | -spec rate(sample_rate(), operation()) ->
14 | ok.
15 |
16 | rate(1, Operation) ->
17 | operation(Operation);
18 | rate(1.0, Operation) ->
19 | operation(Operation);
20 | rate(Rate, Operation) ->
21 | RateInt = trunc(Rate * ?MAX_UNSIGNED_INT_32),
22 | rate_scaled(RateInt, Operation).
23 |
24 | -spec rate_scaled(non_neg_integer(), operation()) ->
25 | ok.
26 |
27 | rate_scaled(RateInt, Operation) ->
28 | Rand = granderl:uniform(?MAX_UNSIGNED_INT_32),
29 | case Rand =< RateInt of
30 | true ->
31 | operation(Operation);
32 | false ->
33 | ok
34 | end.
35 |
36 | %% private
37 | cast(Request) ->
38 | shackle:cast(?APP, Request, undefined),
39 | ok.
40 |
41 | operation({timing_now, Key, Value}) ->
42 | Value2 = statsderl_utils:timing_now(Value),
43 | cast({timing, Key, Value2});
44 | operation({timing_now_us, Key, Value}) ->
45 | Value2 = statsderl_utils:timing_now_us(Value),
46 | cast({timing, Key, Value2});
47 | operation(Operation) ->
48 | cast(Operation).
49 |
--------------------------------------------------------------------------------
/src/statsderl_sup.erl:
--------------------------------------------------------------------------------
1 | -module(statsderl_sup).
2 | -include("statsderl.hrl").
3 |
4 | %% public
5 | -export([
6 | start_link/0
7 | ]).
8 |
9 | -behaviour(supervisor).
10 | -export([
11 | init/1
12 | ]).
13 |
14 |
15 | %% public
16 | -spec start_link() ->
17 | {ok, pid()}.
18 |
19 | start_link() ->
20 | supervisor:start_link({local, ?MODULE}, ?MODULE, []).
21 |
22 | %% supervisor callbacks
23 | -spec init([]) ->
24 | {ok, {{one_for_one, 5, 10}, []}}.
25 |
26 | init(_Args) ->
27 | BacklogSize = ?ENV(backlog_size, ?DEFAULT_BACKLOG_SIZE),
28 | Hostname = ?ENV(?ENV_HOSTNAME, ?DEFAULT_HOSTNAME),
29 | PoolSize = ?ENV(pool_size, ?DEFAULT_POOL_SIZE),
30 | Port = ?ENV(?ENV_PORT, ?DEFAULT_PORT),
31 |
32 | ClientOpts = [
33 | {address, Hostname},
34 | {port, Port},
35 | {protocol, shackle_udp}
36 | ],
37 | PoolOtps = [
38 | {backlog_size, BacklogSize},
39 | {pool_size, PoolSize}
40 | ],
41 | ok = shackle_pool:start(?APP, ?CLIENT, ClientOpts, PoolOtps),
42 |
43 | {ok, {{one_for_one, 5, 10}, []}}.
44 |
--------------------------------------------------------------------------------
/src/statsderl_transform.erl:
--------------------------------------------------------------------------------
1 | -module(statsderl_transform).
2 | -include("statsderl.hrl").
3 |
4 | -export([
5 | parse_transform/2
6 | ]).
7 |
8 | -define(ATOM(Atom), {atom, 0, Atom}).
9 | -define(BIN_ELEMENT(String), {bin_element, 0, String, default, default}).
10 | -define(BINARY(String), {bin, 0, [?BIN_ELEMENT(?STRING(String))]}).
11 | -define(INTEGER(Int), {integer, 0, Int}).
12 | -define(OP_NEGATIVE(X), {op, 0, '-', X}).
13 | -define(STRING(String), {string, 0, String}).
14 | -define(TUPLE(List), {tuple, 0, List}).
15 |
16 | -type forms() :: [erl_parse:abstract_form() | erl_parse:form_info()].
17 |
18 | %% public
19 | -spec parse_transform(forms(), [compile:option()]) ->
20 | forms().
21 |
22 | parse_transform(Forms, _Options) ->
23 | parse_trans:plain_transform(fun do_transform/1, Forms).
24 |
25 | do_transform({call, _, {_, _, {_, _, ?APP}, Function}, _} = F) ->
26 | replace(safe_normalize(Function), F);
27 | do_transform(_Form) ->
28 | continue.
29 |
30 | %% private
31 | call(Function, Arg1, Arg2) ->
32 | {call, 0, {remote, 0,
33 | {atom, 0, statsderl_sample},
34 | {atom, 0, Function}}, [Arg1, Arg2]
35 | }.
36 |
37 | encode(Operation) ->
38 | Encoded = statsderl_protocol:encode(Operation),
39 | binary_to_list(iolist_to_binary(Encoded)).
40 |
41 | not_undefined([]) ->
42 | true;
43 | not_undefined([undefined | _]) ->
44 | false;
45 | not_undefined([_ | T]) ->
46 | not_undefined(T).
47 |
48 | op_code(increment) ->
49 | counter;
50 | op_code(decrement) ->
51 | counter;
52 | op_code(Function) ->
53 | Function.
54 |
55 | packet(counter, Key, Value, Rate) ->
56 | Key2 = safe_normalize(Key),
57 | Value2 = safe_normalize(Value),
58 | Rate2 = safe_normalize(Rate),
59 |
60 | case not_undefined([Key2, Value2, Rate2]) of
61 | true ->
62 | encode({counter, Key2, Value2, Rate2});
63 | false ->
64 | undefined
65 | end;
66 | packet(OpCode, Key, Value, _Rate) ->
67 | Key2 = safe_normalize(Key),
68 | Value2 = safe_normalize(Value),
69 |
70 | case not_undefined([Key2, Value2]) of
71 | true ->
72 | encode({OpCode, Key2, Value2});
73 | false ->
74 | undefined
75 | end.
76 |
77 | rate_scaled({float, _, RateValue}) ->
78 | trunc(RateValue * ?MAX_UNSIGNED_INT_32);
79 | rate_scaled({integer, _, RateValue}) ->
80 | trunc(RateValue * ?MAX_UNSIGNED_INT_32);
81 | rate_scaled(_) ->
82 | undefined.
83 |
84 | replace(timing_fun, F) ->
85 | F;
86 | replace(Function, {_, _, _, [Key, Value, Rate]} = F) ->
87 | RateScaled = rate_scaled(Rate),
88 | OpCode = op_code(Function),
89 | Value2 = value(Function, Value),
90 | Packet = packet(OpCode, Key, Value2, Rate),
91 |
92 | case {RateScaled, Packet} of
93 | {undefined, undefined} ->
94 | F;
95 | {_, undefined} ->
96 | case OpCode of
97 | counter ->
98 | sample_scaled(RateScaled,
99 | ?TUPLE([?ATOM(OpCode), Key, Value2, Rate]));
100 | _ ->
101 | sample_scaled(RateScaled,
102 | ?TUPLE([?ATOM(OpCode), Key, Value2]))
103 | end;
104 | {undefined, _} ->
105 | sample(Rate,
106 | ?TUPLE([?ATOM(cast), ?BINARY(Packet)]));
107 | _ ->
108 | sample_scaled(RateScaled,
109 | ?TUPLE([?ATOM(cast), ?BINARY(Packet)]))
110 | end.
111 |
112 | sample(Rate, Arguments) ->
113 | call(rate, Rate, Arguments).
114 |
115 | sample_scaled(RateScaled, Arguments) ->
116 | call(rate_scaled, ?INTEGER(RateScaled), Arguments).
117 |
118 | safe_normalize(AbsTerm) ->
119 | try erl_parse:normalise(AbsTerm)
120 | catch
121 | _:_ ->
122 | undefined
123 | end.
124 |
125 | value(decrement, Value) ->
126 | ?OP_NEGATIVE(Value);
127 | value(_, Value) ->
128 | Value.
129 |
--------------------------------------------------------------------------------
/src/statsderl_utils.erl:
--------------------------------------------------------------------------------
1 | -module(statsderl_utils).
2 | -include("statsderl.hrl").
3 |
4 | -compile(inline).
5 | -compile({inline_size, 512}).
6 |
7 | -export([
8 | base_key/1,
9 | error_msg/2,
10 | timestamp/0,
11 | timing_now/1,
12 | timing_now_us/1
13 | ]).
14 |
15 | %% public
16 | -spec base_key(base_key()) ->
17 | iodata().
18 |
19 | base_key(hostname) ->
20 | [hostname(), $.];
21 | base_key(name) ->
22 | [name(), $.];
23 | base_key(sname) ->
24 | [sname(), $.];
25 | base_key(undefined) ->
26 | "";
27 | base_key(Key) when is_binary(Key) ->
28 | [Key, $.];
29 | base_key([]) ->
30 | [];
31 | base_key([H | T] = Key) ->
32 | case io_lib:printable_unicode_list(Key) of
33 | true ->
34 | [Key, $.];
35 | false ->
36 | [base_key(H) | base_key(T)]
37 | end.
38 |
39 | -spec error_msg(string(), [term()]) ->
40 | ok.
41 |
42 | error_msg(Format, Data) ->
43 | error_logger:error_msg("[statsderl] " ++ Format, Data).
44 |
45 | -spec timestamp() ->
46 | erlang:timestamp().
47 |
48 | timestamp() ->
49 | os:timestamp().
50 |
51 | -spec timing_now(erlang:timestamp()) ->
52 | non_neg_integer().
53 |
54 | timing_now(Timestamp) ->
55 | timing_now_us(Timestamp) div 1000.
56 |
57 | -spec timing_now_us(erlang:timestamp()) ->
58 | non_neg_integer().
59 |
60 | timing_now_us(Timestamp) ->
61 | timer:now_diff(statsderl_utils:timestamp(), Timestamp).
62 |
63 | %% private
64 | hostname() ->
65 | {ok, Hostname} = inet:gethostname(),
66 | Hostname.
67 |
68 | name() ->
69 | Name = atom_to_list(node()),
70 | re:replace(Name, "@", ".", [global, {return, list}]).
71 |
72 | sname() ->
73 | string:sub_word(atom_to_list(node()), 1, $@).
74 |
--------------------------------------------------------------------------------
/test/statsderl_profile.erl:
--------------------------------------------------------------------------------
1 | -module(statsderl_profile).
2 |
3 | -compile({parse_transform, statsderl_transform}).
4 |
5 | -export([
6 | fprofx/0
7 | ]).
8 |
9 | -define(N, 10000).
10 | -define(P, 20).
11 |
12 | %% public
13 | -spec fprofx() -> ok.
14 |
15 | fprofx() ->
16 | statsderl_app:start(),
17 | [counter() || _ <- lists:seq(1, 100)],
18 | statsderl_app:stop(),
19 |
20 | fprofx:start(),
21 | {ok, Tracer} = fprofx:profile(start),
22 | fprofx:trace([start, {procs, new}, {tracer, Tracer}]),
23 |
24 | {ok, [statsderl]} = statsderl_app:start(),
25 | timer:sleep(250),
26 |
27 | Self = self(),
28 | [spawn(fun () ->
29 | counter(),
30 | Self ! exit
31 | end) || _ <- lists:seq(1, ?P)],
32 | wait(),
33 |
34 | fprofx:trace(stop),
35 | fprofx:analyse([totals, {dest, ""}]),
36 | fprofx:stop(),
37 | application:stop(statsderl),
38 |
39 | ok.
40 |
41 | %% private
42 | counter() ->
43 | [statsderl:counter(["test", <<".test">>], 5, 0.25) ||
44 | _ <- lists:seq(1, ?N)].
45 |
46 | wait() ->
47 | wait(?P).
48 |
49 | wait(0) ->
50 | ok;
51 | wait(X) ->
52 | receive
53 | exit ->
54 | wait(X - 1)
55 | end.
56 |
--------------------------------------------------------------------------------
/test/statsderl_tests.erl:
--------------------------------------------------------------------------------
1 | -module(statsderl_tests).
2 | -include_lib("statsderl/include/statsderl.hrl").
3 | -include_lib("eunit/include/eunit.hrl").
4 |
5 | %% tests
6 | statsderl_base_key_test() ->
7 | assert_base_key("base_key", <<"base_key.">>),
8 | assert_base_key(<<"base_key">>, <<"base_key.">>).
9 |
10 | statsderl_test_() ->
11 | {setup,
12 | fun () -> setup() end,
13 | fun (Socket) -> cleanup(Socket) end,
14 | {with, [
15 | fun counter_subtest/1,
16 | fun decrement_subtest/1,
17 | fun gauge_decrement_subtest/1,
18 | fun gauge_increment_subtest/1,
19 | fun gauge_subtest/1,
20 | fun increment_subtest/1,
21 | fun sampling_rate_subtest/1,
22 | fun timing_fun_subtest/1,
23 | fun timing_subtest/1,
24 | fun timing_now_subtest/1,
25 | fun timing_now_us_subtest/1
26 | ]}
27 | }.
28 |
29 | %% subtests
30 | counter_subtest(Socket) ->
31 | statsderl:counter("test", 1.123, 1),
32 | assert_packet(Socket, <<"test:1.12|c">>).
33 |
34 | decrement_subtest(Socket) ->
35 | statsderl:decrement("test", 1, 1.0),
36 | assert_packet(Socket, <<"test:-1|c">>).
37 |
38 | gauge_subtest(Socket) ->
39 | statsderl:gauge("test", 1, 1),
40 | assert_packet(Socket, <<"test:1|g">>).
41 |
42 | gauge_decrement_subtest(Socket) ->
43 | statsderl:gauge_decrement("test", 1, 1),
44 | assert_packet(Socket, <<"test:-1|g">>).
45 |
46 | gauge_increment_subtest(Socket) ->
47 | statsderl:gauge_increment("test", 1, 1),
48 | assert_packet(Socket, <<"test:+1|g">>).
49 |
50 | increment_subtest(Socket) ->
51 | statsderl:increment("test", 1, 1),
52 | assert_packet(Socket, <<"test:1|c">>).
53 |
54 | sampling_rate_subtest(Socket) ->
55 | meck:new(granderl, [passthrough, no_history]),
56 | meck:expect(granderl, uniform, fun
57 | (4) -> 2;
58 | (?MAX_UNSIGNED_INT_32) -> 1
59 | end),
60 | statsderl:counter("test", 1, 0.1234),
61 | assert_packet(Socket, <<"test:1|c|@0.1234">>),
62 | meck:expect(granderl, uniform, fun
63 | (4) -> 2;
64 | (?MAX_UNSIGNED_INT_32) -> ?MAX_UNSIGNED_INT_32
65 | end),
66 | statsderl:counter("test", 1, 0.1234),
67 | meck:unload(granderl).
68 |
69 | timing_fun_subtest(Socket) ->
70 | meck:new(statsderl_utils, [passthrough, no_history]),
71 | Seq = meck:loop([{1448, 573975, 400000}, {1448, 573975, 500000}]),
72 | meck:expect(statsderl_utils, timestamp, [], Seq),
73 | TestResult = "testresult",
74 | Result = statsderl:timing_fun("test", fun () -> TestResult end, 1),
75 | assert_packet(Socket, <<"test:100|ms">>),
76 | ?assertEqual(Result, TestResult),
77 | meck:unload(statsderl_utils).
78 |
79 | timing_subtest(Socket) ->
80 | statsderl:timing("test", 1, 1),
81 | assert_packet(Socket, <<"test:1|ms">>).
82 |
83 | timing_now_subtest(Socket) ->
84 | meck:new(statsderl_utils, [passthrough, no_history]),
85 | Seq = meck:loop([{1448, 573976, 400000}, {1448, 573976, 500000}]),
86 | meck:expect(statsderl_utils, timestamp, [], Seq),
87 | statsderl:timing_now("test", statsderl_utils:timestamp(), 1),
88 | assert_packet(Socket, <<"test:100|ms">>),
89 | meck:unload(statsderl_utils).
90 |
91 | timing_now_us_subtest(Socket) ->
92 | meck:new(statsderl_utils, [passthrough, no_history]),
93 | Seq = meck:loop([{1448, 573977, 400000}, {1448, 573977, 500000}]),
94 | meck:expect(statsderl_utils, timestamp, [], Seq),
95 | statsderl:timing_now_us("test", statsderl_utils:timestamp(), 1),
96 | assert_packet(Socket, <<"test:100000|ms">>),
97 | meck:unload(statsderl_utils).
98 |
99 | %% helpers
100 | assert_base_key(BaseKey, Expected) ->
101 | Socket = setup([{?ENV_BASEKEY, BaseKey}]),
102 | statsderl:counter("test", 1, 1),
103 | {ok, {_Address, _Port, Packet}} = gen_udp:recv(Socket, 0),
104 | ?assertEqual(<>, Packet),
105 | cleanup(Socket).
106 |
107 | assert_packet(Socket, Expected) ->
108 | {ok, {_Address, _Port, Packet}} = gen_udp:recv(Socket, 0),
109 | ?assertEqual(Expected, Packet).
110 |
111 | cleanup(Socket) ->
112 | ok = gen_udp:close(Socket),
113 | statsderl_app:stop().
114 |
115 | setup() ->
116 | setup([]).
117 |
118 | setup(EnvVars) ->
119 | error_logger:tty(false),
120 | application:load(?APP),
121 | [application:unset_env(?APP, K) || K <- ?ENV_VARS],
122 | [application:set_env(?APP, K, V) || {K, V} <- EnvVars],
123 | {ok, Socket} = gen_udp:open(?DEFAULT_PORT, [binary, {active, false}]),
124 | timer:sleep(100),
125 | statsderl_app:start(),
126 | timer:sleep(1000),
127 | Socket.
128 |
--------------------------------------------------------------------------------
/test/statsderl_transform_tests.erl:
--------------------------------------------------------------------------------
1 | -module(statsderl_transform_tests).
2 | -include_lib("statsderl/include/statsderl.hrl").
3 | -include_lib("eunit/include/eunit.hrl").
4 |
5 | -compile({parse_transform, statsderl_transform}).
6 |
7 | %% tests
8 | statsderl_test_() ->
9 | {setup,
10 | fun () -> setup() end,
11 | fun (Socket) -> cleanup(Socket) end,
12 | {with, [
13 | fun counter_subtest/1,
14 | fun decrement_subtest/1,
15 | fun gauge_subtest/1,
16 | fun increment_subtest/1,
17 | fun timing_fun_subtest/1
18 | ]}
19 | }.
20 |
21 | %% subtests
22 | counter_subtest(Socket) ->
23 | A = "test",
24 | B = 5,
25 | C = 1,
26 | statsderl:counter("test", 1, 1),
27 | assert_packet(Socket, <<"test:1|c">>),
28 | statsderl:counter(["test", A], 1, 1),
29 | assert_packet(Socket, <<"testtest:1|c">>),
30 | statsderl:counter("test", B, 1),
31 | assert_packet(Socket, <<"test:5|c">>),
32 | statsderl:counter("test", 1, C),
33 | assert_packet(Socket, <<"test:1|c">>).
34 |
35 | decrement_subtest(Socket) ->
36 | statsderl:decrement("test", 1, 1.0),
37 | assert_packet(Socket, <<"test:-1|c">>).
38 |
39 | gauge_subtest(Socket) ->
40 | A = "test",
41 | B = 5,
42 | C = 1,
43 | statsderl:gauge("test", 1, 1),
44 | assert_packet(Socket, <<"test:1|g">>),
45 | statsderl:gauge(["test", A], 1, 1),
46 | assert_packet(Socket, <<"testtest:1|g">>),
47 | statsderl:gauge("test", B, 1),
48 | assert_packet(Socket, <<"test:5|g">>),
49 | statsderl:gauge("test", 1, C),
50 | assert_packet(Socket, <<"test:1|g">>).
51 |
52 | increment_subtest(Socket) ->
53 | statsderl:increment("test", 1, 1),
54 | assert_packet(Socket, <<"test:1|c">>).
55 |
56 | timing_fun_subtest(Socket) ->
57 | meck:new(statsderl_utils, [passthrough, no_history]),
58 | Seq = meck:loop([{1448, 573975, 400000}, {1448, 573975, 500000}]),
59 | meck:expect(statsderl_utils, timestamp, [], Seq),
60 | statsderl:timing_fun("test", fun () -> ok end, 1),
61 | assert_packet(Socket, <<"test:100|ms">>),
62 | meck:unload(statsderl_utils).
63 |
64 | %% helpers
65 | assert_packet(Socket, Expected) ->
66 | {ok, {_Address, _Port, Packet}} = gen_udp:recv(Socket, 0),
67 | ?assertEqual(Expected, Packet).
68 |
69 | cleanup(Socket) ->
70 | ok = gen_udp:close(Socket),
71 | statsderl_app:stop().
72 |
73 | setup() ->
74 | error_logger:tty(false),
75 | application:load(?APP),
76 | {ok, Socket} = gen_udp:open(?DEFAULT_PORT, [binary, {active, false}]),
77 | timer:sleep(100),
78 | statsderl_app:start(),
79 | timer:sleep(1000),
80 | Socket.
81 |
--------------------------------------------------------------------------------
/test/statsderl_utils_tests.erl:
--------------------------------------------------------------------------------
1 | -module(statsderl_utils_tests).
2 | -include_lib("statsderl/include/statsderl.hrl").
3 | -include_lib("eunit/include/eunit.hrl").
4 |
5 | base_key_test() ->
6 | meck:new(inet, [passthrough, no_history, unstick]),
7 | meck:expect(inet, gethostname, fun () ->
8 | {ok, "myhost"}
9 | end),
10 |
11 | assert_base_key(name, <<"nonode.nohost.">>),
12 | assert_base_key(sname, <<"nonode.">>),
13 | assert_base_key(undefined, <<"">>),
14 | assert_base_key(["hello", <<"world">>], <<"hello.world.">>),
15 | assert_base_key(["rtb", "gateway", hostname], <<"rtb.gateway.myhost.">>),
16 |
17 | meck:unload(inet).
18 |
19 | %% private
20 | assert_base_key(BaseKey, Expected) ->
21 | BaseKey2 = iolist_to_binary(statsderl_utils:base_key(BaseKey)),
22 | ?assertEqual(Expected, BaseKey2).
23 |
--------------------------------------------------------------------------------