tutorial

How to Monitor Peep with Vigilmon

Track Peep's Telemetry metric reporters — StatsD, Prometheus, and custom sinks — alongside uptime and heartbeat monitoring for Elixir apps that need lightweight metric reporting without Phoenix Dashboard.

How to Monitor Peep with Vigilmon

Peep is a high-performance Telemetry metric reporter for Elixir that supports StatsD, Prometheus, and custom reporter backends. Where PromEx couples metrics to Phoenix Dashboard and LiveDashboard, Peep is intentionally minimal: attach it to your :telemetry events and it routes measurements to whatever sink your infrastructure uses, with no framework dependency.

Peep handles metric export. It does not tell you whether your app is up, whether the reporter process is running, or whether the StatsD/Prometheus endpoint is reachable from outside. Vigilmon fills that gap with external uptime checks and heartbeat monitors.

This tutorial covers:

  • A health check endpoint with Peep reporter status
  • HTTP uptime monitoring with Vigilmon
  • Heartbeat monitoring to detect a stalled Peep reporter
  • Alerts when metric export falls behind

Why Monitor Peep?

| Failure mode | Symptom without monitoring | |---|---| | Peep GenServer crashes | Metrics silently stop; dashboards go stale | | StatsD UDP socket unreachable | No error raised; data disappears | | Prometheus scrape endpoint returns 500 | Prometheus gaps; no alert to app team | | Telemetry event detach after hot code reload | Reporter no longer listening; silent data loss | | App unreachable | Metric reporter running but no real traffic |


Step 1: Add Peep and expose a health endpoint

Install Peep:

# mix.exs
{:peep, "~> 3.0"},
{:telemetry_metrics, "~> 1.0"},

Define your metrics and start a Peep reporter:

# lib/my_app/telemetry.ex
defmodule MyApp.Telemetry do
  import Telemetry.Metrics

  def metrics do
    [
      # Phoenix request metrics
      summary("phoenix.endpoint.stop.duration",
        unit: {:native, :millisecond},
        tags: [:method, :status]
      ),
      counter("phoenix.router_dispatch.stop.duration",
        tags: [:route]
      ),

      # Ecto query metrics
      summary("my_app.repo.query.total_time",
        unit: {:native, :millisecond},
        tags: [:source]
      ),

      # VM metrics
      last_value("vm.memory.total", unit: :byte),
      last_value("vm.total_run_queue_lengths.total"),
    ]
  end
end

Start Peep in your supervision tree (Prometheus example):

# lib/my_app/application.ex
def start(_type, _args) do
  children = [
    MyApp.Repo,
    MyAppWeb.Endpoint,
    {Peep, [
      name: MyApp.Peep,
      reporter: Peep.Reporters.Prometheus,
      metrics: MyApp.Telemetry.metrics()
    ]},
  ]

  Supervisor.start_link(children, strategy: :one_for_one, name: MyApp.Supervisor)
end

Expose a Prometheus scrape endpoint via a plug:

# lib/my_app_web/plugs/metrics.ex
defmodule MyAppWeb.Plugs.Metrics do
  import Plug.Conn

  def init(opts), do: opts

  def call(%Plug.Conn{request_path: "/metrics"} = conn, _opts) do
    case Peep.get_all_metrics(MyApp.Peep) do
      {:ok, metrics} ->
        conn
        |> put_resp_content_type("text/plain; version=0.0.4")
        |> send_resp(200, Peep.Reporters.Prometheus.scrape(metrics))
        |> halt()

      {:error, _} ->
        conn
        |> send_resp(503, "Peep reporter unavailable")
        |> halt()
    end
  end

  def call(conn, _opts), do: conn
end

Add a health check that includes Peep reporter status:

# lib/my_app_web/plugs/health_check.ex
defmodule MyAppWeb.Plugs.HealthCheck do
  import Plug.Conn

  def init(opts), do: opts

  def call(%Plug.Conn{request_path: "/health"} = conn, _opts) do
    checks = %{
      database:      check_database(),
      peep_reporter: check_peep(),
      memory:        check_memory()
    }

    status = if Enum.all?(checks, fn {_, v} -> v == :ok end), do: 200, else: 503

    conn
    |> put_resp_content_type("application/json")
    |> send_resp(status, Jason.encode!(%{
      status: if(status == 200, do: "ok", else: "degraded"),
      checks: checks
    }))
    |> halt()
  end

  def call(conn, _opts), do: conn

  defp check_database do
    case Ecto.Adapters.SQL.query(MyApp.Repo, "SELECT 1", []) do
      {:ok, _} -> :ok
      _ -> :error
    end
  end

  defp check_peep do
    case Process.whereis(MyApp.Peep) do
      pid when is_pid(pid) ->
        if Process.alive?(pid), do: :ok, else: :error
      nil ->
        :error
    end
  end

  defp check_memory do
    total_mb = :erlang.memory()[:total] / 1_048_576
    if total_mb < 1_500, do: :ok, else: :error
  end
end

Register both plugs before the router:

# lib/my_app_web/endpoint.ex
plug MyAppWeb.Plugs.HealthCheck
plug MyAppWeb.Plugs.Metrics
plug MyAppWeb.Router

Test:

curl http://localhost:4000/health | jq .
# {"status":"ok","checks":{"database":"ok","peep_reporter":"ok","memory":"ok"}}

curl http://localhost:4000/metrics | head -10
# # HELP phoenix_endpoint_stop_duration_milliseconds ...

Step 2: Set up HTTP monitoring in Vigilmon

  1. Sign up at vigilmon.online.
  2. Click New Monitor → HTTP.
  3. Enter https://your-app.example.com/health.
  4. Set check interval to 60 seconds.
  5. Add a keyword check for "ok" to catch degraded responses.
  6. Set alert threshold to 2 consecutive failures.

Add a second monitor for the Prometheus scrape endpoint if Prometheus is your primary metrics sink:

  • URL: https://your-app.example.com/metrics
  • Keyword check: phoenix_endpoint_stop_duration (or any stable metric name)

This catches cases where the app is up but the Peep reporter has crashed.


Step 3: Heartbeat monitoring for the Peep reporter

The Peep GenServer can restart after a crash without anyone noticing the gap in exported metrics. A watchdog GenServer that pings Vigilmon only when Peep is running gives you an external signal.

# lib/my_app/peep_watchdog.ex
defmodule MyApp.PeepWatchdog do
  use GenServer
  require Logger

  @interval :timer.seconds(60)

  def start_link(_), do: GenServer.start_link(__MODULE__, %{}, name: __MODULE__)

  @impl true
  def init(state) do
    schedule_check()
    {:ok, state}
  end

  @impl true
  def handle_info(:check, state) do
    case check_peep_health() do
      :ok ->
        ping_heartbeat()
      {:error, reason} ->
        Logger.warning("Peep reporter unhealthy: #{inspect(reason)}")
    end

    schedule_check()
    {:noreply, state}
  end

  defp check_peep_health do
    case Process.whereis(MyApp.Peep) do
      pid when is_pid(pid) ->
        if Process.alive?(pid), do: :ok, else: {:error, :dead}
      nil ->
        {:error, :not_registered}
    end
  end

  defp ping_heartbeat do
    url = System.get_env("VIGILMON_PEEP_HEARTBEAT_URL")
    if url do
      Task.start(fn ->
        :httpc.request(:get, {String.to_charlist(url), []}, [], [])
      end)
    end
  end

  defp schedule_check, do: Process.send_after(self(), :check, @interval)
end

Add to your supervision tree:

children = [
  MyApp.Repo,
  MyAppWeb.Endpoint,
  {Peep, [name: MyApp.Peep, reporter: Peep.Reporters.Prometheus, metrics: MyApp.Telemetry.metrics()]},
  MyApp.PeepWatchdog,
]

In Vigilmon, create a Heartbeat Monitor:

  1. Click New Monitor → Heartbeat.
  2. Set grace period to 3 minutes (watchdog runs every 60 s with a 2x buffer).
  3. Copy the ping URL into VIGILMON_PEEP_HEARTBEAT_URL.

Step 4: Key metrics to alert on

| Metric | Alert threshold | Why | |---|---|---| | /health HTTP status | Non-200 for 2 checks | App or reporter down | | peep_reporter check | error in body | GenServer not alive | | /metrics keyword check | Missing expected metric name | Peep scrape broken | | Heartbeat silence | Grace period exceeded | Reporter crashed or app stopped |


Step 5: Status page

  1. In Vigilmon, go to Status Pages → New.
  2. Add your HTTP monitor, /metrics monitor, and Peep heartbeat.
  3. Label them "App health", "Prometheus scrape", and "Peep reporter".
  4. Share the URL in your infrastructure runbook so the team can distinguish app downtime from metrics-pipeline failures.

Conclusion

Peep makes metric reporting fast and dependency-free. Vigilmon makes sure the reporter and the app it runs in are actually healthy. Together:

  • Uptime checks detect when your app is unreachable
  • Metrics endpoint monitor catches silent Peep reporter failures
  • Heartbeat monitors alert when the reporter goes dark between scrapes

Start with the /health endpoint and the Peep watchdog heartbeat, then add a /metrics keyword monitor once Prometheus scraping is stable.

Get started free at vigilmon.online.

Monitor your app with Vigilmon

Free plan — 5 monitors, no credit card required. Up and running in 60 seconds.

Start free →