Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ jobs:
strategy:
matrix:
postgresql-image:
- postgres:10
- postgres:11
- postgres:12
- postgres:13
- postgres:14
- postgres:15
- postgres:16
- postgres:17
- postgres:18
runs-on: ubuntu-latest
steps:
- name: Checkout code
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
## [Unreleased]

* [ENHANCEMENT] Add PostgreSQL 18 support:
* Add parallel worker activity metrics
* Add vacuum/analyze timing metrics
* Add enhanced checkpointer metrics
* Add `pg_stat_io` collector with byte statistics and WAL I/O activity tracking
* [ENHANCEMENT] Update CI tested PostgreSQL versions to include PostgreSQL 18

## 0.15.0 / 2023-10-27

* [ENHANCEMENT] Add 1kB and 2kB units #915
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

Prometheus exporter for PostgreSQL server metrics.

CI Tested PostgreSQL versions: `11`, `12`, `13`, `14`, `15`, `16`
CI Tested PostgreSQL versions: `13`, `14`, `15`, `16`, `18`

## Quick Start
This package is available for Docker:
Expand Down
106 changes: 100 additions & 6 deletions collector/pg_stat_bgwriter.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package collector
import (
"context"
"database/sql"

"github.com/blang/semver/v4"
"github.com/prometheus/client_golang/prometheus"
)
Expand Down Expand Up @@ -46,6 +47,12 @@ var (
[]string{"collector", "server"},
prometheus.Labels{},
)
statBGWriterCheckpointsDoneDesc = prometheus.NewDesc(
prometheus.BuildFQName(namespace, bgWriterSubsystem, "checkpoints_done_total"),
"Number of completed checkpoints",
[]string{"collector", "server"},
prometheus.Labels{},
)
statBGWriterCheckpointsReqTimeDesc = prometheus.NewDesc(
prometheus.BuildFQName(namespace, bgWriterSubsystem, "checkpoint_write_time_total"),
"Total amount of time that has been spent in the portion of checkpoint processing where files are written to disk, in milliseconds",
Expand Down Expand Up @@ -94,6 +101,12 @@ var (
[]string{"collector", "server"},
prometheus.Labels{},
)
statBGWriterCheckpointsSlruWrittenDesc = prometheus.NewDesc(
prometheus.BuildFQName(namespace, bgWriterSubsystem, "slru_written_total"),
"Number of SLRU buffers written during checkpoints and restartpoints",
[]string{"collector", "server"},
prometheus.Labels{},
)
statBGWriterStatsResetDesc = prometheus.NewDesc(
prometheus.BuildFQName(namespace, bgWriterSubsystem, "stats_reset_total"),
"Time at which these statistics were last reset",
Expand All @@ -114,6 +127,12 @@ var statBGWriter = map[string]*prometheus.Desc{
[]string{"collector", "server"},
prometheus.Labels{},
),
"percona_checkpoints_done": prometheus.NewDesc(
prometheus.BuildFQName(namespace, bgWriterSubsystem, "checkpoints_done"),
"Number of completed checkpoints",
[]string{"collector", "server"},
prometheus.Labels{},
),
"percona_checkpoint_write_time": prometheus.NewDesc(
prometheus.BuildFQName(namespace, bgWriterSubsystem, "checkpoint_write_time"),
"Total amount of time that has been spent in the portion of checkpoint processing where files are written to disk, in milliseconds",
Expand Down Expand Up @@ -162,6 +181,12 @@ var statBGWriter = map[string]*prometheus.Desc{
[]string{"collector", "server"},
prometheus.Labels{},
),
"percona_slru_written": prometheus.NewDesc(
prometheus.BuildFQName(namespace, bgWriterSubsystem, "slru_written"),
"Number of SLRU buffers written during checkpoints and restartpoints",
[]string{"collector", "server"},
prometheus.Labels{},
),
"percona_stats_reset": prometheus.NewDesc(
prometheus.BuildFQName(namespace, bgWriterSubsystem, "stats_reset"),
"Time at which these statistics were last reset",
Expand Down Expand Up @@ -191,25 +216,43 @@ const statBGWriterQueryPost17 = `SELECT
,stats_reset
FROM pg_stat_bgwriter;`

const statCheckpointerQuery = `SELECT
const statCheckpointerQueryPre18 = `SELECT
num_timed
,num_requested
,NULL::bigint as num_done
,restartpoints_timed
,restartpoints_req
,restartpoints_done
,write_time
,sync_time
,buffers_written
,NULL::bigint as slru_written
,stats_reset
FROM pg_stat_checkpointer;`

const statCheckpointerQuery18Plus = `SELECT
num_timed
,num_requested
,num_done
,restartpoints_timed
,restartpoints_req
,restartpoints_done
,write_time
,sync_time
,buffers_written
,slru_written
,stats_reset
FROM pg_stat_checkpointer;`

func (p PGStatBGWriterCollector) Update(ctx context.Context, instance *instance, ch chan<- prometheus.Metric) error {
db := instance.getDB()

var cpt, cpr, bcp, bc, mwc, bb, bbf, ba sql.NullInt64
var cpt, cpr, cpd, bcp, bc, mwc, bb, bbf, ba, slruw sql.NullInt64
var cpwt, cpst sql.NullFloat64
var sr sql.NullTime

after18 := instance.version.GTE(semver.Version{Major: 18})

if instance.version.GE(semver.MustParse("17.0.0")) {
row := db.QueryRowContext(ctx,
statBGWriterQueryPost17)
Expand All @@ -219,10 +262,15 @@ func (p PGStatBGWriterCollector) Update(ctx context.Context, instance *instance,
}
var rpt, rpr, rpd sql.NullInt64
var csr sql.NullTime
// these variables are not used, but I left them here for reference
row = db.QueryRowContext(ctx,
statCheckpointerQuery)
err = row.Scan(&cpt, &cpr, &rpt, &rpr, &rpd, &cpwt, &cpst, &bcp, &csr)

// Use version-specific checkpointer query for PostgreSQL 18+
checkpointerQuery := statCheckpointerQueryPre18
if after18 {
checkpointerQuery = statCheckpointerQuery18Plus
}

row = db.QueryRowContext(ctx, checkpointerQuery)
err = row.Scan(&cpt, &cpr, &cpd, &rpt, &rpr, &rpd, &cpwt, &cpst, &bcp, &slruw, &csr)
if err != nil {
return err
}
Expand Down Expand Up @@ -257,6 +305,21 @@ func (p PGStatBGWriterCollector) Update(ctx context.Context, instance *instance,
"exporter",
instance.name,
)

cpdMetric := 0.0
if after18 {
if cpd.Valid {
cpdMetric = float64(cpd.Int64)
}
ch <- prometheus.MustNewConstMetric(
statBGWriterCheckpointsDoneDesc,
prometheus.CounterValue,
cpdMetric,
"exporter",
instance.name,
)
}

cpwtMetric := 0.0
if cpwt.Valid {
cpwtMetric = float64(cpwt.Float64)
Expand Down Expand Up @@ -345,6 +408,19 @@ func (p PGStatBGWriterCollector) Update(ctx context.Context, instance *instance,
"exporter",
instance.name,
)
slruwMetric := 0.0
if after18 {
if slruw.Valid {
slruwMetric = float64(slruw.Int64)
}
ch <- prometheus.MustNewConstMetric(
statBGWriterCheckpointsSlruWrittenDesc,
prometheus.CounterValue,
slruwMetric,
"exporter",
instance.name,
)
}
srMetric := 0.0
if sr.Valid {
srMetric = float64(sr.Time.Unix())
Expand Down Expand Up @@ -373,6 +449,15 @@ func (p PGStatBGWriterCollector) Update(ctx context.Context, instance *instance,
"exporter",
instance.name,
)
if after18 {
ch <- prometheus.MustNewConstMetric(
statBGWriter["percona_checkpoints_done"],
prometheus.CounterValue,
cpdMetric,
"exporter",
instance.name,
)
}
ch <- prometheus.MustNewConstMetric(
statBGWriter["percona_checkpoint_write_time"],
prometheus.CounterValue,
Expand Down Expand Up @@ -429,6 +514,15 @@ func (p PGStatBGWriterCollector) Update(ctx context.Context, instance *instance,
"exporter",
instance.name,
)
if after18 {
ch <- prometheus.MustNewConstMetric(
statBGWriter["percona_slru_written"],
prometheus.CounterValue,
slruwMetric,
"exporter",
instance.name,
)
}
ch <- prometheus.MustNewConstMetric(
statBGWriter["percona_stats_reset"],
prometheus.CounterValue,
Expand Down
92 changes: 88 additions & 4 deletions collector/pg_stat_database.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"context"
"database/sql"

"github.com/blang/semver/v4"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/prometheus/client_golang/prometheus"
Expand Down Expand Up @@ -206,8 +207,52 @@ var (
[]string{"datid", "datname"},
prometheus.Labels{},
)
statDatabaseParallelWorkersToLaunch = prometheus.NewDesc(prometheus.BuildFQName(
namespace,
statDatabaseSubsystem,
"parallel_workers_to_launch",
),
"Number of parallel workers to launch (PostgreSQL 18+)",
[]string{"datid", "datname"},
prometheus.Labels{},
)
statDatabaseParallelWorkersLaunched = prometheus.NewDesc(prometheus.BuildFQName(
namespace,
statDatabaseSubsystem,
"parallel_workers_launched",
),
"Number of parallel workers launched (PostgreSQL 18+)",
[]string{"datid", "datname"},
prometheus.Labels{},
)

statDatabaseQueryPrePG18 = `
SELECT
datid
,datname
,numbackends
,xact_commit
,xact_rollback
,blks_read
,blks_hit
,tup_returned
,tup_fetched
,tup_inserted
,tup_updated
,tup_deleted
,conflicts
,temp_files
,temp_bytes
,deadlocks
,blk_read_time
,blk_write_time
,stats_reset
,NULL::bigint as parallel_workers_to_launch
,NULL::bigint as parallel_workers_launched
FROM pg_stat_database;
`

statDatabaseQuery = `
statDatabaseQueryPG18 = `
SELECT
datid
,datname
Expand All @@ -228,15 +273,23 @@ var (
,blk_read_time
,blk_write_time
,stats_reset
,parallel_workers_to_launch
,parallel_workers_launched
FROM pg_stat_database;
`
)

func (c *PGStatDatabaseCollector) Update(ctx context.Context, instance *instance, ch chan<- prometheus.Metric) error {
db := instance.getDB()
rows, err := db.QueryContext(ctx,
statDatabaseQuery,
)

after18 := instance.version.GTE(semver.Version{Major: 18})
// Use version-specific query for PostgreSQL 18+
query := statDatabaseQueryPrePG18
if after18 {
query = statDatabaseQueryPG18
}

rows, err := db.QueryContext(ctx, query)
if err != nil {
return err
}
Expand All @@ -246,6 +299,7 @@ func (c *PGStatDatabaseCollector) Update(ctx context.Context, instance *instance
var datid, datname sql.NullString
var numBackends, xactCommit, xactRollback, blksRead, blksHit, tupReturned, tupFetched, tupInserted, tupUpdated, tupDeleted, conflicts, tempFiles, tempBytes, deadlocks, blkReadTime, blkWriteTime sql.NullFloat64
var statsReset sql.NullTime
var parallelWorkersToLaunch, parallelWorkersLaunched sql.NullFloat64

err := rows.Scan(
&datid,
Expand All @@ -267,6 +321,8 @@ func (c *PGStatDatabaseCollector) Update(ctx context.Context, instance *instance
&blkReadTime,
&blkWriteTime,
&statsReset,
&parallelWorkersToLaunch,
&parallelWorkersLaunched,
)
if err != nil {
return err
Expand Down Expand Up @@ -353,6 +409,18 @@ func (c *PGStatDatabaseCollector) Update(ctx context.Context, instance *instance
statsResetMetric = float64(statsReset.Time.Unix())
}

if after18 {
if !parallelWorkersToLaunch.Valid && after18 {
level.Debug(c.log).Log("msg", "Skipping collecting metric because it has no parallel_workers_to_launch")
continue
}

if !parallelWorkersLaunched.Valid {
level.Debug(c.log).Log("msg", "Skipping collecting metric because it has no parallel_workers_launched")
continue
}
}

labels := []string{datid.String, datname.String}

ch <- prometheus.MustNewConstMetric(
Expand Down Expand Up @@ -473,6 +541,22 @@ func (c *PGStatDatabaseCollector) Update(ctx context.Context, instance *instance
statsResetMetric,
labels...,
)

if after18 {
ch <- prometheus.MustNewConstMetric(
statDatabaseParallelWorkersToLaunch,
prometheus.CounterValue,
parallelWorkersToLaunch.Float64,
labels...,
)

ch <- prometheus.MustNewConstMetric(
statDatabaseParallelWorkersLaunched,
prometheus.CounterValue,
parallelWorkersLaunched.Float64,
labels...,
)
}
}
return nil
}
Loading