@@ -3,6 +3,7 @@ package main
33import (
44 "fmt"
55 "strconv"
6+ "strings"
67 "time"
78
89 "github.com/go-kit/log"
@@ -50,7 +51,7 @@ func (q *Query) Run(conn *connection) error {
5051 failedScrapes .WithLabelValues (conn .driver , conn .host , conn .database , conn .user , q .jobName , q .Name ).Set (1.0 )
5152 continue
5253 }
53- m , err := q .updateMetrics (conn , res )
54+ m , err := q .updateMetrics (conn , res , "" , "" )
5455 if err != nil {
5556 level .Error (q .log ).Log ("msg" , "Failed to update metrics" , "err" , err , "host" , conn .host , "db" , conn .database )
5657 failedScrapes .WithLabelValues (conn .driver , conn .host , conn .database , conn .user , q .jobName , q .Name ).Set (1.0 )
@@ -77,8 +78,90 @@ func (q *Query) Run(conn *connection) error {
7778 return nil
7879}
7980
81+ // RunIterator runs the query for each iterator value on a single connection
82+ func (q * Query ) RunIterator (conn * connection , ph string , ivs []string , il string ) error {
83+ if q .log == nil {
84+ q .log = log .NewNopLogger ()
85+ }
86+ queryCounter .WithLabelValues (q .jobName , q .Name ).Inc ()
87+ if q .desc == nil {
88+ failedQueryCounter .WithLabelValues (q .jobName , q .Name ).Inc ()
89+ return fmt .Errorf ("metrics descriptor is nil" )
90+ }
91+ if q .Query == "" {
92+ failedQueryCounter .WithLabelValues (q .jobName , q .Name ).Inc ()
93+ return fmt .Errorf ("query is empty" )
94+ }
95+ if conn == nil || conn .conn == nil {
96+ failedQueryCounter .WithLabelValues (q .jobName , q .Name ).Inc ()
97+ return fmt .Errorf ("db connection not initialized (should not happen)" )
98+ }
99+
100+ // execute query for each iterator value
101+ now := time .Now ()
102+ metrics := make ([]prometheus.Metric , 0 , len (q .metrics ))
103+ updated := 0
104+ for _ , iv := range ivs {
105+ rows , err := conn .conn .Queryx (q .ReplaceIterator (ph , iv ))
106+ if err != nil {
107+ failedScrapes .WithLabelValues (conn .driver , conn .host , conn .database , conn .user , q .jobName , q .Name ).Set (1.0 )
108+ failedQueryCounter .WithLabelValues (q .jobName , q .Name ).Inc ()
109+ return err
110+ }
111+ defer rows .Close ()
112+
113+ for rows .Next () {
114+ res := make (map [string ]interface {})
115+ err := rows .MapScan (res )
116+ if err != nil {
117+ level .Error (q .log ).Log ("msg" , "Failed to scan" , "err" , err , "host" , conn .host , "db" , conn .database )
118+ failedScrapes .WithLabelValues (conn .driver , conn .host , conn .database , conn .user , q .jobName , q .Name ).Set (1.0 )
119+ continue
120+ }
121+ m , err := q .updateMetrics (conn , res , iv , il )
122+ if err != nil {
123+ level .Error (q .log ).Log ("msg" , "Failed to update metrics" , "err" , err , "host" , conn .host , "db" , conn .database )
124+ failedScrapes .WithLabelValues (conn .driver , conn .host , conn .database , conn .user , q .jobName , q .Name ).Set (1.0 )
125+ continue
126+ }
127+ metrics = append (metrics , m ... )
128+ updated ++
129+ failedScrapes .WithLabelValues (conn .driver , conn .host , conn .database , conn .user , q .jobName , q .Name ).Set (0.0 )
130+ }
131+ }
132+
133+ duration := time .Since (now )
134+ queryDurationHistogram .WithLabelValues (q .jobName , q .Name ).Observe (duration .Seconds ())
135+
136+ if updated < 1 {
137+ if q .AllowZeroRows {
138+ failedScrapes .WithLabelValues (conn .driver , conn .host , conn .database , conn .user , q .jobName , q .Name ).Set (0.0 )
139+ } else {
140+ return fmt .Errorf ("zero rows returned" )
141+ }
142+ }
143+
144+ // update the metrics cache
145+ q .Lock ()
146+ q .metrics [conn ] = metrics
147+ q .Unlock ()
148+
149+ return nil
150+ }
151+
152+ // HasIterator returns true if the query contains the given placeholder
153+ func (q * Query ) HasIterator (ph string ) bool {
154+ return strings .Contains (q .Query , ph )
155+ }
156+
157+ // ReplaceIterator replaces a given placeholder with an iterator value and returns a new query
158+ func (q * Query ) ReplaceIterator (ph string , iv string ) string {
159+ iteratorReplacer := strings .NewReplacer (fmt .Sprint ("{{" , ph , "}}" ), iv )
160+ return iteratorReplacer .Replace (q .Query )
161+ }
162+
80163// updateMetrics parses the result set and returns a slice of const metrics
81- func (q * Query ) updateMetrics (conn * connection , res map [string ]interface {}) ([]prometheus.Metric , error ) {
164+ func (q * Query ) updateMetrics (conn * connection , res map [string ]interface {}, iv string , il string ) ([]prometheus.Metric , error ) {
82165 // if no value were defined to be parsed, return immediately
83166 if len (q .Values ) == 0 {
84167 level .Debug (q .log ).Log ("msg" , "No values defined in configuration, skipping metric update" )
@@ -87,7 +170,7 @@ func (q *Query) updateMetrics(conn *connection, res map[string]interface{}) ([]p
87170 updated := 0
88171 metrics := make ([]prometheus.Metric , 0 , len (q .Values ))
89172 for _ , valueName := range q .Values {
90- m , err := q .updateMetric (conn , res , valueName )
173+ m , err := q .updateMetric (conn , res , valueName , iv , il )
91174 if err != nil {
92175 level .Error (q .log ).Log (
93176 "msg" , "Failed to update metric" ,
@@ -108,7 +191,7 @@ func (q *Query) updateMetrics(conn *connection, res map[string]interface{}) ([]p
108191}
109192
110193// updateMetrics parses a single row and returns a const metric
111- func (q * Query ) updateMetric (conn * connection , res map [string ]interface {}, valueName string ) (prometheus.Metric , error ) {
194+ func (q * Query ) updateMetric (conn * connection , res map [string ]interface {}, valueName string , iv string , il string ) (prometheus.Metric , error ) {
112195 var value float64
113196 if i , ok := res [valueName ]; ok {
114197 switch f := i .(type ) {
@@ -154,6 +237,12 @@ func (q *Query) updateMetric(conn *connection, res map[string]interface{}, value
154237 // added below
155238 labels := make ([]string , 0 , len (q .Labels )+ 5 )
156239 for _ , label := range q .Labels {
240+ // append iterator value to the labels
241+ if label == il && iv != "" {
242+ labels = append (labels , iv )
243+ continue
244+ }
245+
157246 // we need to fill every spot in the slice or the key->value mapping
158247 // won't match up in the end.
159248 //
0 commit comments