mirror of
https://github.com/prometheus-community/postgres_exporter
synced 2025-04-11 03:31:26 +00:00
135 lines
4.6 KiB
Go
135 lines
4.6 KiB
Go
package query
|
|
|
|
import (
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"database/sql"
|
|
"fmt"
|
|
"errors"
|
|
"github.com/prometheus/common/log"
|
|
"github.com/wrouesnel/postgres_exporter/exporter/metricmaps"
|
|
"github.com/wrouesnel/postgres_exporter/exporter/dbconv"
|
|
)
|
|
|
|
// Query within a namespace mapping and emit metrics. Returns fatal errors if
|
|
// the scrape fails, and a slice of errors if they were non-fatal.
|
|
func queryNamespaceMapping(ch chan<- prometheus.Metric, db *sql.DB, namespace string, mapping metricmaps.MetricMapNamespace, queryOverrides metricmaps.NamespaceOverrideQueryMapping) ([]error, error) {
|
|
// Check for a query override for this namespace
|
|
query, found := queryOverrides[namespace]
|
|
|
|
// Was this query disabled (i.e. nothing sensible can be queried on cu
|
|
// version of PostgreSQL?
|
|
if query == "" && found {
|
|
// Return success (no pertinent data)
|
|
return []error{}, nil
|
|
}
|
|
|
|
// Don't fail on a bad scrape of one metric
|
|
var rows *sql.Rows
|
|
var err error
|
|
|
|
if !found {
|
|
// I've no idea how to avoid this properly at the moment, but this is
|
|
// an admin tool so you're not injecting SQL right?
|
|
rows, err = db.Query(fmt.Sprintf("SELECT * FROM %s;", namespace)) // nolint: gas, safesql
|
|
} else {
|
|
rows, err = db.Query(query) // nolint: safesql
|
|
}
|
|
if err != nil {
|
|
return []error{}, errors.New(fmt.Sprintln("Error running query on database: ", namespace, err))
|
|
}
|
|
defer rows.Close() // nolint: errcheck
|
|
|
|
var columnNames []string
|
|
columnNames, err = rows.Columns()
|
|
if err != nil {
|
|
return []error{}, errors.New(fmt.Sprintln("Error retrieving column list for: ", namespace, err))
|
|
}
|
|
|
|
// Make a lookup map for the column indices
|
|
var columnIdx = make(map[string]int, len(columnNames))
|
|
for i, n := range columnNames {
|
|
columnIdx[n] = i
|
|
}
|
|
|
|
var columnData = make([]interface{}, len(columnNames))
|
|
var scanArgs = make([]interface{}, len(columnNames))
|
|
for i := range columnData {
|
|
scanArgs[i] = &columnData[i]
|
|
}
|
|
|
|
nonfatalErrors := []error{}
|
|
|
|
for rows.Next() {
|
|
err = rows.Scan(scanArgs...)
|
|
if err != nil {
|
|
return []error{}, errors.New(fmt.Sprintln("Error retrieving rows:", namespace, err))
|
|
}
|
|
|
|
// Get the label values for this row
|
|
var labels = make([]string, len(mapping.Labels))
|
|
for idx, columnName := range mapping.Labels {
|
|
labels[idx], _ = dbconv.DbToString(columnData[columnIdx[columnName]])
|
|
}
|
|
|
|
// Loop over column names, and match to scan data. Unknown columns
|
|
// will be filled with an untyped metric number *if* they can be
|
|
// converted to float64s. NULLs are allowed and treated as NaN.
|
|
for idx, columnName := range columnNames {
|
|
if metricMapping, ok := mapping.ColumnMappings[columnName]; ok {
|
|
// Is this a metricy metric?
|
|
if metricMapping.Discard {
|
|
continue
|
|
}
|
|
|
|
value, ok := dbconv.DbToFloat64(columnData[idx])
|
|
if !ok {
|
|
nonfatalErrors = append(nonfatalErrors, errors.New(fmt.Sprintln("Unexpected error parsing column: ", namespace, columnName, columnData[idx])))
|
|
continue
|
|
}
|
|
|
|
// Generate the metric
|
|
ch <- prometheus.MustNewConstMetric(metricMapping.Desc, metricMapping.Vtype, value, labels...)
|
|
} else {
|
|
// Unknown metric. Report as untyped if scan to float64 works, else note an error too.
|
|
metricLabel := fmt.Sprintf("%s_%s", namespace, columnName)
|
|
desc := prometheus.NewDesc(metricLabel, fmt.Sprintf("Unknown metric from %s", namespace), mapping.Labels, nil)
|
|
|
|
// Its not an error to fail here, since the values are
|
|
// unexpected anyway.
|
|
value, ok := dbconv.DbToFloat64(columnData[idx])
|
|
if !ok {
|
|
nonfatalErrors = append(nonfatalErrors, errors.New(fmt.Sprintln("Unparseable column type - discarding: ", namespace, columnName, err)))
|
|
continue
|
|
}
|
|
ch <- prometheus.MustNewConstMetric(desc, prometheus.UntypedValue, value, labels...)
|
|
}
|
|
}
|
|
}
|
|
return nonfatalErrors, nil
|
|
}
|
|
|
|
// Iterate through all the namespace mappings in the exporter and run their
|
|
// queries.
|
|
func queryNamespaceMappings(ch chan<- prometheus.Metric, db *sql.DB, metricMap map[string]metricmaps.MetricMapNamespace, queryOverrides map[string]string) map[string]error {
|
|
// Return a map of namespace -> errors
|
|
namespaceErrors := make(map[string]error)
|
|
|
|
for namespace, mapping := range metricMap {
|
|
log.Debugln("Querying namespace: ", namespace)
|
|
nonFatalErrors, err := queryNamespaceMapping(ch, db, namespace, mapping, queryOverrides)
|
|
// Serious error - a namespace disappeared
|
|
if err != nil {
|
|
namespaceErrors[namespace] = err
|
|
log.Infoln(err)
|
|
}
|
|
// Non-serious errors - likely version or parsing problems.
|
|
if len(nonFatalErrors) > 0 {
|
|
for _, err := range nonFatalErrors {
|
|
log.Infoln(err.Error())
|
|
}
|
|
}
|
|
}
|
|
|
|
return namespaceErrors
|
|
}
|