Skip to content
Open
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
5 changes: 5 additions & 0 deletions lib/custom_functions.ex
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ defmodule Instruments.CustomFunctions do
def measure(key, options \\ [], func) do
Instruments.measure([unquote(prefix_with_dot), key], options, func)
end

@doc false
def send_service_check(key, status, options \\ []) do
Instruments.send_service_check([unquote(prefix_with_dot), key], status, options)
end
end
end
end
73 changes: 73 additions & 0 deletions lib/instruments.ex
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,79 @@ defmodule Instruments do
end
end

@doc """
Sends a service check to DataDog

Reports the health status of a service. Status must be one of:
`:ok` (0), `:warning` (1), `:critical` (2), or `:unknown` (3).

## Options

* `tags` - A list of String tags
* `message` - A description of the current status
* `hostname` - The hostname to associate with the check
* `timestamp` - A Unix timestamp for the check

## Examples

Instruments.send_service_check("my.service", :ok)
Instruments.send_service_check("my.service", :critical,
tags: ["env:prod"],
message: "connection refused",
hostname: "web-01",
timestamp: 1234567890
)

"""
defmacro send_service_check(name_ast, status, opts \\ []) do
name_iodata = MacroHelpers.to_iolist(name_ast, __CALLER__)

quote do
status_code =
case unquote(status) do
:ok -> "0"
:warning -> "1"
:critical -> "2"
:unknown -> "3"
end

header = ["_sc", "|", unquote(name_iodata), "|", status_code]

opts = unquote(opts)

message =
Enum.reduce([:timestamp, :hostname, :tags, :message], header, fn
:timestamp, acc ->
case Keyword.get(opts, :timestamp) do
nil -> acc
ts -> [acc, "|d:", Integer.to_string(ts)]
end

:hostname, acc ->
case Keyword.get(opts, :hostname) do
nil -> acc
h -> [acc, "|h:", h]
end

:tags, acc ->
case Keyword.get(opts, :tags) do
nil -> acc
tag_list -> [acc, "|#", Enum.intersperse(tag_list, ",")]
end

:message, acc ->
case Keyword.get(opts, :message) do
nil -> acc
m -> [acc, "|m:", m]
end
end)

unquote(@metrics_module)
|> Process.whereis()
|> :gen_udp.send(Instruments.statsd_host(), Instruments.statsd_port(), message)
end
end

@doc false
def flush_all_probes(wait_for_flush \\ true, flush_timeout_ms \\ 10_000) do
Probe.Supervisor
Expand Down
2 changes: 1 addition & 1 deletion lib/macro_helpers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ defmodule Instruments.MacroHelpers do

alias Instruments.RateTracker

@safe_metric_types [:increment, :decrement, :gauge, :event, :set]
@safe_metric_types [:increment, :decrement, :gauge, :event, :set, :service_check]

@metrics_module Application.get_env(:instruments, :reporter_module, Instruments.Statix)

Expand Down
8 changes: 8 additions & 0 deletions test/custom_functions_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,14 @@ defmodule Instruments.CustomFunctionsTest do

assert_metric_reported(:timing, "custom.my.measure", 10..11, tags: ["timing:short"])
end

test "to send_service_check calls" do
Custom.send_service_check("my.check", :ok)
assert_metric_reported(:service_check, "custom.my.check", :ok)

Custom.send_service_check("my.check", :critical, tags: ["env:prod"])
assert_metric_reported(:service_check, "custom.my.check", :critical, tags: ["env:prod"])
end
end

test "setting a runtime prefix" do
Expand Down
30 changes: 30 additions & 0 deletions test/instruments_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,36 @@ defmodule InstrumentsTest do
assert_metric_reported(:event, "my_title", "my text", tags: ["host:any", "another:tag"])
end

test "sending service checks" do
Instruments.send_service_check("my.service", :ok)
assert_metric_reported(:service_check, "my.service", :ok)

Instruments.send_service_check("my.service", :warning)
assert_metric_reported(:service_check, "my.service", :warning)

Instruments.send_service_check("my.service", :critical)
assert_metric_reported(:service_check, "my.service", :critical)

Instruments.send_service_check("my.service", :unknown)
assert_metric_reported(:service_check, "my.service", :unknown)
end

test "sending service checks with all options" do
Instruments.send_service_check("my.service", :critical,
timestamp: 1_234_567_890,
hostname: "web-01",
tags: ["env:prod"],
message: "connection refused"
)

assert_metric_reported(:service_check, "my.service", :critical,
timestamp: 1_234_567_890,
hostname: "web-01",
tags: ["env:prod"],
message: "connection refused"
)
end

test "sending events with a title that's a variable blows up" do
quoted =
quote do
Expand Down
36 changes: 36 additions & 0 deletions test/support/fake_statsd.ex
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,19 @@ defmodule FakeStatsd do
|> do_decode
end

defp do_decode(["_sc", name, status | rest]) do
status_atom =
case status do
"0" -> :ok
"1" -> :warning
"2" -> :critical
"3" -> :unknown
end

opts = decode_service_check_metadata(rest)
{:service_check, name, status_atom, opts}
end

defp do_decode([name_and_val, type | rest]) do
opts = decode_tags_and_sampling(rest)
{name, val} = decode_name_and_value(name_and_val)
Expand Down Expand Up @@ -109,4 +122,27 @@ defmodule FakeStatsd do
end
end
end

defp decode_service_check_metadata(fields),
do: decode_service_check_metadata(fields, [])

defp decode_service_check_metadata([], accum), do: Enum.reverse(accum)

defp decode_service_check_metadata([<<"d:", ts::binary>> | rest], accum) do
{timestamp, ""} = Integer.parse(ts)
decode_service_check_metadata(rest, Keyword.put(accum, :timestamp, timestamp))
end

defp decode_service_check_metadata([<<"h:", hostname::binary>> | rest], accum) do
decode_service_check_metadata(rest, Keyword.put(accum, :hostname, hostname))
end

defp decode_service_check_metadata([<<"#", tags::binary>> | rest], accum) do
tag_list = String.split(tags, ",")
decode_service_check_metadata(rest, Keyword.put(accum, :tags, tag_list))
end

defp decode_service_check_metadata([<<"m:", message::binary>> | rest], accum) do
decode_service_check_metadata(rest, Keyword.put(accum, :message, message))
end
end
2 changes: 1 addition & 1 deletion test/test_helper.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
ExUnit.start()

defmodule MetricsAssertions do
@safe_metric_types [:increment, :decrement, :gauge, :event, :set]
@safe_metric_types [:increment, :decrement, :gauge, :event, :set, :service_check]
use ExUnit.Case

def assert_metric_reported(metric_type, metric_name) do
Expand Down