2015-02-18 21:40:55 +00:00
|
|
|
// Copyright 2013 The Prometheus Authors
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
2013-07-05 17:31:53 +00:00
|
|
|
//
|
2015-02-18 21:40:55 +00:00
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
2013-07-05 17:31:53 +00:00
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"regexp"
|
|
|
|
"strings"
|
2013-07-08 18:37:12 +00:00
|
|
|
"sync"
|
|
|
|
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
2013-07-05 17:31:53 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2015-10-09 22:03:21 +00:00
|
|
|
identifierRE = `[a-zA-Z_][a-zA-Z0-9_]+`
|
|
|
|
statsdMetricRE = `[a-zA-Z_](-?[a-zA-Z0-9_])+`
|
|
|
|
|
|
|
|
metricLineRE = regexp.MustCompile(`^(\*\.|` + statsdMetricRE + `\.)+(\*|` + statsdMetricRE + `)$`)
|
2013-07-05 17:31:53 +00:00
|
|
|
labelLineRE = regexp.MustCompile(`^(` + identifierRE + `)\s*=\s*"(.*)"$`)
|
2013-07-12 12:27:51 +00:00
|
|
|
metricNameRE = regexp.MustCompile(`^` + identifierRE + `$`)
|
2013-07-05 17:31:53 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type metricMapping struct {
|
|
|
|
regex *regexp.Regexp
|
2014-06-26 13:56:21 +00:00
|
|
|
labels prometheus.Labels
|
2013-07-05 17:31:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type metricMapper struct {
|
|
|
|
mappings []metricMapping
|
2013-07-08 18:37:12 +00:00
|
|
|
mutex sync.Mutex
|
2013-07-05 17:31:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type configLoadStates int
|
|
|
|
|
|
|
|
const (
|
|
|
|
SEARCHING configLoadStates = iota
|
|
|
|
METRIC_DEFINITION
|
|
|
|
)
|
|
|
|
|
2013-07-08 18:37:12 +00:00
|
|
|
func (m *metricMapper) initFromString(fileContents string) error {
|
2013-07-05 17:31:53 +00:00
|
|
|
lines := strings.Split(fileContents, "\n")
|
2017-02-24 15:48:05 +00:00
|
|
|
numLines := len(lines)
|
2013-07-05 17:31:53 +00:00
|
|
|
state := SEARCHING
|
|
|
|
|
2013-07-08 18:37:12 +00:00
|
|
|
parsedMappings := []metricMapping{}
|
2014-06-26 13:56:21 +00:00
|
|
|
currentMapping := metricMapping{labels: prometheus.Labels{}}
|
2013-07-05 17:31:53 +00:00
|
|
|
for i, line := range lines {
|
|
|
|
line := strings.TrimSpace(line)
|
|
|
|
|
|
|
|
switch state {
|
|
|
|
case SEARCHING:
|
|
|
|
if line == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if !metricLineRE.MatchString(line) {
|
|
|
|
return fmt.Errorf("Line %d: expected metric match line, got: %s", i, line)
|
|
|
|
}
|
2013-07-08 18:37:12 +00:00
|
|
|
|
|
|
|
// Translate the glob-style metric match line into a proper regex that we
|
|
|
|
// can use to match metrics later on.
|
2013-07-05 17:31:53 +00:00
|
|
|
metricRe := strings.Replace(line, ".", "\\.", -1)
|
|
|
|
metricRe = strings.Replace(metricRe, "*", "([^.]+)", -1)
|
2013-07-08 18:37:12 +00:00
|
|
|
currentMapping.regex = regexp.MustCompile("^" + metricRe + "$")
|
|
|
|
|
2013-07-05 17:31:53 +00:00
|
|
|
state = METRIC_DEFINITION
|
|
|
|
|
|
|
|
case METRIC_DEFINITION:
|
2017-02-24 15:48:05 +00:00
|
|
|
if (i == numLines-1) && (line != "") {
|
|
|
|
return fmt.Errorf("Line %d: missing terminating newline", i)
|
|
|
|
}
|
2013-07-05 17:31:53 +00:00
|
|
|
if line == "" {
|
2013-07-08 18:37:12 +00:00
|
|
|
if len(currentMapping.labels) == 0 {
|
2013-07-05 17:31:53 +00:00
|
|
|
return fmt.Errorf("Line %d: metric mapping didn't set any labels", i)
|
|
|
|
}
|
2013-07-08 18:37:12 +00:00
|
|
|
if _, ok := currentMapping.labels["name"]; !ok {
|
2013-07-05 17:31:53 +00:00
|
|
|
return fmt.Errorf("Line %d: metric mapping didn't set a metric name", i)
|
|
|
|
}
|
2013-07-08 18:37:12 +00:00
|
|
|
parsedMappings = append(parsedMappings, currentMapping)
|
2013-07-05 17:31:53 +00:00
|
|
|
state = SEARCHING
|
2014-06-26 13:56:21 +00:00
|
|
|
currentMapping = metricMapping{labels: prometheus.Labels{}}
|
2013-07-05 17:31:53 +00:00
|
|
|
continue
|
|
|
|
}
|
2017-02-24 15:48:05 +00:00
|
|
|
if err := m.updateMapping(line, i, ¤tMapping); err != nil {
|
|
|
|
return err
|
2013-07-12 12:27:51 +00:00
|
|
|
}
|
2013-07-05 17:31:53 +00:00
|
|
|
default:
|
|
|
|
panic("illegal state")
|
|
|
|
}
|
|
|
|
}
|
2013-07-08 18:37:12 +00:00
|
|
|
|
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
|
|
|
m.mappings = parsedMappings
|
|
|
|
|
2014-06-26 13:56:21 +00:00
|
|
|
mappingsCount.Set(float64(len(parsedMappings)))
|
2013-07-08 18:37:12 +00:00
|
|
|
|
2013-07-05 17:31:53 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-07-08 18:37:12 +00:00
|
|
|
func (m *metricMapper) initFromFile(fileName string) error {
|
2013-07-05 17:31:53 +00:00
|
|
|
mappingStr, err := ioutil.ReadFile(fileName)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2013-07-08 18:37:12 +00:00
|
|
|
return m.initFromString(string(mappingStr))
|
2013-07-05 17:31:53 +00:00
|
|
|
}
|
|
|
|
|
2014-06-26 13:56:21 +00:00
|
|
|
func (m *metricMapper) getMapping(statsdMetric string) (labels prometheus.Labels, present bool) {
|
2013-07-08 18:37:12 +00:00
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
|
|
|
|
|
|
|
for _, mapping := range m.mappings {
|
2013-07-05 17:31:53 +00:00
|
|
|
matches := mapping.regex.FindStringSubmatchIndex(statsdMetric)
|
|
|
|
if len(matches) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2014-06-26 13:56:21 +00:00
|
|
|
labels := prometheus.Labels{}
|
2013-07-05 17:31:53 +00:00
|
|
|
for label, valueExpr := range mapping.labels {
|
|
|
|
value := mapping.regex.ExpandString([]byte{}, valueExpr, statsdMetric, matches)
|
|
|
|
labels[label] = string(value)
|
|
|
|
}
|
|
|
|
return labels, true
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, false
|
|
|
|
}
|
2017-02-24 15:48:05 +00:00
|
|
|
|
|
|
|
func (m *metricMapper) updateMapping(line string, i int, mapping *metricMapping) error {
|
|
|
|
matches := labelLineRE.FindStringSubmatch(line)
|
|
|
|
if len(matches) != 3 {
|
|
|
|
return fmt.Errorf("Line %d: expected label mapping line, got: %s", i, line)
|
|
|
|
}
|
|
|
|
label, value := matches[1], matches[2]
|
|
|
|
if label == "name" && !metricNameRE.MatchString(value) {
|
|
|
|
return fmt.Errorf("Line %d: metric name '%s' doesn't match regex '%s'", i, value, metricNameRE)
|
|
|
|
}
|
|
|
|
(*mapping).labels[label] = value
|
|
|
|
return nil
|
|
|
|
}
|