From a6b9654e6aabd79000f429ddc312ab946cc54628 Mon Sep 17 00:00:00 2001 From: rustyclock Date: Wed, 19 Aug 2020 08:56:31 +0900 Subject: [PATCH] Use NewConstMetric instead of regular Gauge Signed-off-by: rustyclock --- cmd/main.go | 11 +++-- config/config.go | 8 ---- examples/config.yml | 2 +- go.mod | 2 - go.sum | 10 ----- internal/collector.go | 101 ++++++++++++++++++++++++------------------ internal/util.go | 68 +++++++++++++++++++--------- 7 files changed, 113 insertions(+), 89 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index 9e1a4c6..473e088 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -84,11 +84,14 @@ func probeHandler(w http.ResponseWriter, r *http.Request, logger log.Logger, con registry := prometheus.NewPedanticRegistry() - metrics, err := internal.CreateMetricsList(registry, config) + metrics, err := internal.CreateMetricsList(config) if err != nil { level.Error(logger).Log("msg", "Failed to create metrics list from config", "err", err) //nolint:errcheck } + jsonMetricCollector := internal.JsonMetricCollector{JsonMetrics: metrics} + jsonMetricCollector.Logger = logger + target := r.URL.Query().Get("target") if target == "" { http.Error(w, "Target parameter is missing", http.StatusBadRequest) @@ -98,10 +101,12 @@ func probeHandler(w http.ResponseWriter, r *http.Request, logger log.Logger, con data, err := internal.FetchJson(ctx, logger, target, config) if err != nil { http.Error(w, "Failed to fetch JSON response. TARGET: "+target+", ERROR: "+err.Error(), http.StatusServiceUnavailable) - } else { - internal.Scrape(logger, metrics, data) + return } + jsonMetricCollector.Data = data + + registry.MustRegister(jsonMetricCollector) h := promhttp.HandlerFor(registry, promhttp.HandlerOpts{}) h.ServeHTTP(w, r) diff --git a/config/config.go b/config/config.go index 58d0247..6f32bd7 100644 --- a/config/config.go +++ b/config/config.go @@ -49,14 +49,6 @@ type GlobalConfig struct { TimeoutSeconds float64 `yaml:"timeout_seconds,omitempty"` } -func (metric *Metric) LabelNames() []string { - var labelNames []string - for name := range metric.Labels { - labelNames = append(labelNames, name) - } - return labelNames -} - func LoadConfig(configPath string) (Config, error) { var config Config data, err := ioutil.ReadFile(configPath) diff --git a/examples/config.yml b/examples/config.yml index 9dc0593..9252d78 100644 --- a/examples/config.yml +++ b/examples/config.yml @@ -26,7 +26,7 @@ headers: # # http_client_config: # tls_config: -# insecure_skip_verify: false +# insecure_skip_verify: true # basic_auth: # username: myuser # #password: veryverysecret diff --git a/go.mod b/go.mod index 33348f6..34b98f4 100644 --- a/go.mod +++ b/go.mod @@ -3,12 +3,10 @@ module github.com/prometheus-community/json_exporter go 1.14 require ( - github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect github.com/go-kit/kit v0.9.0 github.com/kawamuray/jsonpath v0.0.0-20160208140654-5c448ebf9735 github.com/prometheus/client_golang v1.7.1 github.com/prometheus/common v0.10.0 - github.com/urfave/cli v1.22.4 gopkg.in/alecthomas/kingpin.v2 v2.2.6 gopkg.in/yaml.v2 v2.3.0 ) diff --git a/go.sum b/go.sum index 9d7bb98..864456d 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,3 @@ -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -11,9 +10,6 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -82,10 +78,6 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -95,8 +87,6 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA= -github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= diff --git a/internal/collector.go b/internal/collector.go index f9932a4..110cebb 100644 --- a/internal/collector.go +++ b/internal/collector.go @@ -14,7 +14,7 @@ package internal import ( - "fmt" + "errors" "strconv" "github.com/go-kit/kit/log" @@ -23,49 +23,71 @@ import ( "github.com/prometheus/client_golang/prometheus" ) -type JsonGaugeCollector struct { - *prometheus.GaugeVec - KeyJsonPath string - ValueJsonPath string - LabelsJsonPath map[string]string +type JsonMetricCollector struct { + JsonMetrics []JsonMetric + Data []byte + Logger log.Logger } -func Scrape(logger log.Logger, collectors []JsonGaugeCollector, json []byte) { +type JsonMetric struct { + Desc *prometheus.Desc + KeyJsonPath string + ValueJsonPath string + LabelsJsonPaths []string +} - for _, collector := range collectors { - if collector.ValueJsonPath == "" { // ScrapeType is 'value' +func (mc JsonMetricCollector) Describe(ch chan<- *prometheus.Desc) { + for _, m := range mc.JsonMetrics { + ch <- m.Desc + } +} - // Since this is a 'value' type metric, there should be exactly one element in results - // If there are more, just return the first one - // TODO: Better handling/logging for this scenario - floatValue, err := extractValue(logger, json, collector.KeyJsonPath) +func (mc JsonMetricCollector) Collect(ch chan<- prometheus.Metric) { + for _, m := range mc.JsonMetrics { + if m.ValueJsonPath == "" { // ScrapeType is 'value' + floatValue, err := extractValue(mc.Logger, mc.Data, m.KeyJsonPath) if err != nil { - level.Error(logger).Log("msg", "Failed to extract float value for metric", "path", collector.KeyJsonPath, "err", err) //nolint:errcheck + // Avoid noise and continue silently if it was a missing path error + if err.Error() == "Path not found" { + level.Debug(mc.Logger).Log("msg", "Failed to extract float value for metric", "path", m.KeyJsonPath, "err", err, "metric", m.Desc) //nolint:errcheck + continue + } + level.Error(mc.Logger).Log("msg", "Failed to extract float value for metric", "path", m.KeyJsonPath, "err", err, "metric", m.Desc) //nolint:errcheck continue } - collector.With(extractLabels(logger, json, collector.LabelsJsonPath)).Set(floatValue) + ch <- prometheus.MustNewConstMetric( + m.Desc, + prometheus.UntypedValue, + floatValue, + extractLabels(mc.Logger, mc.Data, m.LabelsJsonPaths)..., + ) } else { // ScrapeType is 'object' - path, err := compilePath(collector.KeyJsonPath) + path, err := compilePath(m.KeyJsonPath) if err != nil { - level.Error(logger).Log("msg", "Failed to compile path", "path", collector.KeyJsonPath, "err", err) //nolint:errcheck + level.Error(mc.Logger).Log("msg", "Failed to compile path", "path", m.KeyJsonPath, "err", err) //nolint:errcheck continue } - eval, err := jsonpath.EvalPathsInBytes(json, []*jsonpath.Path{path}) + eval, err := jsonpath.EvalPathsInBytes(mc.Data, []*jsonpath.Path{path}) if err != nil { - level.Error(logger).Log("msg", "Failed to create evaluator for json path", "path", collector.KeyJsonPath, "err", err) //nolint:errcheck + level.Error(mc.Logger).Log("msg", "Failed to create evaluator for json path", "path", m.KeyJsonPath, "err", err) //nolint:errcheck continue } for { if result, ok := eval.Next(); ok { - floatValue, err := extractValue(logger, result.Value, collector.ValueJsonPath) + floatValue, err := extractValue(mc.Logger, result.Value, m.ValueJsonPath) if err != nil { - level.Error(logger).Log("msg", "Failed to extract value", "path", collector.ValueJsonPath, "err", err) //nolint:errcheck + level.Error(mc.Logger).Log("msg", "Failed to extract value", "path", m.ValueJsonPath, "err", err) //nolint:errcheck continue } - collector.With(extractLabels(logger, result.Value, collector.LabelsJsonPath)).Set(floatValue) + ch <- prometheus.MustNewConstMetric( + m.Desc, + prometheus.UntypedValue, + floatValue, + extractLabels(mc.Logger, result.Value, m.LabelsJsonPaths)..., + ) } else { break } @@ -102,68 +124,61 @@ func extractValue(logger log.Logger, json []byte, path string) (float64, error) // Dynamic value p, err := compilePath(path) if err != nil { - return floatValue, fmt.Errorf("Failed to compile path: '%s', ERROR: '%s'", path, err) + return floatValue, err } eval, err := jsonpath.EvalPathsInBytes(json, []*jsonpath.Path{p}) if err != nil { - return floatValue, fmt.Errorf("Failed to create evaluator for JSON Path: %s, ERROR: '%s'", path, err) + return floatValue, err } result, ok := eval.Next() if result == nil || !ok { if eval.Error != nil { - return floatValue, fmt.Errorf("Failed to evaluate json. ERROR: '%s', PATH: '%s', JSON: '%s'", eval.Error, path, string(json)) + return floatValue, eval.Error } else { level.Debug(logger).Log("msg", "Path not found", "path", path, "json", string(json)) //nolint:errcheck - return floatValue, fmt.Errorf("Could not find path. PATH: '%s'", path) + return floatValue, errors.New("Path not found") } } return SanitizeValue(result) } -func extractLabels(logger log.Logger, json []byte, l map[string]string) map[string]string { - labels := make(map[string]string) - for label, path := range l { - - if len(path) < 1 || path[0] != '$' { - // Static value - labels[label] = path - continue - } +// Returns the list of labels created from the list of provided json paths +func extractLabels(logger log.Logger, json []byte, paths []string) []string { + labels := make([]string, len(paths)) + for i, path := range paths { // Dynamic value p, err := compilePath(path) if err != nil { - level.Error(logger).Log("msg", "Failed to compile path for label", "path", path, "label", label, "err", err) //nolint:errcheck - labels[label] = "" + level.Error(logger).Log("msg", "Failed to compile path for label", "path", path, "err", err) //nolint:errcheck continue } eval, err := jsonpath.EvalPathsInBytes(json, []*jsonpath.Path{p}) if err != nil { level.Error(logger).Log("msg", "Failed to create evaluator for json", "path", path, "err", err) //nolint:errcheck - labels[label] = "" continue } result, ok := eval.Next() if result == nil || !ok { if eval.Error != nil { - level.Error(logger).Log("msg", "Failed to evaluate", "label", label, "json", string(json), "err", eval.Error) //nolint:errcheck + level.Error(logger).Log("msg", "Failed to evaluate", "json", string(json), "err", eval.Error) //nolint:errcheck } else { - level.Warn(logger).Log("msg", "Label path not found in json", "path", path, "label", label) //nolint:errcheck - level.Debug(logger).Log("msg", "Label path not found in json", "path", path, "label", label, "json", string(json)) //nolint:errcheck + level.Warn(logger).Log("msg", "Label path not found in json", "path", path) //nolint:errcheck + level.Debug(logger).Log("msg", "Label path not found in json", "path", path, "json", string(json)) //nolint:errcheck } continue } l, err := strconv.Unquote(string(result.Value)) if err == nil { - labels[label] = l + labels[i] = l } else { - labels[label] = string(result.Value) + labels[i] = string(result.Value) } } return labels diff --git a/internal/util.go b/internal/util.go index 2cd812e..9ad4eb0 100644 --- a/internal/util.go +++ b/internal/util.go @@ -71,35 +71,59 @@ func parseValue(bytes []byte) (float64, error) { return value, nil } -func CreateMetricsList(r *prometheus.Registry, c config.Config) ([]JsonGaugeCollector, error) { - var metrics []JsonGaugeCollector +func CreateMetricsList(c config.Config) ([]JsonMetric, error) { + var metrics []JsonMetric for _, metric := range c.Metrics { switch metric.Type { case config.ValueScrape: - jsonCollector := JsonGaugeCollector{ - GaugeVec: prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Name: metric.Name, - Help: metric.Help, - }, metric.LabelNames()), - KeyJsonPath: metric.Path, - LabelsJsonPath: metric.Labels, + constLabels := make(map[string]string) + var variableLabels, variableLabelsValues []string + for k, v := range metric.Labels { + if len(v) < 1 || v[0] != '$' { + // Static value + constLabels[k] = v + } else { + variableLabels = append(variableLabels, k) + variableLabelsValues = append(variableLabelsValues, v) + } } - r.MustRegister(jsonCollector) - metrics = append(metrics, jsonCollector) + jsonMetric := JsonMetric{ + Desc: prometheus.NewDesc( + metric.Name, + metric.Help, + variableLabels, + constLabels, + ), + KeyJsonPath: metric.Path, + LabelsJsonPaths: variableLabelsValues, + } + metrics = append(metrics, jsonMetric) case config.ObjectScrape: for subName, valuePath := range metric.Values { name := MakeMetricName(metric.Name, subName) - jsonCollector := JsonGaugeCollector{ - GaugeVec: prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Name: name, - Help: name, - }, metric.LabelNames()), - KeyJsonPath: metric.Path, - ValueJsonPath: valuePath, - LabelsJsonPath: metric.Labels, + constLabels := make(map[string]string) + var variableLabels, variableLabelsValues []string + for k, v := range metric.Labels { + if len(v) < 1 || v[0] != '$' { + // Static value + constLabels[k] = v + } else { + variableLabels = append(variableLabels, k) + variableLabelsValues = append(variableLabelsValues, v) + } } - r.MustRegister(jsonCollector) - metrics = append(metrics, jsonCollector) + jsonMetric := JsonMetric{ + Desc: prometheus.NewDesc( + name, + metric.Help, + variableLabels, + constLabels, + ), + KeyJsonPath: metric.Path, + ValueJsonPath: valuePath, + LabelsJsonPaths: variableLabelsValues, + } + metrics = append(metrics, jsonMetric) } default: return nil, fmt.Errorf("Unknown metric type: '%s', for metric: '%s'", metric.Type, metric.Name) @@ -112,7 +136,7 @@ func FetchJson(ctx context.Context, logger log.Logger, endpoint string, config c httpClientConfig := config.HTTPClientConfig client, err := pconfig.NewClientFromConfig(httpClientConfig, "fetch_json", true) if err != nil { - level.Error(logger).Log("msg", "Error generating HTTP client", "err", err) + level.Error(logger).Log("msg", "Error generating HTTP client", "err", err) //nolint:errcheck return nil, err } req, err := http.NewRequest("GET", endpoint, nil)