@@ -16,27 +16,51 @@ package collector
1616import (
1717"context"
1818"database/sql"
19+ "fmt"
1920"log/slog"
2021
22+ "github.com/alecthomas/kingpin/v2"
2123"github.com/blang/semver/v4"
2224"github.com/prometheus/client_golang/prometheus"
2325)
2426
2527const statStatementsSubsystem = "stat_statements"
2628
29+ var (
30+ includeQueryFlag * bool = nil
31+ statementLengthFlag * uint = nil
32+ )
33+
2734func init () {
2835// WARNING:
2936// Disabled by default because this set of metrics can be quite expensive on a busy server
3037// Every unique query will cause a new timeseries to be created
3138registerCollector (statStatementsSubsystem , defaultDisabled , NewPGStatStatementsCollector )
39+
40+ includeQueryFlag = kingpin .Flag (
41+ fmt .Sprint (collectorFlagPrefix , statStatementsSubsystem , ".include_query" ),
42+ "Enable selecting statement query together with queryId. (default: disabled)" ).
43+ Default (fmt .Sprintf ("%v" , defaultDisabled )).
44+ Bool ()
45+ statementLengthFlag = kingpin .Flag (
46+ fmt .Sprint (collectorFlagPrefix , statStatementsSubsystem , ".query_length" ),
47+ "Maximum length of the statement text." ).
48+ Default ("120" ).
49+ Uint ()
3250}
3351
3452type PGStatStatementsCollector struct {
35- log * slog.Logger
53+ log * slog.Logger
54+ includeQueryStatement bool
55+ statementLength uint
3656}
3757
3858func NewPGStatStatementsCollector (config collectorConfig ) (Collector , error ) {
39- return & PGStatStatementsCollector {log : config .logger }, nil
59+ return & PGStatStatementsCollector {
60+ log : config .logger ,
61+ includeQueryStatement : * includeQueryFlag ,
62+ statementLength : * statementLengthFlag ,
63+ }, nil
4064}
4165
4266var (
@@ -71,10 +95,22 @@ var (
7195prometheus.Labels {},
7296)
7397
98+ statStatementsQuery = prometheus .NewDesc (
99+ prometheus .BuildFQName (namespace , statStatementsSubsystem , "query_id" ),
100+ "SQL Query to queryid mapping" ,
101+ []string {"queryid" , "query" },
102+ prometheus.Labels {},
103+ )
104+ )
105+
106+ const (
107+ pgStatStatementQuerySelect = `LEFT(pg_stat_statements.query, %d) as query,`
108+
74109pgStatStatementsQuery = `SELECT
75110pg_get_userbyid(userid) as user,
76111pg_database.datname,
77112pg_stat_statements.queryid,
113+ %s
78114pg_stat_statements.calls as calls_total,
79115pg_stat_statements.total_time / 1000.0 as seconds_total,
80116pg_stat_statements.rows as rows_total,
96132pg_get_userbyid(userid) as user,
97133pg_database.datname,
98134pg_stat_statements.queryid,
135+ %s
99136pg_stat_statements.calls as calls_total,
100137pg_stat_statements.total_exec_time / 1000.0 as seconds_total,
101138pg_stat_statements.rows as rows_total,
@@ -117,6 +154,7 @@ var (
117154pg_get_userbyid(userid) as user,
118155pg_database.datname,
119156pg_stat_statements.queryid,
157+ %s
120158pg_stat_statements.calls as calls_total,
121159pg_stat_statements.total_exec_time / 1000.0 as seconds_total,
122160pg_stat_statements.rows as rows_total,
@@ -135,30 +173,42 @@ var (
135173LIMIT 100;`
136174)
137175
138- func (PGStatStatementsCollector ) Update (ctx context.Context , instance * instance , ch chan <- prometheus.Metric ) error {
139- var query string
176+ func (c PGStatStatementsCollector ) Update (ctx context.Context , instance * instance , ch chan <- prometheus.Metric ) error {
177+ var queryTemplate string
140178switch {
141179case instance .version .GE (semver .MustParse ("17.0.0" )):
142- query = pgStatStatementsQuery_PG17
180+ queryTemplate = pgStatStatementsQuery_PG17
143181case instance .version .GE (semver .MustParse ("13.0.0" )):
144- query = pgStatStatementsNewQuery
182+ queryTemplate = pgStatStatementsNewQuery
145183default :
146- query = pgStatStatementsQuery
184+ queryTemplate = pgStatStatementsQuery
147185}
186+ var querySelect = ""
187+ if c .includeQueryStatement {
188+ querySelect = fmt .Sprintf (pgStatStatementQuerySelect , c .statementLength )
189+ }
190+ query := fmt .Sprintf (queryTemplate , querySelect )
148191
149192db := instance .getDB ()
150193rows , err := db .QueryContext (ctx , query )
151194
195+ var presentQueryIds = make (map [string ]struct {})
196+
152197if err != nil {
153198return err
154199}
155200defer rows .Close ()
156201for rows .Next () {
157- var user , datname , queryid sql.NullString
202+ var user , datname , queryid , statement sql.NullString
158203var callsTotal , rowsTotal sql.NullInt64
159204var secondsTotal , blockReadSecondsTotal , blockWriteSecondsTotal sql.NullFloat64
160-
161- if err := rows .Scan (& user , & datname , & queryid , & callsTotal , & secondsTotal , & rowsTotal , & blockReadSecondsTotal , & blockWriteSecondsTotal ); err != nil {
205+ var columns []any
206+ if c .includeQueryStatement {
207+ columns = []any {& user , & datname , & queryid , & statement , & callsTotal , & secondsTotal , & rowsTotal , & blockReadSecondsTotal , & blockWriteSecondsTotal }
208+ } else {
209+ columns = []any {& user , & datname , & queryid , & callsTotal , & secondsTotal , & rowsTotal , & blockReadSecondsTotal , & blockWriteSecondsTotal }
210+ }
211+ if err := rows .Scan (columns ... ); err != nil {
162212return err
163213}
164214
@@ -229,6 +279,25 @@ func (PGStatStatementsCollector) Update(ctx context.Context, instance *instance,
229279blockWriteSecondsTotalMetric ,
230280userLabel , datnameLabel , queryidLabel ,
231281)
282+
283+ if c .includeQueryStatement {
284+ _ , ok := presentQueryIds [queryidLabel ]
285+ if ! ok {
286+ presentQueryIds [queryidLabel ] = struct {}{}
287+
288+ queryLabel := "unknown"
289+ if statement .Valid {
290+ queryLabel = statement .String
291+ }
292+
293+ ch <- prometheus .MustNewConstMetric (
294+ statStatementsQuery ,
295+ prometheus .CounterValue ,
296+ 1 ,
297+ queryidLabel , queryLabel ,
298+ )
299+ }
300+ }
232301}
233302if err := rows .Err (); err != nil {
234303return err
0 commit comments