Skip to content

Commit

Permalink
implement basic telemetry support
Browse files Browse the repository at this point in the history
  • Loading branch information
asakura committed Nov 2, 2023
1 parent cd9ad8d commit 6938637
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 2 deletions.
17 changes: 16 additions & 1 deletion lib/mixpanel/client.ex
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,16 @@ defmodule Mixpanel.Client do
@impl GenServer
@spec init([option, ...]) :: {:ok, State.t()}
def init(opts) do
{:ok, State.new(opts)}
Process.flag(:trap_exit, true)
state = State.new(opts)

client_span =
Mixpanel.Telemetry.start_span(:client, %{}, %{
base_url: State.base_url(state),
http_adapter: State.http_adapter(state)
})

{:ok, State.attach_span(state, client_span)}
end

@spec handle_cast(
Expand Down Expand Up @@ -200,6 +209,12 @@ defmodule Mixpanel.Client do
{:noreply, state}
end

@impl GenServer
@spec terminate(reason, State.t()) :: :ok
when reason: :normal | :shutdown | {:shutdown, term} | term
def terminate(_reason, state),
do: Mixpanel.Telemetry.stop_span(State.span(state))

defp put_token(events, project_token) when is_list(events),
do: Enum.map(events, &put_token(&1, project_token))

Expand Down
16 changes: 15 additions & 1 deletion lib/mixpanel/client/state.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ defmodule Mixpanel.Client.State do
}

@enforce_keys [:project_token, :base_url, :http_adapter]
defstruct [:project_token, :base_url, :http_adapter]
defstruct [:project_token, :base_url, :http_adapter, :span]

def new(opts) do
project_token = Keyword.fetch!(opts, :project_token)
Expand All @@ -24,4 +24,18 @@ defmodule Mixpanel.Client.State do
http_adapter: http_adapter
}
end

@spec attach_span(t(), Mixpanel.Telemetry.t()) :: t()
def attach_span(state, span) do
%__MODULE__{state | span: span}
end

@spec base_url(t()) :: base_url
def base_url(state), do: state.base_url

@spec http_adapter(t()) :: module
def http_adapter(state), do: state.http_adapter

@spec span(t()) :: Mixpanel.Telemetry.t()
def span(state), do: state.span
end
118 changes: 118 additions & 0 deletions lib/mixpanel/telemetry.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
defmodule Mixpanel.Telemetry do
@moduledoc """
The following telemetry spans are emitted by mixpanel_api_ex:
## `[:mixpanel_api_ex, :client, *]`
Represents a Mixpanel API client is ready
This span is started by the following event:
* `[:mixpanel_api_ex, :client, :start]`
Represents the start of the span
This event contains the following measurements:
* `monotonic_time` - The time of this event, in `:native` units
This event contains the following metadata:
* `base_url` - The URL which a client instance uses to communicate with
the Mixpanel API
* `http_adapter` - The HTTP adapter which a client instance uses to send
actual requests to the backend
This span is ended by the following event:
* `[:mixpanel_api_ex, :client, :stop]`
Represents the end of the span
This event contains the following measurements:
* `monotonic_time`: The time of this event, in `:native` units
* `duration`: The span duration, in `:native` units
This event contains the following metadata:
* `base_url` - The URL which a client instance uses to communicate with
the Mixpanel API
* `http_adapter` - The HTTP adapter which a client instance uses to send
actual requests to the backend
"""

@enforce_keys [:span_name, :telemetry_span_context, :start_time, :start_metadata]
defstruct @enforce_keys

@type t :: %__MODULE__{
span_name: span_name,
telemetry_span_context: reference,
start_time: integer,
start_metadata: metadata
}

@type span_name :: :client
@type metadata :: :telemetry.event_metadata()

@typedoc false
@type measurements :: :telemetry.event_measurements()

@typedoc false
@type event_name :: :ready

@typedoc false
@type untimed_event_name :: :stop

@app_name :mixpanel_api_ex

@doc false
@spec start_span(span_name(), measurements(), metadata()) :: t()
def start_span(span_name, measurements, metadata) do
measurements = Map.put_new_lazy(measurements, :monotonic_time, &monotonic_time/0)
telemetry_span_context = make_ref()
metadata = Map.put(metadata, :telemetry_span_context, telemetry_span_context)
_ = event([span_name, :start], measurements, metadata)

%__MODULE__{
span_name: span_name,
telemetry_span_context: telemetry_span_context,
start_time: measurements[:monotonic_time],
start_metadata: metadata
}
end

@doc false
@spec stop_span(t(), measurements(), metadata()) :: :ok
def stop_span(span, measurements \\ %{}, metadata \\ %{}) do
measurements = Map.put_new_lazy(measurements, :monotonic_time, &monotonic_time/0)

measurements =
Map.put(measurements, :duration, measurements[:monotonic_time] - span.start_time)

metadata = Map.merge(span.start_metadata, metadata)

untimed_span_event(span, :stop, measurements, metadata)
end

# @doc false
# @spec span_event(t(), event_name(), measurements(), metadata()) :: :ok
# def span_event(span, name, measurements \\ %{}, metadata \\ %{}) do
# end

@doc false
@spec untimed_span_event(t(), event_name() | untimed_event_name(), measurements(), metadata()) ::
:ok
def untimed_span_event(span, name, measurements \\ %{}, metadata \\ %{}) do
metadata = Map.put(metadata, :telemetry_span_context, span.telemetry_span_context)
event([span.span_name, name], measurements, metadata)
end

@spec monotonic_time() :: integer
defdelegate monotonic_time, to: System

defp event(suffix, measurements, metadata) do
:telemetry.execute([@app_name | suffix], measurements, metadata)
end
end

0 comments on commit 6938637

Please sign in to comment.