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 metrics #27

Closed
wants to merge 8 commits into from
Closed
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
27 changes: 24 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

Wraps some [`:opencensus`][:opencensus] capabilities for Elixir users so
they don't have to [learn them some Erlang][lyse] in order to get
[OpenCensus] distributed tracing.
[OpenCensus] distributed tracing and metrics.

[opencensus]: http://opencensus.io
[:opencensus]: https://hex.pm/packages/opencensus
Expand All @@ -25,6 +25,7 @@ end
```

## Usage
### Tracing

Wrap your code with the
[`Opencensus.Trace.with_child_span/3`][oce-with_child_span-3] macro to
Expand All @@ -40,6 +41,20 @@ def traced_fn(arg) do
end
```

### Metrics

Register measures, define aggregations, record measures:
```elixir
alias Opencensus.Metrics

def foo do
Metrics.new(:latency, "the latency in milliseconds", :milli_seconds)
Metrics.aggregate_count("request_count", :latency, "number of requests", [:protocol, :operation])
Metrics.aggregate_distribution("latencies_distribution", :latency, "distribution of latencies", [:protocol, :operation], [10, 50, 100, 500])
Metrics.record(:latency, 121.2, %{protocol: :http, operation: :get_cats})
end
```

## Alternatives

If you prefer driving Erlang packages directly (see also `:telemetry`), copy
Expand Down Expand Up @@ -74,16 +89,22 @@ end

## Troubleshooting

To see your spans, use the `:oc_reporter_stdout` reporter, either in config:
To see your spans, use the `:oc_reporter_stdout` reporter. To see your metrics, use the `:oc_stdout_exporter` exporter.
You can set it either in config:

```elixir
config :opencensus, reporters: [{:oc_reporter_stdout, []}]
config :opencensus,
reporters: [{:oc_reporter_stdout, []}],
stat: [
exporters: [{:oc_stdout_exporter, []}]
]
```

... or at the `iex` prompt:

```plain
iex> :oc_reporter.register(:oc_reporter_stdout)
iex> :oc_stat_exporter.register(:oc_stdout_exporter)
```

[oce-with_child_span-3]: https://hexdocs.pm/opencensus_elixir/Opencensus.Trace.html#with_child_span/3
Expand Down
12 changes: 10 additions & 2 deletions config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ if Mix.env() == :test do
report_dir: "reports/exunit"

config :opencensus,
reporters: [{Opencensus.TestSupport.SpanCaptureReporter, []}],
send_interval_ms: 100
reporters: [
{Opencensus.TestSupport.SpanCaptureReporter, []}
],
send_interval_ms: 100,
stat: [
export_interval: 100,
exporters: [
{Opencensus.TestSupport.MetricsCaptureExporter, []}
Copy link
Collaborator

Choose a reason for hiding this comment

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

I would dynamically attach exporter in tests as it will allow to make test async.

Copy link
Author

Choose a reason for hiding this comment

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

Hmm, I don't think attaching it dynamically makes any difference. I think it's fine to just make tests async (which I just did)

]
]
end
96 changes: 96 additions & 0 deletions lib/opencensus/metrics.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
defmodule Opencensus.Metrics do
@moduledoc """
Functions to help Elixir programmers use OpenCensus metrics.

First, a measure must be created:
```elixir
Metrics.new(:name, "description", :unit)
Metrics.new(:another, "", :unit)
```

Next step is to choose aggregations to be exported:
```elixir
Metrics.aggregate_count(:name_count, :name, "count", [:tag1, :tag2])
Metrics.aggregate_gauge(:name_gauge, :name, "gauge", [:tag1, :tag2])
Metrics.aggregate_sum(:name_sum, :name, "sum", [:tag1, :tag2])
Metrics.aggregate_distribution(:name_distribution, :name, "distribution", [:tag1, :tag2], [0, 100, 1000])
```

After aggregations are decided, measures may be recorded by explicitly providing tags:
```elixir
Metrics.record(:name}, 3, %{tag1: "v1", tag2: "v2"})
Metrics.record([another: 1, name: 100], tag1: "v1", tag2: "v2)
```
or using tag values that are present in process dictionary:
```elixir
Metrics.record(:name, 3)
```
"""

defdelegate new(name, description, unit), to: :oc_stat_measure

@doc """
count of how many times `measure` was recorded will be exported
"""
def aggregate_count(name, measure, description, tags) do
:oc_stat_view.subscribe(name, measure, description, tags, :oc_stat_aggregation_count)
end

@doc """
only latest recorded value of `measure` will be exported
"""
def aggregate_gauge(name, measure, description, tags) do
:oc_stat_view.subscribe(name, measure, description, tags, :oc_stat_aggregation_latest)
end

@doc """
sum of all recorded values of `measure` will be exported
"""
def aggregate_sum(name, measure, description, tags) do
:oc_stat_view.subscribe(name, measure, description, tags, :oc_stat_aggregation_sum)
end

@doc """
distribution of all recorded values of `measure` across all `buckets` will be exported
"""
def aggregate_distribution(name, measure, description, tags, buckets) do
:oc_stat_view.subscribe(
name,
measure,
description,
tags,
{:oc_stat_aggregation_distribution, buckets: buckets}
)
end

@doc """
records single measure
"""
def record(measure, value, %{} = tags) when is_number(value) do
:oc_stat.record(tags, measure, value)
end

def record(measure, value, tags) when is_list(tags) and is_number(value) do
tags = Enum.into(tags, %{})
record(measure, value, tags)
end

@doc """
records multiple measures
"""
def record(measures, %{} = tags) when is_list(measures) do
:oc_stat.record(tags, measures)
end

def record(measures, tags) when is_list(measures) and is_list(tags) do
tags = Enum.into(tags, %{})
record(measures, tags)
end

@doc """
records single measure, takes tags from process dictionary
"""
def record(measure, value) when is_atom(measure) and is_number(value) do
:ocp.record(measure, value)
end
end
39 changes: 39 additions & 0 deletions lib/opencensus/test_support/metrics_capture_exporter.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
defmodule Opencensus.TestSupport.MetricsCaptureExporter do
@moduledoc """
An `:oc_stat_exporter` to capture exported metrics. To wait for next exported data to be returned, call `capture_next`.
"""
use GenServer
@behaviour :oc_stat_exporter

def start_link do
GenServer.start_link(__MODULE__, nil, name: __MODULE__)
end

def capture_next do
GenServer.call(__MODULE__, :capture_next)
end

@impl GenServer
def init(_arg) do
{:ok, [send_to: nil]}
end

@impl :oc_stat_exporter
def export(view, _config) do
GenServer.cast(__MODULE__, {:export, view})
end

@impl GenServer
def handle_call(:capture_next, from, send_to: nil) do
{:noreply, send_to: from}
end

@impl GenServer
def handle_cast({:export, view}, send_to: pid) do
unless pid == nil do
GenServer.reply(pid, view)
end

{:noreply, send_to: nil}
end
end
2 changes: 1 addition & 1 deletion mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"},
"mix_test_watch": {:hex, :mix_test_watch, "0.9.0", "c72132a6071261893518fa08e121e911c9358713f62794a90c95db59042af375", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm"},
"nimble_parsec": {:hex, :nimble_parsec, "0.4.0", "ee261bb53214943679422be70f1658fff573c5d0b0a1ecd0f18738944f818efe", [:mix], [], "hexpm"},
"opencensus": {:hex, :opencensus, "0.9.1", "47481a832f46883d9b5cdec51d3c693e40e7324a5e3b4dfd9012a27152aa6df6", [:rebar3], [{:counters, "~> 0.2.1", [hex: :counters, repo: "hexpm", optional: false]}, {:ctx, "~> 0.5", [hex: :ctx, repo: "hexpm", optional: false]}, {:wts, "~> 0.3", [hex: :wts, repo: "hexpm", optional: false]}], "hexpm"},
"opencensus": {:hex, :opencensus, "0.9.2", "ab36b0c4e4500b976180bd088cea7520d345711a72df2d7188e2d7b9573a8728", [:rebar3], [{:counters, "~> 0.2.1", [hex: :counters, repo: "hexpm", optional: false]}, {:ctx, "~> 0.5", [hex: :ctx, repo: "hexpm", optional: false]}, {:wts, "~> 0.3", [hex: :wts, repo: "hexpm", optional: false]}], "hexpm"},
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"},
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
"rfc3339": {:hex, :rfc3339, "0.9.0", "2075653dc9407541c84b1e15f8bda2abe95fb17c9694025e079583f2d19c1060", [:mix, :rebar], [], "hexpm"},
Expand Down
Loading