@@ -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
3138 registerCollector (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 (
7195 prometheus.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+
74109 pgStatStatementsQuery = `SELECT
75110 pg_get_userbyid(userid) as user,
76111 pg_database.datname,
77112 pg_stat_statements.queryid,
113+ %s
78114 pg_stat_statements.calls as calls_total,
79115 pg_stat_statements.total_time / 1000.0 as seconds_total,
80116 pg_stat_statements.rows as rows_total,
96132 pg_get_userbyid(userid) as user,
97133 pg_database.datname,
98134 pg_stat_statements.queryid,
135+ %s
99136 pg_stat_statements.calls as calls_total,
100137 pg_stat_statements.total_exec_time / 1000.0 as seconds_total,
101138 pg_stat_statements.rows as rows_total,
@@ -117,6 +154,7 @@ var (
117154 pg_get_userbyid(userid) as user,
118155 pg_database.datname,
119156 pg_stat_statements.queryid,
157+ %s
120158 pg_stat_statements.calls as calls_total,
121159 pg_stat_statements.total_exec_time / 1000.0 as seconds_total,
122160 pg_stat_statements.rows as rows_total,
@@ -135,30 +173,42 @@ var (
135173 LIMIT 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
140178 switch {
141179 case instance .version .GE (semver .MustParse ("17.0.0" )):
142- query = pgStatStatementsQuery_PG17
180+ queryTemplate = pgStatStatementsQuery_PG17
143181 case instance .version .GE (semver .MustParse ("13.0.0" )):
144- query = pgStatStatementsNewQuery
182+ queryTemplate = pgStatStatementsNewQuery
145183 default :
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
149192 db := instance .getDB ()
150193 rows , err := db .QueryContext (ctx , query )
151194
195+ var presentQueryIds = make (map [string ]struct {})
196+
152197 if err != nil {
153198 return err
154199 }
155200 defer rows .Close ()
156201 for rows .Next () {
157- var user , datname , queryid sql.NullString
202+ var user , datname , queryid , statement sql.NullString
158203 var callsTotal , rowsTotal sql.NullInt64
159204 var 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 {
162212 return err
163213 }
164214
@@ -229,6 +279,25 @@ func (PGStatStatementsCollector) Update(ctx context.Context, instance *instance,
229279 blockWriteSecondsTotalMetric ,
230280 userLabel , 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 }
233302 if err := rows .Err (); err != nil {
234303 return err
0 commit comments