@@ -3,6 +3,7 @@ package main
3
3
import (
4
4
"fmt"
5
5
"strconv"
6
+ "strings"
6
7
"time"
7
8
8
9
"github.com/go-kit/log"
@@ -50,7 +51,7 @@ func (q *Query) Run(conn *connection) error {
50
51
failedScrapes .WithLabelValues (conn .driver , conn .host , conn .database , conn .user , q .jobName , q .Name ).Set (1.0 )
51
52
continue
52
53
}
53
- m , err := q .updateMetrics (conn , res )
54
+ m , err := q .updateMetrics (conn , res , "" , "" )
54
55
if err != nil {
55
56
level .Error (q .log ).Log ("msg" , "Failed to update metrics" , "err" , err , "host" , conn .host , "db" , conn .database )
56
57
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 {
77
78
return nil
78
79
}
79
80
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
+
80
163
// 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 ) {
82
165
// if no value were defined to be parsed, return immediately
83
166
if len (q .Values ) == 0 {
84
167
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
87
170
updated := 0
88
171
metrics := make ([]prometheus.Metric , 0 , len (q .Values ))
89
172
for _ , valueName := range q .Values {
90
- m , err := q .updateMetric (conn , res , valueName )
173
+ m , err := q .updateMetric (conn , res , valueName , iv , il )
91
174
if err != nil {
92
175
level .Error (q .log ).Log (
93
176
"msg" , "Failed to update metric" ,
@@ -108,7 +191,7 @@ func (q *Query) updateMetrics(conn *connection, res map[string]interface{}) ([]p
108
191
}
109
192
110
193
// 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 ) {
112
195
var value float64
113
196
if i , ok := res [valueName ]; ok {
114
197
switch f := i .(type ) {
@@ -154,6 +237,12 @@ func (q *Query) updateMetric(conn *connection, res map[string]interface{}, value
154
237
// added below
155
238
labels := make ([]string , 0 , len (q .Labels )+ 5 )
156
239
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
+
157
246
// we need to fill every spot in the slice or the key->value mapping
158
247
// won't match up in the end.
159
248
//
0 commit comments