Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for sampling rate #28

Merged
merged 14 commits into from
Apr 20, 2020
21 changes: 21 additions & 0 deletions lib/telemetry_metrics_statsd.ex
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,27 @@ defmodule TelemetryMetricsStatsd do
exceed the Maximum Transmission Unit, or MTU, of the link, so that no data is lost on the way.
By default the reporter will break up the datagrams at 512 bytes, but this is configurable via
the `:mtu` option.

## Sampling data

It's not always convenient to capture every piece of data, such as in the
case of high-traffic applications. In those cases, you may want to capture a
"sample" of the data. You can do this by passing `[sample_rate: <rate>]` as
an option to `:reporter_options`, where "rate" is a value between 0.0 and
1.0. The default `:sample_rate` is 1.0.

### Example

TelemetryMetricsStatsd.start_link(
metrics: [
counter("http.request.count"),
summary("http.request.duration", reporter_options: [sample_rate: 0.1]),
distribution("http.request.duration", buckets: [25, 50, 100, 250], reporter_options: [sample_rate: 0.1])
]
)

In this example, we are captureing 100% of the measurements for the counter,
Copy link

Choose a reason for hiding this comment

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

captureing -> capturing

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thaks. Speling is hard.

but only 10% for both summary and distribution.
"""

use GenServer
Expand Down
22 changes: 19 additions & 3 deletions lib/telemetry_metrics_statsd/event_handler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,20 @@ defmodule TelemetryMetricsStatsd.EventHandler do

@spec fetch_measurement(Metrics.t(), :telemetry.event_measurements()) ::
{:ok, number()} | :error
defp fetch_measurement(%Metrics.Counter{}, _measurements) do
defp fetch_measurement(%Metrics.Counter{} = metric, _measurements) do
# For counter, we can ignore the measurements and just use 0.
{:ok, 0}
case sample(metric) do
nil -> :error
_ -> {:ok, 0}
end
end

defp fetch_measurement(metric, measurements) do
value =
case metric.measurement do
case sample(metric) do
nil ->
nil

fun when is_function(fun, 1) ->
fun.(measurements)

Expand Down Expand Up @@ -123,4 +129,14 @@ defmodule TelemetryMetricsStatsd.EventHandler do
end
end)
end

@spec sample(Metrics.t()) :: Metrics.measurement() | nil
defp sample(metric) do
rate = Keyword.get(metric.reporter_options, :sample_rate, 1.0)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
rate = Keyword.get(metric.reporter_options, :sample_rate, 1.0)
rate = Keyword.get(metric.reporter_options, :sampling_rate, 1.0)

Can we use that name, or is sample_rate the correct wording?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Renamed. I wondered about that, but went with Bryan's comment from beam-telemetry/telemetry_metrics#33 since he was the last to say anything.

sample(metric, rate, :rand.uniform_real())
end

defp sample(metric, 1.0, _random_real), do: metric.measurement
defp sample(metric, rate, random_real) when rate >= random_real, do: metric.measurement
defp sample(_metric, _rate, _random_real), do: nil
end
18 changes: 9 additions & 9 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
%{
"certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},
"dialyxir": {:hex, :dialyxir, "0.5.1", "b331b091720fd93e878137add264bac4f644e1ddae07a70bf7062c7862c4b952", [:mix], [], "hexpm"},
"earmark": {:hex, :earmark, "1.3.2", "b840562ea3d67795ffbb5bd88940b1bed0ed9fa32834915125ea7d02e35888a5", [:mix], [], "hexpm"},
"dialyxir": {:hex, :dialyxir, "0.5.1", "b331b091720fd93e878137add264bac4f644e1ddae07a70bf7062c7862c4b952", [:mix], [], "hexpm", "6c32a70ed5d452c6650916555b1f96c79af5fc4bf286997f8b15f213de786f73"},
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you revert the dependency update change? This is unrelated to the PR, we can do it in another one 🙂

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

"earmark": {:hex, :earmark, "1.3.2", "b840562ea3d67795ffbb5bd88940b1bed0ed9fa32834915125ea7d02e35888a5", [:mix], [], "hexpm", "e3be2bc3ae67781db529b80aa7e7c49904a988596e2dbff897425b48b3581161"},
"erlex": {:hex, :erlex, "0.2.1", "cee02918660807cbba9a7229cae9b42d1c6143b768c781fa6cee1eaf03ad860b", [:mix], [], "hexpm"},
"ex_doc": {:hex, :ex_doc, "0.20.2", "1bd0dfb0304bade58beb77f20f21ee3558cc3c753743ae0ddbb0fd7ba2912331", [:mix], [{:earmark, "~> 1.3", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"},
"ex_doc": {:hex, :ex_doc, "0.20.2", "1bd0dfb0304bade58beb77f20f21ee3558cc3c753743ae0ddbb0fd7ba2912331", [:mix], [{:earmark, "~> 1.3", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "8e24fc8ff9a50b9f557ff020d6c91a03cded7e59ac3e0eec8a27e771430c7d27"},
"hackney": {:hex, :hackney, "1.15.1", "9f8f471c844b8ce395f7b6d8398139e26ddca9ebc171a8b91342ee15a19963f4", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
"idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
"jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
"makeup": {:hex, :makeup, "0.8.0", "9cf32aea71c7fe0a4b2e9246c2c4978f9070257e5c9ce6d4a28ec450a839b55f", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
"makeup_elixir": {:hex, :makeup_elixir, "0.13.0", "be7a477997dcac2e48a9d695ec730b2d22418292675c75aa2d34ba0909dcdeda", [:mix], [{:makeup, "~> 0.8", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"},
"makeup": {:hex, :makeup, "0.8.0", "9cf32aea71c7fe0a4b2e9246c2c4978f9070257e5c9ce6d4a28ec450a839b55f", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5fbc8e549aa9afeea2847c0769e3970537ed302f93a23ac612602e805d9d1e7f"},
"makeup_elixir": {:hex, :makeup_elixir, "0.13.0", "be7a477997dcac2e48a9d695ec730b2d22418292675c75aa2d34ba0909dcdeda", [:mix], [{:makeup, "~> 0.8", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "adf0218695e22caeda2820eaba703fa46c91820d53813a2223413da3ef4ba515"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm"},
"nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm"},
"nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm", "5c040b8469c1ff1b10093d3186e2e10dbe483cd73d79ec017993fb3985b8a9b3"},
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"},
"stream_data": {:hex, :stream_data, "0.4.3", "62aafd870caff0849a5057a7ec270fad0eb86889f4d433b937d996de99e3db25", [:mix], [], "hexpm"},
"telemetry": {:hex, :telemetry, "0.4.0", "8339bee3fa8b91cb84d14c2935f8ecf399ccd87301ad6da6b71c09553834b2ab", [:rebar3], [], "hexpm"},
"telemetry_metrics": {:hex, :telemetry_metrics, "0.3.0", "5de4037d058faf6355835c0ec65ff19605258ee696fa9f93304a389d2d497445", [:mix], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"},
"stream_data": {:hex, :stream_data, "0.4.3", "62aafd870caff0849a5057a7ec270fad0eb86889f4d433b937d996de99e3db25", [:mix], [], "hexpm", "7dafd5a801f0bc897f74fcd414651632b77ca367a7ae4568778191fc3bf3a19a"},
"telemetry": {:hex, :telemetry, "0.4.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm", "4738382e36a0a9a2b6e25d67c960e40e1a2c95560b9f936d8e29de8cd858480f"},
"telemetry_metrics": {:hex, :telemetry_metrics, "0.4.2", "1de986fad9aa6bf81f8a33ddfd16e5d8ab0dec6272e624eb517c1a92a44d41a9", [:mix], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e56ffed2dbe293ab6cf7c94980faeb368cb360662c1927f54fc634a4ca55362e"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"},
}
66 changes: 66 additions & 0 deletions test/telemetry_metrics_statsd_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,72 @@ defmodule TelemetryMetricsStatsdTest do
refute_reported(socket)
end

test "doesn't report data for Counter metric when outside sample rate" do
{socket, port} = given_udp_port_opened()

counter =
given_counter("http.requests",
event_name: "http.request",
reporter_options: [sample_rate: 0.1]
)

# :rand.uniform_real will return 0.68352076602406
:rand.seed(:exs1024, {1, 2, 3})

start_reporter(metrics: [counter], port: port)

:telemetry.execute([:http, :request], %{sample: 42})

refute_reported(socket)
end

test "doesn't report data when non-Counter metric outside sample rate" do
{socket, port} = given_udp_port_opened()
sum = given_sum("http.request.sample", reporter_options: [sample_rate: 0.1])

# :rand.uniform_real will return 0.68352076602406
:rand.seed(:exs1024, {1, 2, 3})

start_reporter(metrics: [sum], port: port)

:telemetry.execute([:http, :request], %{sample: 42})

refute_reported(socket)
end

test "report data for Counter metric when inside sample rate" do
{socket, port} = given_udp_port_opened()

counter =
given_counter("http.requests",
event_name: "http.request",
reporter_options: [sample_rate: 0.1]
)

# :rand.uniform_real will return 0.06947673849645647
:rand.seed(:exs1024, {1, 2, 21})

start_reporter(metrics: [counter], port: port)

:telemetry.execute([:http, :request], %{sample: 42})

assert_reported(socket, "http.requests:1|c")
end

test "report data when non-Counter metric inside sample rate" do
{socket, port} = given_udp_port_opened()
sum = given_sum("http.request.sample", reporter_options: [sample_rate: 0.1])

# :rand.uniform_real will return 0.06947673849645647
:rand.seed(:exs1024, {1, 2, 21})

start_reporter(metrics: [sum], port: port)

:telemetry.execute([:http, :request], %{sample: 42})

assert_reported(socket, "http.request.sample:+42|g")
end

defp given_udp_port_opened() do
{:ok, socket} = :gen_udp.open(0, [:binary, active: false])
{:ok, port} = :inet.port(socket)
Expand Down