Publishing custom telemetry events

I’m just starting out with Phoenix, thanks for your patience for the basic question that follows:

I want to publish the application start time as a metric.

Here’s what I have so far:

defmodule MyAppWeb.Telemetry do
  use Supervisor
  import Telemetry.Metrics
  
  @application_start_time System.os_time(:second)

  def start_link(arg) do
    Supervisor.start_link(__MODULE__, arg, name: __MODULE__)
  end

  @impl true
  def init(_arg) do
    children = [
      {TelemetryMetricsPrometheus, [metrics: MyAppWeb.Telemetry.metrics()]}
    ]

    
    ## Emit the application start time here
    emit_process_start_time()

    Supervisor.init(children, strategy: :one_for_one)
  end

  def metrics do
    [
      last_value("process.start.time.seconds")
    ]
  end
  
  def emit_process_start_time do
    :telemetry.execute([:process, :start, :time], %{seconds: @application_start_time})
  end
end

When I run the application, the metric is never published. When I manually execute MyAppWeb.Telemetry.emit_process_start_time() from iex, the metric is published.

I think my mistake is that in order for the metric to be exported it must be published after the Telemetry supervision tree has been started, but I’m uncertain of the best way of remedying that. The documentation suggests I should create my own metrics module, but for this simple task that seems a bit overkill. Indeed there may already be a metric that I can reuse that I’m not aware of!

Thanks for reading and for any help!

Well, the metric is fired, however it is fired way before the TelemetryMetricsPrometheus started listening to it. What you want to do is to run emit_process_start_time after supervisor children are started. There are few ways to do so:

  1. Fire it in a child:
    children = [
      {TelemetryMetricsPrometheus, [metrics: MyAppWeb.Telemetry.metrics()]},
      {Task, &emit_process_start_time/0}
    ]
    
    Supervisor.init(children, strategy: :one_for_one)
    
  2. Run it after supervisor is started:
    children = [
      {TelemetryMetricsPrometheus, [metrics: MyAppWeb.Telemetry.metrics()]}
    ]
    
    case Supervisor.init(children, strategy: :one_for_one) do
      {:ok, _pid} = ret ->
        emit_process_start_time()
    
        ret
      other -> other
    end
    
  3. Use start_phase/3:
    • add to your application/0 in mix.exs:
      [
        mod: {MyAppWeb.Application, []},
        start_phases: [{:emit, []}]
      ]
      
    • in your application module
      def start_phase(:emit, _type, _args) do
        emit_process_start_time()
      end
      
1 Like

Thanks very much! I’ll give those a go.