Skip to content

Commit fc1a99b

Browse files
authored
Merge pull request #36 from prometheus-community/superq/show_lists
Add support for SHOW LISTS metrics
2 parents 074447c + af8318a commit fc1a99b

File tree

2 files changed

+81
-7
lines changed

2 files changed

+81
-7
lines changed

Diff for: CHANGELOG.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ Counter names have been updated to match Prometheus naming conventions.
66
* `pgbouncer_stats_server_in_transaction_seconds` -> `pgbouncer_stats_server_in_transaction_seconds_total`
77

88
* [CHANGE] Cleanup exporter metrics #33
9-
* [CHANGE] Update counter metric names
9+
* [CHANGE] Update counter metric names #35
10+
* [FEATURE] Add support for SHOW LISTS metrics #36
1011

1112
## 0.3.0 / 2020-05-27
1213

Diff for: collector.go

+79-6
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,42 @@ var (
6969
"maxwait": {GAUGE, "client_maxwait_seconds", 1, "Age of oldest unserved client connection, shown as second"},
7070
},
7171
}
72+
73+
listsMap = map[string]*(prometheus.Desc){
74+
"databases": prometheus.NewDesc(
75+
prometheus.BuildFQName(namespace, "", "databases"),
76+
"Count of databases", nil, nil),
77+
"users": prometheus.NewDesc(
78+
prometheus.BuildFQName(namespace, "", "users"),
79+
"Count of users", nil, nil),
80+
"pools": prometheus.NewDesc(
81+
prometheus.BuildFQName(namespace, "", "pools"),
82+
"Count of pools", nil, nil),
83+
"free_clients": prometheus.NewDesc(
84+
prometheus.BuildFQName(namespace, "", "free_clients"),
85+
"Count of free clients", nil, nil),
86+
"used_clients": prometheus.NewDesc(
87+
prometheus.BuildFQName(namespace, "", "used_clients"),
88+
"Count of used clients", nil, nil),
89+
"login_clients": prometheus.NewDesc(
90+
prometheus.BuildFQName(namespace, "", "login_clients"),
91+
"Count of clients in login state", nil, nil),
92+
"free_servers": prometheus.NewDesc(
93+
prometheus.BuildFQName(namespace, "", "free_servers"),
94+
"Count of free servers", nil, nil),
95+
"used_servers": prometheus.NewDesc(
96+
prometheus.BuildFQName(namespace, "", "used_servers"),
97+
"Count of used servers", nil, nil),
98+
"dns_names": prometheus.NewDesc(
99+
prometheus.BuildFQName(namespace, "", "cached_dns_names"),
100+
"Count of DNS names in the cache", nil, nil),
101+
"dns_zones": prometheus.NewDesc(
102+
prometheus.BuildFQName(namespace, "", "cached_dns_zones"),
103+
"Count of DNS zones in the cache", nil, nil),
104+
"dns_queries": prometheus.NewDesc(
105+
prometheus.BuildFQName(namespace, "", "in_flight_dns_queries"),
106+
"Count of in-flight DNS queries", nil, nil),
107+
}
72108
)
73109

74110
// Metric descriptors.
@@ -101,6 +137,38 @@ func NewExporter(connectionString string, namespace string, logger log.Logger) *
101137
}
102138
}
103139

140+
// Query SHOW LISTS, which has a series of rows, not columns.
141+
func queryShowLists(ch chan<- prometheus.Metric, db *sql.DB, logger log.Logger) error {
142+
rows, err := db.Query("SHOW LISTS;")
143+
if err != nil {
144+
return errors.New(fmt.Sprintln("error running SHOW LISTS on database: ", err))
145+
}
146+
defer rows.Close()
147+
148+
columnNames, err := rows.Columns()
149+
if err != nil || len(columnNames) != 2 {
150+
return errors.New(fmt.Sprintln("error retrieving columns list from SHOW LISTS: ", err))
151+
}
152+
153+
var list string
154+
var items sql.RawBytes
155+
for rows.Next() {
156+
if err = rows.Scan(&list, &items); err != nil {
157+
return errors.New(fmt.Sprintln("error retrieving SHOW LISTS rows:", err))
158+
}
159+
value, err := strconv.ParseFloat(string(items), 64)
160+
if err != nil {
161+
return errors.New(fmt.Sprintln("error parsing SHOW LISTS column: ", list, err))
162+
}
163+
if metric, ok := listsMap[list]; ok {
164+
ch <- prometheus.MustNewConstMetric(metric, prometheus.GaugeValue, value)
165+
} else {
166+
level.Debug(logger).Log("msg", "SHOW LISTS unknown list", "list", list)
167+
}
168+
}
169+
return nil
170+
}
171+
104172
// Query within a namespace mapping and emit metrics. Returns fatal errors if
105173
// the scrape fails, and a slice of errors if they were non-fatal.
106174
func queryNamespaceMapping(ch chan<- prometheus.Metric, db *sql.DB, namespace string, mapping MetricMapNamespace, logger log.Logger) ([]error, error) {
@@ -109,15 +177,15 @@ func queryNamespaceMapping(ch chan<- prometheus.Metric, db *sql.DB, namespace st
109177
// Don't fail on a bad scrape of one metric
110178
rows, err := db.Query(query)
111179
if err != nil {
112-
return []error{}, errors.New(fmt.Sprintln("Error running query on database: ", namespace, err))
180+
return []error{}, errors.New(fmt.Sprintln("error running query on database: ", namespace, err))
113181
}
114182

115183
defer rows.Close()
116184

117185
var columnNames []string
118186
columnNames, err = rows.Columns()
119187
if err != nil {
120-
return []error{}, errors.New(fmt.Sprintln("Error retrieving column list for: ", namespace, err))
188+
return []error{}, errors.New(fmt.Sprintln("error retrieving column list for: ", namespace, err))
121189
}
122190

123191
// Make a lookup map for the column indices
@@ -138,7 +206,7 @@ func queryNamespaceMapping(ch chan<- prometheus.Metric, db *sql.DB, namespace st
138206
labelValues := make([]string, len(mapping.labels))
139207
err = rows.Scan(scanArgs...)
140208
if err != nil {
141-
return []error{}, errors.New(fmt.Sprintln("Error retrieving rows:", namespace, err))
209+
return []error{}, errors.New(fmt.Sprintln("error retrieving rows:", namespace, err))
142210
}
143211

144212
for i, label := range mapping.labels {
@@ -156,14 +224,14 @@ func queryNamespaceMapping(ch chan<- prometheus.Metric, db *sql.DB, namespace st
156224
case nil:
157225
labelValues[i] = ""
158226
default:
159-
nonfatalErrors = append(nonfatalErrors, fmt.Errorf("Column %s in %s has an unhandled type %v for label: %s ", columnName, namespace, v, columnData[idx]))
227+
nonfatalErrors = append(nonfatalErrors, fmt.Errorf("column %s in %s has an unhandled type %v for label: %s ", columnName, namespace, v, columnData[idx]))
160228
labelValues[i] = "<invalid>"
161229
continue
162230
}
163231

164232
// Prometheus will fail hard if the database and usernames are not UTF-8
165233
if !utf8.ValidString(labelValues[i]) {
166-
nonfatalErrors = append(nonfatalErrors, fmt.Errorf("Column %s in %s has an invalid UTF-8 for a label: %s ", columnName, namespace, columnData[idx]))
234+
nonfatalErrors = append(nonfatalErrors, fmt.Errorf("column %s in %s has an invalid UTF-8 for a label: %s ", columnName, namespace, columnData[idx]))
167235
labelValues[i] = "<invalid>"
168236
continue
169237
}
@@ -183,7 +251,7 @@ func queryNamespaceMapping(ch chan<- prometheus.Metric, db *sql.DB, namespace st
183251

184252
value, ok := metricMapping.conversion(columnData[idx])
185253
if !ok {
186-
nonfatalErrors = append(nonfatalErrors, errors.New(fmt.Sprintln("Unexpected error parsing column: ", namespace, columnName, columnData[idx])))
254+
nonfatalErrors = append(nonfatalErrors, errors.New(fmt.Sprintln("unexpected error parsing column: ", namespace, columnName, columnData[idx])))
187255
continue
188256
}
189257
// Generate the metric
@@ -345,6 +413,11 @@ func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
345413
up = 0
346414
}
347415

416+
if err = queryShowLists(ch, e.db, e.logger); err != nil {
417+
level.Error(e.logger).Log("msg", "error getting SHOW LISTS", "err", err)
418+
up = 0
419+
}
420+
348421
errMap := queryNamespaceMappings(ch, e.db, e.metricMap, e.logger)
349422
if len(errMap) > 0 {
350423
level.Error(e.logger).Log("msg", "error querying namespace mappings", "err", errMap)

0 commit comments

Comments
 (0)