Powered by AppSignal & Oban Pro

Runs and Reliability

05-runs-and-reliability.livemd

Runs and Reliability

Setup

Mix.install([
  {:postgrex, "~> 0.22.0"},
  {:kino, "~> 0.19.0"},
  {:kino_vega_lite, "~> 0.1.13"}
])

Code.require_file("/opt/emisar/product_analytics.exs")
alias EmisarProductAnalytics, as: Analytics
db = Analytics.connect!()

Terminal reliability, 30 days

reliability =
  Analytics.query(db, """
  SELECT
    count(*) AS runs,
    count(*) FILTER (WHERE status = 'success') AS successful,
    count(*) FILTER (WHERE status IN ('failed', 'error', 'validation_failed', 'unknown_action', 'cancelled', 'timed_out', 'refused', 'denied')) AS unsuccessful,
    count(*) FILTER (WHERE status IN ('pending', 'pending_approval', 'sent', 'running')) AS unsettled,
    round(percentile_cont(0.50) WITHIN GROUP (ORDER BY duration_ms) FILTER (WHERE duration_ms IS NOT NULL))::bigint AS p50_duration_ms,
    round(percentile_cont(0.95) WITHIN GROUP (ORDER BY duration_ms) FILTER (WHERE duration_ms IS NOT NULL))::bigint AS p95_duration_ms
  FROM action_runs
  WHERE inserted_at >= now() - interval '30 days'
  """)
  |> hd()

terminal = reliability["successful"] + reliability["unsuccessful"]

Analytics.kpis([
  {"Runs, 30d", reliability["runs"]},
  {"Terminal success", Analytics.percent(reliability["successful"], terminal)},
  {"Unsuccessful", reliability["unsuccessful"]},
  {"Unsettled", reliability["unsettled"]},
  {"p50 duration", "#{reliability["p50_duration_ms"] || 0} ms"},
  {"p95 duration", "#{reliability["p95_duration_ms"] || 0} ms"}
])

Daily outcomes

daily_outcomes =
  Analytics.query(db, """
  WITH days AS (
    SELECT generate_series(current_date - 29, current_date, interval '1 day')::date AS day
  ), outcomes AS (
    SELECT inserted_at::date AS day,
      CASE WHEN status = 'success' THEN 'success'
           WHEN status IN ('pending', 'pending_approval', 'sent', 'running') THEN 'unsettled'
           ELSE 'unsuccessful' END AS outcome,
      count(*) AS runs
    FROM action_runs
    WHERE inserted_at >= current_date - 29
    GROUP BY inserted_at::date, outcome
  )
  SELECT to_char(days.day, 'YYYY-MM-DD') AS day, labels.outcome, coalesce(outcomes.runs, 0) AS runs
  FROM days
  CROSS JOIN (VALUES ('success'), ('unsuccessful'), ('unsettled')) labels(outcome)
  LEFT JOIN outcomes ON outcomes.day = days.day AND outcomes.outcome = labels.outcome
  ORDER BY days.day, labels.outcome
  """)

Analytics.stacked_bar(daily_outcomes, "day", "runs", "outcome", title: "Daily run outcomes", y_title: "Runs")

Action reliability

actions =
  Analytics.query(db, """
  SELECT action_id,
    count(*) AS runs,
    count(*) FILTER (WHERE status = 'success') AS successful,
    round(100.0 * count(*) FILTER (WHERE status = 'success') / NULLIF(count(*) FILTER (WHERE status IN ('success', 'failed', 'error', 'validation_failed', 'unknown_action', 'cancelled', 'timed_out', 'refused', 'denied')), 0), 1)::float8 AS success_rate,
    round(percentile_cont(0.50) WITHIN GROUP (ORDER BY duration_ms) FILTER (WHERE duration_ms IS NOT NULL))::bigint AS p50_ms,
    round(percentile_cont(0.95) WITHIN GROUP (ORDER BY duration_ms) FILTER (WHERE duration_ms IS NOT NULL))::bigint AS p95_ms
  FROM action_runs
  WHERE inserted_at >= now() - interval '30 days'
  GROUP BY action_id
  ORDER BY runs DESC
  LIMIT 100
  """)

Analytics.table(actions)

Runner reliability

runners =
  Analytics.query(db, """
  SELECT a.name AS account, r.name AS runner,
    count(ar.id) FILTER (WHERE ar.inserted_at >= now() - interval '30 days') AS runs_30d,
    count(ar.id) FILTER (WHERE ar.inserted_at >= now() - interval '30 days' AND ar.status <> 'success' AND ar.status NOT IN ('pending', 'pending_approval', 'sent', 'running', 'cancelling')) AS unsuccessful_30d,
    max(ar.inserted_at) AS last_run_at,
    r.last_connected_at,
    r.last_disconnected_at
  FROM runners r
  JOIN accounts a ON a.id = r.account_id
  LEFT JOIN action_runs ar ON ar.runner_id = r.id
  WHERE r.deleted_at IS NULL
  GROUP BY a.name, r.id, r.name, r.last_connected_at, r.last_disconnected_at
  ORDER BY unsuccessful_30d DESC, runs_30d DESC
  LIMIT 500
  """)

Analytics.table(runners)