Skip to content

PostgreSQL OpenTelemetry Receiver Dashboards

Dashboards for monitoring PostgreSQL databases using OpenTelemetry's PostgreSQL receiver.

Overview

This dashboard provides visibility into PostgreSQL database performance, connections, transactions, and I/O metrics.

Dashboards

Dashboard File Description
Overview 02-overview-esql.yaml ES|QL-based overview with KPIs, time series charts, and database metrics

Dashboard Definitions

Overview (02-overview-esql.yaml)
---
# PostgreSQL OpenTelemetry Overview Dashboard (ES|QL Version)
# Comprehensive PostgreSQL metrics dashboard using OpenTelemetry PostgreSQL receiver
# Uses ES|QL with TS command for time-series optimization
#
# Requirements:
#   - data_stream.dataset == "postgresqlreceiver.otel"
#   - Enable postgresql.connection.max metric for the Max Connections KPI panel
#   - Enable postgresql.database.count metric for the Database Count KPI panel
#
# Metrics reference: https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/postgresqlreceiver
dashboards:
  - id: postgresql-otel-overview-esql
    name: '[Metrics PostgreSQL] Overview'
    description: Comprehensive PostgreSQL metrics dashboard using OpenTelemetry PostgreSQL receiver
    controls:
      - type: options
        label: Database Name
        data_view: metrics-*
        field: resource.attributes.postgresql.database.name
      - type: options
        label: Instance
        data_view: metrics-*
        field: resource.attributes.service.instance.id
    filters:
      - field: data_stream.dataset
        equals: postgresqlreceiver.otel
      - exists: resource.attributes.postgresql.database.name
    panels:
      # KPI Metrics Row (y=0, h=6)
      - title: Total Databases
        hide_title: true
        size: {w: 12, h: 4}
        esql:
          type: metric
          query:
            - FROM metrics-*
            - WHERE data_stream.dataset == "postgresqlreceiver.otel"
            - WHERE resource.attributes.postgresql.database.name IS NOT NULL
            - STATS total_databases = COUNT_DISTINCT(resource.attributes.postgresql.database.name)
          primary:
            field: total_databases
            label: Databases
            format:
              type: number
              decimals: 0
      - title: Active Connections
        hide_title: true
        size: {w: 12, h: 4}
        esql:
          type: metric
          query:
            - TS metrics-*
            - WHERE data_stream.dataset == "postgresqlreceiver.otel"
            - WHERE postgresql.backends IS NOT NULL
            - STATS active_backends = SUM(LAST_OVER_TIME(postgresql.backends))
          primary:
            field: active_backends
            label: Active Backends
            format:
              type: number
              decimals: 0
      - title: Max Connections
        hide_title: true
        size: {w: 12, h: 4}
        description: Requires postgresql.connection.max metric (enable in OTel collector)
        esql:
          type: metric
          query:
            - TS metrics-*
            - WHERE data_stream.dataset == "postgresqlreceiver.otel"
            - WHERE postgresql.connection.max IS NOT NULL
            - STATS max_connections = MAX(LAST_OVER_TIME(postgresql.connection.max))
          primary:
            field: max_connections
            label: Max Connections
            format:
              type: number
              decimals: 0
      - title: Database Count
        hide_title: true
        size: {w: 12, h: 4}
        description: Requires postgresql.database.count metric (enable in OTel collector)
        esql:
          type: metric
          query:
            - TS metrics-*
            - WHERE data_stream.dataset == "postgresqlreceiver.otel"
            - WHERE postgresql.database.count IS NOT NULL
            - STATS db_count = MAX(LAST_OVER_TIME(TO_LONG(postgresql.database.count)))
          primary:
            field: db_count
            label: Total DB Count
            format:
              type: number
              decimals: 0

      # Performance Summary DataTable (y=6)
      - title: Database Performance Summary
        size: {w: 48, h: 18}
        description: Peak metrics per database over the selected time range. Counter metrics show maximum cumulative values.
        esql:
          type: datatable
          query:
            - FROM metrics-*
            - WHERE data_stream.dataset == "postgresqlreceiver.otel"
            - WHERE resource.attributes.postgresql.database.name IS NOT NULL
            - STATS backends_count = ROUND(MAX(postgresql.backends), 0), size_bytes = MAX(postgresql.db_size), commit_total = MAX(TO_LONG(postgresql.commits)),
              rollback_total = MAX(TO_LONG(postgresql.rollbacks)), blocks_read_total = MAX(TO_LONG(postgresql.blocks_read)) BY database = resource.attributes.postgresql.database.name
            - KEEP database, backends_count, size_bytes, commit_total, rollback_total, blocks_read_total
            - SORT database ASC
            - LIMIT 100
          breakdowns:
            - field: database
              label: Database
          metrics:
            - field: backends_count
              label: Active Backends
              format:
                type: number
                decimals: 0
            - field: size_bytes
              label: Size (Bytes)
            - field: commit_total
              label: Total Commits
              format:
                type: number
                decimals: 0
            - field: rollback_total
              label: Total Rollbacks
              format:
                type: number
                decimals: 0
            - field: blocks_read_total
              label: Blocks Read
              format:
                type: number
                decimals: 0

      # Connections Over Time (y=24)
      - title: Active Connections Over Time
        size: {w: 48, h: 10}
        esql:
          type: line
          query:
            - TS metrics-*
            - WHERE data_stream.dataset == "postgresqlreceiver.otel"
            - WHERE postgresql.backends IS NOT NULL
            - STATS connections = MAX(AVG_OVER_TIME(postgresql.backends)) BY time_bucket = BUCKET(@timestamp, 20, ?_tstart, ?_tend), database
              = resource.attributes.postgresql.database.name
            - SORT time_bucket ASC
          legend:
            visible: show
            position: right
          dimension:
            field: time_bucket
            label: Time
            data_type: date
          metrics:
            - field: connections
              label: Active Connections
              format:
                type: number
          breakdown:
            field: database

      # Transaction Rates (y=34)
      - title: Commits Rate
        size: {w: 24, h: 10}
        esql:
          type: area
          mode: stacked
          query:
            - TS metrics-*
            - WHERE data_stream.dataset == "postgresqlreceiver.otel"
            - WHERE postgresql.commits IS NOT NULL
            - STATS commits_rate = SUM(RATE(postgresql.commits)) BY time_bucket = BUCKET(@timestamp, 20, ?_tstart, ?_tend), database = resource.attributes.postgresql.database.name
            - SORT time_bucket ASC
          legend:
            visible: show
            position: right
          dimension:
            field: time_bucket
            label: Time
            data_type: date
          metrics:
            - field: commits_rate
              label: Commits/sec
              format:
                type: number
                decimals: 2
          breakdown:
            field: database
      - title: Rollbacks Rate
        size: {w: 24, h: 10}
        esql:
          type: area
          mode: stacked
          query:
            - TS metrics-*
            - WHERE data_stream.dataset == "postgresqlreceiver.otel"
            - WHERE postgresql.rollbacks IS NOT NULL
            - STATS rollbacks_rate = SUM(RATE(postgresql.rollbacks)) BY time_bucket = BUCKET(@timestamp, 20, ?_tstart, ?_tend), database = resource.attributes.postgresql.database.name
            - SORT time_bucket ASC
          legend:
            visible: show
            position: right
          dimension:
            field: time_bucket
            label: Time
            data_type: date
          metrics:
            - field: rollbacks_rate
              label: Rollbacks/sec
              format:
                type: number
                decimals: 2
          breakdown:
            field: database

      # Block I/O (y=44)
      - title: Block I/O by Source
        size: {w: 48, h: 12}
        esql:
          type: line
          query:
            - TS metrics-*
            - WHERE data_stream.dataset == "postgresqlreceiver.otel"
            - WHERE postgresql.blocks_read IS NOT NULL
            - STATS blocks_rate = SUM(RATE(postgresql.blocks_read)) BY time_bucket = BUCKET(@timestamp, 20, ?_tstart, ?_tend), source = attributes.source
            - SORT time_bucket ASC
          legend:
            visible: show
            position: right
          dimension:
            field: time_bucket
            label: Time
            data_type: date
          metrics:
            - field: blocks_rate
              label: Blocks Read/sec
          breakdown:
            field: source

      # Database Operations (y=56)
      - title: Operations by Type
        size: {w: 48, h: 12}
        esql:
          type: bar
          mode: stacked
          query:
            - TS metrics-*
            - WHERE data_stream.dataset == "postgresqlreceiver.otel"
            - WHERE postgresql.operations IS NOT NULL
            - STATS operations_rate = SUM(RATE(postgresql.operations)) BY time_bucket = BUCKET(@timestamp, 20, ?_tstart, ?_tend), operation =
              attributes.operation
            - SORT time_bucket ASC
          legend:
            visible: show
            position: right
          dimension:
            field: time_bucket
            label: Time
            data_type: date
          metrics:
            - field: operations_rate
              label: Operations/sec
          breakdown:
            field: operation

      # Distribution Charts (y=68)
      - title: Database Sizes
        size: {w: 24, h: 10}
        esql:
          type: pie
          query:
            - FROM metrics-*
            - WHERE data_stream.dataset == "postgresqlreceiver.otel"
            - WHERE postgresql.db_size IS NOT NULL
            - STATS db_size = MAX(postgresql.db_size) BY database = resource.attributes.postgresql.database.name
            - SORT db_size DESC
            - LIMIT 20
          metrics:
            - field: db_size
              label: Size
              format:
                type: bytes
                decimals: 2
          breakdowns:
            - field: database
              label: Database
          appearance:
            donut: medium
      - title: Connection States
        size: {w: 24, h: 10}
        esql:
          type: pie
          query:
            - FROM metrics-*
            - WHERE data_stream.dataset == "postgresqlreceiver.otel"
            - WHERE postgresql.backends IS NOT NULL
            - WHERE attributes.state IS NOT NULL
            - STATS connections = MAX(postgresql.backends) BY state = attributes.state
            - SORT connections DESC
            - LIMIT 10
          metrics:
            - field: connections
              label: Connections
              format:
                type: number
                decimals: 0
          breakdowns:
            - field: state
              label: State
          appearance:
            donut: medium

      # Metadata Table (y=78)
      - title: Database Metadata
        size: {w: 48, h: 12}
        description: List of monitored databases and their host locations
        esql:
          type: datatable
          query:
            - FROM metrics-*
            - WHERE data_stream.dataset == "postgresqlreceiver.otel"
            - WHERE resource.attributes.postgresql.database.name IS NOT NULL
            - STATS BY database = resource.attributes.postgresql.database.name, instance = resource.attributes.service.instance.id
            - KEEP database, instance
            - SORT database ASC
            - LIMIT 100
          breakdowns:
            - field: database
              label: Database
            - field: instance
              label: Instance

Prerequisites

  • PostgreSQL: PostgreSQL database server
  • OpenTelemetry Collector: Collector with PostgreSQL receiver configured
  • Kibana: Version 9.2 or later (dashboards use ES|QL TS command)

Data Requirements

  • Data stream dataset: postgresqlreceiver.otel
  • Data view: metrics-*

OpenTelemetry Collector Configuration

receivers:
  postgresql:
    endpoint: localhost:5432
    transport: tcp
    username: ${env:POSTGRES_USER}
    password: ${env:POSTGRES_PASSWORD}
    databases:
      - postgres
      - myapp_db
    collection_interval: 60s
    metrics:
      postgresql.backends:
        enabled: true
      postgresql.connection.max:
        enabled: true
      postgresql.database.count:
        enabled: true
      postgresql.commits:
        enabled: true
      postgresql.rollbacks:
        enabled: true
      postgresql.blocks_read:
        enabled: true
      postgresql.db_size:
        enabled: true
      postgresql.operations:
        enabled: true

Metrics Reference

Default Metrics

Metric Type Unit Description Attributes
postgresql.backends Sum 1 Number of backends
postgresql.bgwriter.buffers.allocated Sum {buffers} Number of buffers allocated
postgresql.bgwriter.buffers.writes Sum {buffers} Number of buffers written source
postgresql.bgwriter.checkpoint.count Sum {checkpoints} Number of checkpoints performed type
postgresql.bgwriter.duration Sum ms Time spent writing/syncing during checkpoints type
postgresql.bgwriter.maxwritten Sum 1 Times background writer stopped
postgresql.blocks_read Sum 1 Number of blocks read source
postgresql.commits Sum 1 Number of commits
postgresql.connection.max Gauge {connections} Maximum client connections allowed
postgresql.database.count Sum {databases} Number of user databases
postgresql.db_size Sum By Database disk usage
postgresql.index.scans Sum {scans} Number of index scans on a table
postgresql.index.size Gauge By Size of the index on disk
postgresql.operations Sum 1 Number of db row operations operation
postgresql.replication.data_delay Gauge By Amount of data delayed in replication replication_client
postgresql.rollbacks Sum 1 Number of rollbacks
postgresql.rows Sum 1 Number of rows in the database state
postgresql.table.count Sum {table} Number of user tables in a database
postgresql.table.size Sum By Disk space used by a table
postgresql.table.vacuum.count Sum {vacuum} Number of times a table has been vacuumed
postgresql.wal.age Gauge s Age of oldest WAL file
postgresql.wal.lag Gauge s WAL replication lag time operation, replication_client

Optional Metrics (disabled by default)

Metric Type Unit Description Attributes
postgresql.blks_hit Sum {blks_hit} Cache buffer hits
postgresql.blks_read Sum {blks_read} Disk blocks read
postgresql.database.locks Gauge {lock} Number of database locks relation, mode, lock_type
postgresql.deadlocks Sum {deadlock} Number of deadlocks
postgresql.sequential_scans Sum {sequential_scan} Sequential scan count
postgresql.temp.io Sum By Data written to temporary files
postgresql.temp_files Sum {temp_file} Number of temp files

Metric Attributes

Attribute Values Description
source (bgwriter) backend, backend_fsync, checkpoints, bgwriter Buffer write source
source (blocks_read) heap_read, heap_hit, idx_read, idx_hit, toast_read, toast_hit, tidx_read, tidx_hit Block I/O source
type (checkpoint) requested, scheduled Checkpoint type
type (duration) sync, write Duration type
operation ins, upd, del, hot_upd Row operation type
state dead, live Row state
operation (wal.lag) flush, replay, write WAL operation

Resource Attributes

Attribute Description
postgresql.database.name Database name
postgresql.index.name Index name
postgresql.schema.name Schema name
postgresql.table.name Table name
service.instance.id Service instance identifier

Metrics Not Used in Dashboards

The following metrics are available from the PostgreSQL receiver but are not currently visualized in the dashboards:

Default Metrics Not Used

Metric Type Unit Description Attributes
postgresql.bgwriter.buffers.allocated Sum {buffers} Number of buffers allocated
postgresql.bgwriter.buffers.writes Sum {buffers} Number of buffers written source
postgresql.bgwriter.checkpoint.count Sum {checkpoints} Number of checkpoints performed type
postgresql.bgwriter.duration Sum ms Time spent writing/syncing during checkpoints type
postgresql.bgwriter.maxwritten Sum 1 Times background writer stopped
postgresql.index.scans Sum {scans} Number of index scans on a table
postgresql.index.size Gauge By Size of the index on disk
postgresql.replication.data_delay Gauge By Amount of data delayed in replication replication_client
postgresql.rows Sum 1 Number of rows in the database state
postgresql.table.count Sum {table} Number of user tables in a database
postgresql.table.size Sum By Disk space used by a table
postgresql.table.vacuum.count Sum {vacuum} Number of times a table has been vacuumed
postgresql.wal.age Gauge s Age of oldest WAL file
postgresql.wal.lag Gauge s WAL replication lag time operation, replication_client

Optional Metrics Not Used

Metric Type Unit Description Attributes
postgresql.blks_hit Sum {blks_hit} Cache buffer hits
postgresql.blks_read Sum {blks_read} Disk blocks read
postgresql.database.locks Gauge {lock} Number of database locks relation, mode, lock_type
postgresql.deadlocks Sum {deadlock} Number of deadlocks
postgresql.sequential_scans Sum {sequential_scan} Sequential scan count
postgresql.temp.io Sum By Data written to temporary files
postgresql.temp_files Sum {temp_file} Number of temp files