Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ metrics:
key_labels:
# Populated from the `market` column of each row.
- Market
#json_labels: labels # Optional column, with additional JSON formated labels, ie. { "label1": "value1", ... }
values: [LastUpdateTime]
query: |
SELECT Market, max(UpdateTime) AS LastUpdateTime
Expand Down
4 changes: 2 additions & 2 deletions collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@ type collector struct {

// NewCollector returns a new Collector with the given configuration and database. The metrics it creates will all have
// the provided const labels applied.
func NewCollector(logContext string, cc *config.CollectorConfig, constLabels []*dto.LabelPair) (Collector, errors.WithContext) {
func NewCollector(logContext string, cc *config.CollectorConfig, constLabels []*dto.LabelPair, gc *config.GlobalConfig) (Collector, errors.WithContext) {
logContext = fmt.Sprintf("%s, collector=%q", logContext, cc.Name)

// Maps each query to the list of metric families it populates.
queryMFs := make(map[*config.QueryConfig][]*MetricFamily, len(cc.Metrics))

// Instantiate metric families.
for _, mc := range cc.Metrics {
mf, err := NewMetricFamily(logContext, mc, constLabels)
mf, err := NewMetricFamily(logContext, mc, constLabels, gc)
if err != nil {
return nil, err
}
Expand Down
7 changes: 7 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@ type GlobalConfig struct {
TimeoutOffset model.Duration `yaml:"scrape_timeout_offset"` // offset to subtract from timeout in seconds
MaxConns int `yaml:"max_connections"` // maximum number of open connections to any one target
MaxIdleConns int `yaml:"max_idle_connections"` // maximum number of idle connections to any one target
MaxLabelNameLen int `yaml:"max_label_name_len"` // maximum length of label name
MaxLabelValueLen int `yaml:"max_label_value_len"` // maximum length of label value
MaxJsonLabels int `yaml:"max_json_labels"` // maximum number of labels extracted from json column

// Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline" json:"-"`
Expand All @@ -157,6 +160,9 @@ func (g *GlobalConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
g.TimeoutOffset = model.Duration(500 * time.Millisecond)
g.MaxConns = 3
g.MaxIdleConns = 3
g.MaxLabelNameLen = 25
g.MaxLabelValueLen = 50
g.MaxJsonLabels = 10

type plain GlobalConfig
if err := unmarshal((*plain)(g)); err != nil {
Expand Down Expand Up @@ -371,6 +377,7 @@ type MetricConfig struct {
Help string `yaml:"help"` // the Prometheus metric help text
KeyLabels []string `yaml:"key_labels,omitempty"` // expose these columns as labels
ValueLabel string `yaml:"value_label,omitempty"` // with multiple value columns, map their names under this label
JsonLabels string `yaml:"json_labels,omitempty"` // expose content of given json column as labels
Values []string `yaml:"values"` // expose each of these columns as a value, keyed by column name
QueryLiteral string `yaml:"query,omitempty"` // a literal query
QueryRef string `yaml:"query_ref,omitempty"` // references a query in the query map
Expand Down
113 changes: 91 additions & 22 deletions metric.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ package sql_exporter
import (
"fmt"
"sort"
"math"
"encoding/json"

"github.com/free/sql_exporter/config"
"github.com/free/sql_exporter/errors"
"github.com/golang/protobuf/proto"
"github.com/prometheus/client_golang/prometheus"
dto "github.com/prometheus/client_model/go"
log "github.com/golang/glog"
)

// MetricDesc is a descriptor for a family of metrics, sharing the same name, help, labes, type.
Expand All @@ -19,6 +22,7 @@ type MetricDesc interface {
ConstLabels() []*dto.LabelPair
Labels() []string
LogContext() string
GlobalConfig() *config.GlobalConfig
}

//
Expand All @@ -27,14 +31,15 @@ type MetricDesc interface {

// MetricFamily implements MetricDesc for SQL metrics, with logic for populating its labels and values from sql.Rows.
type MetricFamily struct {
config *config.MetricConfig
constLabels []*dto.LabelPair
labels []string
logContext string
config *config.MetricConfig
constLabels []*dto.LabelPair
labels []string
logContext string
globalConfig *config.GlobalConfig
}

// NewMetricFamily creates a new MetricFamily with the given metric config and const labels (e.g. job and instance).
func NewMetricFamily(logContext string, mc *config.MetricConfig, constLabels []*dto.LabelPair) (*MetricFamily, errors.WithContext) {
func NewMetricFamily(logContext string, mc *config.MetricConfig, constLabels []*dto.LabelPair, gc *config.GlobalConfig) (*MetricFamily, errors.WithContext) {
logContext = fmt.Sprintf("%s, metric=%q", logContext, mc.Name)

if len(mc.Values) == 0 {
Expand All @@ -51,25 +56,33 @@ func NewMetricFamily(logContext string, mc *config.MetricConfig, constLabels []*
}

return &MetricFamily{
config: mc,
constLabels: constLabels,
labels: labels,
logContext: logContext,
config: mc,
constLabels: constLabels,
labels: labels,
logContext: logContext,
globalConfig: gc,
}, nil
}

// Collect is the equivalent of prometheus.Collector.Collect() but takes a Query output map to populate values from.
func (mf MetricFamily) Collect(row map[string]interface{}, ch chan<- Metric) {
labelValues := make([]string, len(mf.labels))
for i, label := range mf.config.KeyLabels {
labelValues[i] = row[label].(string)
var userLabels []*dto.LabelPair

// TODO: move to func()
if mf.config.JsonLabels != "" && row[mf.config.JsonLabels].(string) != "" {
userLabels = parseJsonLabels(mf, row[mf.config.JsonLabels].(string))
}

labelValues := make([]string, 0, len(mf.labels))
for _, label := range mf.config.KeyLabels {
labelValues = append(labelValues, row[label].(string))
}
for _, v := range mf.config.Values {
if mf.config.ValueLabel != "" {
labelValues[len(labelValues)-1] = v
}
value := row[v].(float64)
ch <- NewMetric(&mf, value, labelValues...)
ch <- NewMetric(&mf, value, labelValues, userLabels...)
}
}

Expand Down Expand Up @@ -103,6 +116,11 @@ func (mf MetricFamily) LogContext() string {
return mf.logContext
}

// GlobalConfig implements MetricDesc.
func (mf MetricFamily) GlobalConfig() *config.GlobalConfig {
return mf.globalConfig
}

//
// automaticMetricDesc
//
Expand Down Expand Up @@ -160,6 +178,11 @@ func (a automaticMetricDesc) LogContext() string {
return a.logContext
}

// GlobalConfig implements MetricDesc.
func (a automaticMetricDesc) GlobalConfig() *config.GlobalConfig {
return nil
}

//
// Metric
//
Expand All @@ -173,14 +196,14 @@ type Metric interface {
// NewMetric returns a metric with one fixed value that cannot be changed.
//
// NewMetric panics if the length of labelValues is not consistent with desc.labels().
func NewMetric(desc MetricDesc, value float64, labelValues ...string) Metric {
func NewMetric(desc MetricDesc, value float64, labelValues []string, userLabels ...*dto.LabelPair) Metric {
if len(desc.Labels()) != len(labelValues) {
panic(fmt.Sprintf("[%s] expected %d labels, got %d", desc.LogContext(), len(desc.Labels()), len(labelValues)))
}
return &constMetric{
desc: desc,
val: value,
labelPairs: makeLabelPairs(desc, labelValues),
labelPairs: makeLabelPairs(desc, labelValues, userLabels),
}
}

Expand Down Expand Up @@ -210,26 +233,72 @@ func (m *constMetric) Write(out *dto.Metric) errors.WithContext {
return nil
}

func makeLabelPairs(desc MetricDesc, labelValues []string) []*dto.LabelPair {
func parseJsonLabels(desc MetricDesc, labels string) []*dto.LabelPair {
var userLabels []*dto.LabelPair
var jsonLabels map[string]string

config := desc.GlobalConfig()
maxJsonLabels := 0
if (config != nil) {
maxJsonLabels = config.MaxJsonLabels
}

err := json.Unmarshal([]byte(labels), &jsonLabels)
// errors are logged but ignored
if err != nil {
log.Warningf("[%s] Failed to parse JSON labels returned by query - %s", desc.LogContext(), err)
} else {
userLabelsMax := int(math.Min(float64(len(jsonLabels)), float64(maxJsonLabels)))
userLabels = make([]*dto.LabelPair, userLabelsMax)

idx := 0
for name, value := range jsonLabels {
// limit label count
if idx >= maxJsonLabels {
break
}
userLabels[idx] = makeLabelPair(desc, name, value)
idx = idx + 1
}
}
return userLabels
}

func makeLabelPair(desc MetricDesc, label string, value string) *dto.LabelPair {
config := desc.GlobalConfig()
if config != nil {
if (len(label) > config.MaxLabelNameLen) {
label = label[:config.MaxLabelNameLen]
}
if (len(value) > config.MaxLabelValueLen) {
value = value[:config.MaxLabelValueLen]
}
}

return &dto.LabelPair{
Name: proto.String(label),
Value: proto.String(value),
}
}

func makeLabelPairs(desc MetricDesc, labelValues []string, userLabels []*dto.LabelPair) []*dto.LabelPair {
labels := desc.Labels()
constLabels := desc.ConstLabels()

totalLen := len(labels) + len(constLabels)
totalLen := len(labels) + len(constLabels) + len(userLabels)
if totalLen == 0 {
// Super fast path.
return nil
}
if len(labels) == 0 {
if len(labels) == 0 && len(userLabels) == 0{
// Moderately fast path.
return constLabels
}
labelPairs := make([]*dto.LabelPair, 0, totalLen)
for i, label := range labels {
labelPairs = append(labelPairs, &dto.LabelPair{
Name: proto.String(label),
Value: proto.String(labelValues[i]),
})
labelPairs = append(labelPairs, makeLabelPair(desc, label, labelValues[i]))
}
labelPairs = append(labelPairs, userLabels...)
labelPairs = append(labelPairs, constLabels...)
sort.Sort(prometheus.LabelPairSorter(labelPairs))
return labelPairs
Expand Down
5 changes: 5 additions & 0 deletions query.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ func NewQuery(logContext string, qc *config.QueryConfig, metricFamilies ...*Metr
return nil, err
}
}
if mf.config.JsonLabels != "" {
if err := setColumnType(logContext, mf.config.JsonLabels, columnTypeKey, columnTypes); err != nil {
return nil, err
}
}
}

q := Query{
Expand Down
6 changes: 3 additions & 3 deletions target.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func NewTarget(

collectors := make([]Collector, 0, len(ccs))
for _, cc := range ccs {
c, err := NewCollector(logContext, cc, constLabelPairs)
c, err := NewCollector(logContext, cc, constLabelPairs, gc)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -105,7 +105,7 @@ func (t *target) Collect(ctx context.Context, ch chan<- Metric) {
}
if t.name != "" {
// Export the target's `up` metric as early as we know what it should be.
ch <- NewMetric(t.upDesc, boolToFloat64(targetUp))
ch <- NewMetric(t.upDesc, boolToFloat64(targetUp), nil)
}

var wg sync.WaitGroup
Expand All @@ -125,7 +125,7 @@ func (t *target) Collect(ctx context.Context, ch chan<- Metric) {

if t.name != "" {
// And export a `scrape duration` metric once we're done scraping.
ch <- NewMetric(t.scrapeDurationDesc, float64(time.Since(scrapeStart))*1e-9)
ch <- NewMetric(t.scrapeDurationDesc, float64(time.Since(scrapeStart))*1e-9, nil)
}
}

Expand Down