Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ This Discord fork of Statix exists for [discord/instruments](https://github.com/

- [OTP26 compability via Port.command replacement](https://github.com/cabify/statix/pull/1)
- Name change to discord_statix
- Unix Domain Socket support
- Support for Datadog events

Statix is an Elixir client for StatsD-compatible servers.
It is focused on speed without sacrificing simplicity, completeness, or correctness.
Expand Down
114 changes: 70 additions & 44 deletions lib/statix.ex
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,26 @@ defmodule Statix do
"""
@callback set(key, value :: String.Chars.t()) :: on_send

@doc """
Sends a DataDog event.

This uses the DogStatsD event format (`_e{title_len,text_len}:title|text`),
which is a DataDog-specific extension to the StatsD protocol. Standard StatsD
servers do not support events.

## Examples

iex> MyApp.Statix.send_event("deploy", "v1.2.3", tags: ["env:prod"])
:ok

"""
@callback send_event(title :: iodata, text :: iodata, options) :: on_send

@doc """
Same as `send_event(title, text, [])`.
"""
@callback send_event(title :: iodata, text :: iodata) :: on_send

@doc """
Measures the execution time of the given `function` and writes that to the
StatsD timing identified by `key`.
Expand Down Expand Up @@ -322,23 +342,23 @@ defmodule Statix do
unquote(current_statix)

def increment(key, val \\ 1, options \\ []) when is_number(val) do
Statix.transmit(current_statix(), :counter, key, val, options)
Statix.transmit_metric(current_statix(), :counter, key, val, options)
end

def decrement(key, val \\ 1, options \\ []) when is_number(val) do
Statix.transmit(current_statix(), :counter, key, [?-, to_string(val)], options)
Statix.transmit_metric(current_statix(), :counter, key, [?-, to_string(val)], options)
end

def gauge(key, val, options \\ []) do
Statix.transmit(current_statix(), :gauge, key, val, options)
Statix.transmit_metric(current_statix(), :gauge, key, val, options)
end

def histogram(key, val, options \\ []) do
Statix.transmit(current_statix(), :histogram, key, val, options)
Statix.transmit_metric(current_statix(), :histogram, key, val, options)
end

def timing(key, val, options \\ []) do
Statix.transmit(current_statix(), :timing, key, val, options)
Statix.transmit_metric(current_statix(), :timing, key, val, options)
end

def measure(key, options \\ [], fun) when is_function(fun, 0) do
Expand All @@ -350,7 +370,11 @@ defmodule Statix do
end

def set(key, val, options \\ []) do
Statix.transmit(current_statix(), :set, key, val, options)
Statix.transmit_metric(current_statix(), :set, key, val, options)
end

def send_event(title, text, options \\ []) do
Statix.transmit_event(current_statix(), title, text, options)
end

defoverridable(
Expand All @@ -360,7 +384,8 @@ defmodule Statix do
histogram: 3,
timing: 3,
measure: 3,
set: 3
set: 3,
send_event: 3
)
end
end
Expand Down Expand Up @@ -409,54 +434,55 @@ defmodule Statix do
end

@doc false
def transmit(
%{conn: %{transport: :uds, socket_path: path}, tags: tags},
type,
key,
value,
options
)
def transmit_metric(statix, type, key, value, options)
when (is_binary(key) or is_list(key)) and is_list(options) do
if should_send?(options) do
options = put_global_tags(options, tags)

case Statix.ConnTracker.get(path) do
{:ok, conn} ->
case Conn.transmit(conn, type, key, to_string(value), options) do
:ok ->
:ok

{:error, _reason} = error ->
Statix.ConnTracker.report_send_error(path)
error
end

{:error, :not_found} ->
{:error, :socket_not_found}
end
do_transmit(statix, options, fn conn, opts ->
Conn.transmit_metric(conn, type, key, to_string(value), opts)
end)
else
:ok
end
end

def transmit(
%{conn: conn, pool: pool, tags: tags},
type,
key,
value,
options
)
when (is_binary(key) or is_list(key)) and is_list(options) do
if should_send?(options) do
options = put_global_tags(options, tags)
@doc false
def transmit_event(statix, title, text, options)
when is_list(options) do
do_transmit(statix, options, fn conn, opts ->
Conn.transmit_event(conn, title, text, opts)
end)
end

%{conn | sock: pick_name(pool)}
|> Conn.transmit(type, key, to_string(value), options)
else
:ok
defp do_transmit(
%{conn: %{transport: :uds, socket_path: path}, tags: tags},
options,
send_fn
) do
options = put_global_tags(options, tags)

case Statix.ConnTracker.get(path) do
{:ok, conn} ->
case send_fn.(conn, options) do
:ok ->
:ok

{:error, _reason} = error ->
Statix.ConnTracker.report_send_error(path)
error
end

{:error, :not_found} ->
{:error, :socket_not_found}
end
end

defp do_transmit(%{conn: conn, pool: pool, tags: tags}, options, send_fn) do
options = put_global_tags(options, tags)
conn = %{conn | sock: pick_name(pool)}

send_fn.(conn, options)
end

defp should_send?([]), do: true
defp should_send?([{:sample_rate, rate} | _]), do: rate >= :rand.uniform()
defp should_send?([_ | rest]), do: should_send?(rest)
Expand Down
20 changes: 18 additions & 2 deletions lib/statix/conn.ex
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,27 @@ defmodule Statix.Conn do
end
end

def transmit(%__MODULE__{sock: sock, prefix: prefix} = conn, type, key, val, options)
def transmit_event(%__MODULE__{sock: sock} = conn, title, text, options)
when is_list(options) do
result =
Packet.build_event(title, text, options)
|> transmit(conn)

with {:error, error} <- result do
Logger.error(fn ->
if(is_atom(sock), do: "", else: "Statix ") <>
"#{inspect(sock)} event \"#{title}\" lost, error=#{inspect(error)}"
end)
end

result
end

def transmit_metric(%__MODULE__{sock: sock, prefix: prefix} = conn, type, key, val, options)
when is_binary(val) and is_list(options) do
result =
prefix
|> Packet.build(type, key, val, options)
|> Packet.build_metric(type, key, val, options)
|> transmit(conn)

with {:error, error} <- result do
Expand Down
26 changes: 25 additions & 1 deletion lib/statix/packet.ex
Original file line number Diff line number Diff line change
@@ -1,12 +1,36 @@
defmodule Statix.Packet do
@moduledoc false

def build(prefix, name, key, val, options) do
def build_metric(prefix, name, key, val, options) do
[prefix, key, ?:, val, ?|, metric_type(name)]
|> set_option(:sample_rate, options[:sample_rate])
|> set_option(:tags, options[:tags])
end

@doc """
Builds a DataDog event packet.

Uses the DogStatsD event format (`_e{title_len,text_len}:title|text`),
which is a DataDog-specific extension to the StatsD protocol. Standard StatsD
servers do not support events.
"""
def build_event(title, text, options) do
title_bin = IO.iodata_to_binary(title)
text_bin = IO.iodata_to_binary(text)

[
"_e{",
Integer.to_string(byte_size(title_bin)),
",",
Integer.to_string(byte_size(text_bin)),
"}:",
title_bin,
"|",
text_bin
Comment on lines +22 to +29
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verifying that this is correct according to the dogstatsd event specification. We could support more here, but I don't think we have much need for the additional fields at the application layer yet.

]
|> set_option(:tags, options[:tags])
end

metrics = %{
counter: "c",
gauge: "g",
Expand Down
8 changes: 8 additions & 0 deletions test/statix/uds_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,14 @@ defmodule Statix.UDSTest do
assert_receive {:test_server, _, "sample:3|ms|#foo:bar,baz"}
end

test "send_event via UDS", _context do
TestStatix.send_event("my_title", "my text")
assert_receive {:test_server, _, "_e{8,7}:my_title|my text"}

TestStatix.send_event("deploy", "v1.2.3", tags: ["env:prod"])
assert_receive {:test_server, _, "_e{6,6}:deploy|v1.2.3|#env:prod"}
end

test "set via UDS", _context do
TestStatix.set("sample", "user1")
assert_receive {:test_server, _, "sample:user1|s"}
Expand Down
8 changes: 8 additions & 0 deletions test/statix_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,14 @@ defmodule StatixTest do
refute_received _any
end

test "send_event/2,3" do
__MODULE__.send_event("my_title", "my text")
assert_receive {:test_server, _, "_e{8,7}:my_title|my text"}

send_event("deploy", "v1.2.3", tags: ["env:prod"])
assert_receive {:test_server, _, "_e{6,6}:deploy|v1.2.3|#env:prod"}
end

test "set/2,3" do
__MODULE__.set(["sample"], 2)
assert_receive {:test_server, _, "sample:2|s"}
Expand Down
Loading