AutomationFlowsData & Sheets › Metrics Snapshot

Metrics Snapshot

Metrics-Snapshot. Uses httpRequest, postgres. Event-driven trigger; 13 nodes.

Event trigger★★★★☆ complexity13 nodesHTTP RequestPostgres
Data & Sheets Trigger: Event Nodes: 13 Complexity: ★★★★☆ Added:

This workflow follows the HTTP Request → Postgres recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "nodes": [
    {
      "parameters": {},
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        160,
        1008
      ],
      "id": "3ffe52cf-0477-43ea-85d7-d08c0eb0fca8",
      "name": "Manual Trigger \u2014 Collect Metrics"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "8ebd10c8-26ee-431b-aa4c-a0fae1dee5e4",
              "name": "INSTANCE_ID",
              "value": "prod",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        528,
        928
      ],
      "id": "2f2d3f24-1e37-434b-a3f0-4a496f5b1fe9",
      "name": "Set Instance Context"
    },
    {
      "parameters": {},
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        784,
        928
      ],
      "id": "f9ec8b4b-9e87-48a8-bc3a-3d7c84d94e9a",
      "name": "Fetch n8n Prometheus Metrics"
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "INSERT INTO public.n8n_metrics_snapshot\n(ts, instance_id, n8n_version, node_version,\n process_start_time_seconds,\n is_leader, active_workflows,\n cpu_total_seconds, memory_rss_bytes, heap_used_bytes, external_memory_bytes,\n eventloop_lag_p99_s, open_fds)\nVALUES\n($1, $2, $3, $4,\n $5,\n $6, $7,\n $8, $9, $10, $11,\n $12, $13);\n",
        "options": {
          "queryReplacement": "={{$json.snapshotParams.ts}}\n\n{{$json.snapshotParams.instance_id}}\n\n{{$json.snapshotParams.n8n_version}}\n\n{{$json.snapshotParams.node_version}}\n\n{{$json.snapshotParams.process_start_time_seconds}}\n\n{{$json.snapshotParams.is_leader}}\n\n{{$json.snapshotParams.active_workflows}}\n\n{{$json.snapshotParams.cpu_total_seconds}}\n\n{{$json.snapshotParams.memory_rss_bytes}}\n\n{{$json.snapshotParams.heap_used_bytes}}\n\n{{$json.snapshotParams.external_memory_bytes}}\n\n{{$json.snapshotParams.eventloop_lag_p99_s}}\n\n{{$json.snapshotParams.open_fds}}"
        }
      },
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        1296,
        880
      ],
      "id": "0bb06cd0-df34-4edc-8712-390aeb5c772a",
      "name": "Insert Metrics Snapshot"
    },
    {
      "parameters": {
        "content": "## Metrics \u2014 Collect & Store Instance Snapshot\n\nThis workflow collects runtime metrics from the n8n instance\nvia the Prometheus endpoint and stores normalized snapshots\nin Pulse for dashboards and historical analysis.\n\nIncludes:\n- Instance metrics (CPU, memory, event loop)\n- Execution counters and rates\n- Health indicators\n\nRuns periodically per instance.\n",
        "height": 448,
        "width": 416
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        0
      ],
      "typeVersion": 1,
      "id": "caca756c-8489-4691-8960-7d04a3a3c78e",
      "name": "Sticky Note"
    },
    {
      "parameters": {
        "content": "## Fetch Metrics\n\nCalls the n8n `/metrics` endpoint and retrieves raw\nPrometheus metrics exposed by the instance.\n\nThese metrics reflect current runtime state and\nexecution activity.\n",
        "height": 752,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        704,
        464
      ],
      "typeVersion": 1,
      "id": "66288651-8ce1-4c05-b4bd-9150d5a6e92d",
      "name": "Sticky Note1"
    },
    {
      "parameters": {
        "content": "## Parse Metrics\n\nParses raw metrics from http reqest node and prepares them for storage in Pulse.\n\nIt extracts key values like CPU, memory, event loop lag, workflow activity,\nand execution stats, then formats them into structured data used by dashboards\nand monitoring.\n\nPurpose: make metrics readable and ready for analysis.\n\n\n",
        "height": 752,
        "width": 256,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        960,
        464
      ],
      "typeVersion": 1,
      "id": "0de2e630-08b0-4a3b-9b3e-44221869e97a",
      "name": "Sticky Note2"
    },
    {
      "parameters": {
        "content": "## Insert Metrics Snapshot\n\nStores a periodic snapshot of system metrics in the database.\n\nCaptures key indicators like CPU, memory, workflow activity,\nand runtime status to track system health over time.\n\nUsed for dashboards, trends, and performance monitoring.\n\n## Upsert Metrics Series + Samples\n\nStores detailed time-series metrics.\n\nCreates or updates metric series definitions, then inserts\nthe latest metric values with timestamps.\n\nEnsures metrics are organized and ready for charts,\nanalysis, and historical tracking.",
        "height": 752,
        "width": 640,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1232,
        464
      ],
      "typeVersion": 1,
      "id": "27c6bb69-c48e-42df-b145-9aff4c5465c8",
      "name": "Sticky Note3"
    },
    {
      "parameters": {
        "content": "## Instance Selection\n\nDefines which environment this workflow reports metrics for.\n\nSet this value to match the current n8n instance\n(e.g. prod, dev, test).\n\nMetrics and snapshots will be tagged with this identifier\nso dashboards can separate environments correctly.\n",
        "height": 752,
        "width": 256,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        432,
        464
      ],
      "typeVersion": 1,
      "id": "8cde307e-4c04-477d-9b5d-2b94e5540eb5",
      "name": "Sticky Note4"
    },
    {
      "parameters": {
        "content": "### Triggers\nRun every 5 Minutes (schedule) or via manual test. Both paths go into the same flow.",
        "height": 752,
        "width": 416,
        "color": 7
      },
      "id": "011b74d6-16a0-4e5e-914a-db8671bcce41",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        464
      ],
      "typeVersion": 1
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes"
            }
          ]
        }
      },
      "id": "5cc0d120-58f0-496f-8ca4-67548a8c1e59",
      "name": "Run every 5 Minutes",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        160,
        848
      ],
      "typeVersion": 1.2
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "WITH\nseries_in AS (\n  SELECT *\n  FROM jsonb_to_recordset($1::jsonb) AS x(\n    key text,\n    instance_id text,\n    metric_name text,\n    labels jsonb,\n    labels_hash text,\n    metric_type text,\n    help text\n  )\n),\nupsert_series AS (\n  INSERT INTO public.metrics_series\n    (instance_id, metric_name, labels, labels_hash, metric_type, help)\n  SELECT\n    instance_id, metric_name, labels, labels_hash, metric_type, help\n  FROM series_in\n  ON CONFLICT (instance_id, metric_name, labels_hash)\n  DO UPDATE SET\n    labels      = EXCLUDED.labels,\n    metric_type = COALESCE(EXCLUDED.metric_type, public.metrics_series.metric_type),\n    help        = COALESCE(EXCLUDED.help, public.metrics_series.help)\n  RETURNING\n    id,\n    instance_id,\n    metric_name,\n    labels_hash\n),\nseries_keys AS (\n  SELECT\n    si.key,\n    us.id AS series_id\n  FROM series_in si\n  JOIN upsert_series us\n    ON us.instance_id = si.instance_id\n   AND us.metric_name = si.metric_name\n   AND us.labels_hash = si.labels_hash\n),\nsamples_in AS (\n  SELECT *\n  FROM jsonb_to_recordset($2::jsonb) AS y(\n    key text,\n    ts timestamptz,\n    value double precision\n  )\n)\nINSERT INTO public.metrics_samples (series_id, ts, value)\nSELECT\n  sk.series_id,\n  sm.ts,\n  sm.value\nFROM samples_in sm\nJOIN series_keys sk\n  ON sk.key = sm.key\nON CONFLICT (series_id, ts)\nDO UPDATE SET value = EXCLUDED.value;",
        "options": {
          "queryReplacement": "=$1 = {{ JSON.stringify($json.seriesRows) }}\n\n$2 = {{ JSON.stringify($json.sampleRows) }}"
        }
      },
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        1296,
        1056
      ],
      "id": "0929cf53-cda7-4d07-8fd2-5b8bafde031e",
      "name": "Upsert Metrics Series + Samples"
    },
    {
      "parameters": {
        "jsCode": "const INSTANCE_ID = $('Set Instance Context').first().json.INSTANCE_ID;\n\nconst USE_HIGH_RES_TS = true;\n\nconst ROUND_SECONDS = 60;\n\nconst RESPECT_LINE_TIMESTAMPS = false;\n\nconst inItem = $input.first().json;\nconst raw = inItem.data ?? '';\nif (typeof raw !== 'string' || raw.length === 0) {\n  return [{ json: { error: 'No metrics text found in $json.data', gotType: typeof raw } }];\n}\nconst text = raw.includes('\\\\n') ? raw.replace(/\\\\n/g, '\\n') : raw;\n\nfunction roundedNowIso(roundSeconds) {\n  const ms = Date.now();\n  const step = roundSeconds * 1000;\n  const rounded = Math.floor(ms / step) * step;\n  return new Date(rounded).toISOString();\n}\n\nfunction nowIso() {\n  return new Date().toISOString();\n}\n\nfunction parseLabels(labelsStr) {\n  if (!labelsStr) return {};\n  const parts = labelsStr.split(/,(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)/);\n  const labels = {};\n  for (const part of parts) {\n    const p = part.trim();\n    if (!p) continue;\n    const eq = p.indexOf('=');\n    if (eq === -1) continue;\n    const key = p.slice(0, eq).trim();\n    let val = p.slice(eq + 1).trim();\n    val = val.replace(/^\"|\"$/g, '');\n    val = val.replace(/\\\\\"/g, '\"');\n    val = val.replace(/\\\\\\\\/g, '\\\\');\n    labels[key] = val;\n  }\n  return labels;\n}\n\nfunction canonicalLabels(labelsObj) {\n  const keys = Object.keys(labelsObj).sort();\n  const out = {};\n  for (const k of keys) out[k] = labelsObj[k];\n  return out;\n}\n\nfunction labelsHash(labelsObj) {\n  return JSON.stringify(canonicalLabels(labelsObj));\n}\n\nfunction seriesKey(instanceId, metricName, labelsHashVal) {\n  return `${instanceId}||${metricName}||${labelsHashVal}`;\n}\n\nfunction hasNoLabels(s) {\n  return !s.labels || Object.keys(s.labels).length === 0;\n}\n\nfunction findNoLabelValue(samples, patterns) {\n  for (const re of patterns) {\n    const s = samples.find(x => re.test(x.name) && hasNoLabels(x));\n    if (s && Number.isFinite(s.value)) return s.value;\n  }\n  return null;\n}\n\nfunction findLabelValue(samples, namePatterns, labelKeys) {\n  for (const re of namePatterns) {\n    const s = samples.find(x => re.test(x.name));\n    if (!s || !s.labels) continue;\n    for (const k of labelKeys) {\n      if (s.labels[k] != null) return String(s.labels[k]);\n    }\n  }\n  return null;\n}\n\nfunction baseNameForSuffixMetric(name) {\n  const m = name.match(/^(.*)_(bucket|sum|count)$/);\n  return m ? m[1] : null;\n}\n\nfunction normalizeType(name, directType, typeByBaseName) {\n  if (directType) return directType;\n  const base = baseNameForSuffixMetric(name);\n  if (!base) return null;\n  return typeByBaseName.get(base) ?? null;\n}\n\nconst helpByName = new Map();\nconst typeByName = new Map();\nconst typeByBaseName = new Map();\nconst samples = [];\n\nconst SCRAPE_TS = USE_HIGH_RES_TS ? nowIso() : roundedNowIso(ROUND_SECONDS);\n\nconst metricLineRe =\n  /^([a-zA-Z_:][a-zA-Z0-9_:]*)(\\{([^}]*)\\})?\\s+([+-]?\\d+(\\.\\d+)?([eE][+-]?\\d+)?|NaN|\\+Inf|-Inf)(\\s+(\\d+))?$/;\n\nfor (const line of text.split('\\n')) {\n  const l = line.trim();\n  if (!l) continue;\n\n  if (l.startsWith('# HELP ')) {\n    const m = l.match(/^# HELP ([^\\s]+)\\s+(.+)$/);\n    if (m) helpByName.set(m[1], m[2]);\n    continue;\n  }\n\n  if (l.startsWith('# TYPE ')) {\n    const m = l.match(/^# TYPE ([^\\s]+)\\s+([^\\s]+)$/);\n    if (m) {\n      const metric = m[1];\n      const t = m[2];\n      typeByName.set(metric, t);\n      typeByBaseName.set(metric, t);\n    }\n    continue;\n  }\n\n  if (l.startsWith('#')) continue;\n\n  const m = l.match(metricLineRe);\n  if (!m) continue;\n\n  const name = m[1];\n  const labelsStr = m[3] || '';\n  const rawValue = m[4];\n  const tsRaw = m[8] ? Number(m[8]) : null;\n\n  const value = Number(rawValue);\n  if (!Number.isFinite(value)) continue;\n\n  const ts = (RESPECT_LINE_TIMESTAMPS && tsRaw)\n    ? new Date(tsRaw).toISOString()\n    : SCRAPE_TS;\n\n  const labels = parseLabels(labelsStr);\n  const directType = typeByName.get(name) ?? null;\n  const metric_type = normalizeType(name, directType, typeByBaseName);\n  let help = helpByName.get(name) ?? null;\n  if (!help) {\n    const base = baseNameForSuffixMetric(name);\n    if (base) help = helpByName.get(base) ?? null;\n  }\n\n  samples.push({\n    name,\n    labels,\n    value,\n    metric_type,\n    help,\n    ts,\n  });\n}\n\nconst seriesMap = new Map();\nconst sampleMap = new Map();\n\nfor (const s of samples) {\n  const labels = canonicalLabels(s.labels || {});\n  const lh = labelsHash(labels);\n  const key = seriesKey(INSTANCE_ID, s.name, lh);\n\n  if (!seriesMap.has(key)) {\n    seriesMap.set(key, {\n      key,\n      instance_id: INSTANCE_ID,\n      metric_name: s.name,\n      labels,\n      labels_hash: lh,\n      metric_type: s.metric_type,\n      help: s.help,\n    });\n  }\n  const sampleKey = `${key}||${s.ts}`;\n  sampleMap.set(sampleKey, { key, ts: s.ts, value: s.value });\n}\n\nconst seriesRows = Array.from(seriesMap.values());\nconst sampleRows = Array.from(sampleMap.values());\nconst snapshotParams = {\n  ts: SCRAPE_TS,\n  instance_id: INSTANCE_ID,\n\n  n8n_version: findLabelValue(\n  samples,\n  [/^n8n_(?!nodejs_).*version.*info$/i],\n  ['version', 'n8n_version', 'build_version', 'release']\n),\n  node_version: findLabelValue(\n    samples,\n    [/node(js)?_version.*info/i, /n8n.*node.*version.*info/i, /.*node.*version.*/i],\n    ['version', 'node_version', 'runtime_version']\n  ),\n\n  process_start_time_seconds: findNoLabelValue(samples, [/process_start_time_seconds$/i]),\n\n  is_leader: (() => {\n    const v = findNoLabelValue(samples, [/instance_role_leader$/i, /leader$/i]);\n    return v === null ? null : v === 1;\n  })(),\n  active_workflows: findNoLabelValue(samples, [/active_workflow_count$/i, /active_workflows$/i]),\n\n  cpu_total_seconds: findNoLabelValue(samples, [/process_cpu_seconds_total$/i, /cpu_seconds_total$/i]),\n  memory_rss_bytes: findNoLabelValue(samples, [/resident_memory_bytes$/i, /memory_rss_bytes$/i, /process_resident_memory_bytes$/i]),\n  heap_used_bytes: findNoLabelValue(samples, [/heap_size_used_bytes$/i, /heap_used_bytes$/i]),\n  external_memory_bytes: findNoLabelValue(samples, [/external_memory_bytes$/i]),\n  eventloop_lag_p99_s: findNoLabelValue(samples, [/eventloop_lag_p99_seconds$/i, /event_loop_lag_p99.*seconds$/i]),\n  open_fds: findNoLabelValue(samples, [/process_open_fds$/i, /open_fds$/i]),\n};\n\nreturn [{\n  json: {\n    INSTANCE_ID,\n    scrape_ts: SCRAPE_TS,\n    snapshotParams,\n    seriesRows,\n    sampleRows,\n    counts: {\n      parsed_samples: samples.length,\n      unique_series: seriesRows.length,\n      unique_samples: sampleRows.length,\n    },\n    config: {\n      USE_HIGH_RES_TS,\n      ROUND_SECONDS: USE_HIGH_RES_TS ? null : ROUND_SECONDS,\n      RESPECT_LINE_TIMESTAMPS,\n    }\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1040,
        928
      ],
      "id": "ec9a9b7d-ba21-4fb1-8cfd-ce2e20e9ba31",
      "name": "Parse Metrics \u2192 Build Models"
    }
  ],
  "connections": {
    "Manual Trigger \u2014 Collect Metrics": {
      "main": [
        [
          {
            "node": "Set Instance Context",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Run every 5 Minutes": {
      "main": [
        [
          {
            "node": "Set Instance Context",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Metrics \u2192 Build Models": {
      "main": [
        [
          {
            "node": "Insert Metrics Snapshot",
            "type": "main",
            "index": 0
          },
          {
            "node": "Upsert Metrics Series + Samples",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Metrics-Snapshot. Uses httpRequest, postgres. Event-driven trigger; 13 nodes.

Source: https://github.com/Mohammedaljer/n8nTrace/blob/main/Workflows/metrics-snapshot.json — original creator credit. Request a take-down →

More Data & Sheets workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Data & Sheets

This workflow acts as a junior finance research analyst for a UK boutique M&A or corporate finance team. It listens for Slack messages, classifies the request, gathers company or market data, and prod

HTTP Request, Google Drive, Google Docs +5
Data & Sheets

Agendamiento_v2. Uses n8n-nodes-evolution-api, redis, httpRequest, executeWorkflowTrigger. Event-driven trigger; 59 nodes.

N8N Nodes Evolution Api, Redis, HTTP Request +3
Data & Sheets

Cancelacion_v2. Uses executeWorkflowTrigger, redis, httpRequest, n8n-nodes-evolution-api. Event-driven trigger; 46 nodes.

Execute Workflow Trigger, Redis, HTTP Request +3
Data & Sheets

Tarım Haberleri (AI + HTTP). Uses rssFeedRead, httpRequest, postgres. Event-driven trigger; 26 nodes.

RSS Feed Read, HTTP Request, Postgres
Data & Sheets

dummy_client - Shopify abandoned carts. Uses httpRequest, shopifyTrigger, whatsApp, supabase. Event-driven trigger; 25 nodes.

HTTP Request, Shopify Trigger, WhatsApp +2