func handler(w http.ResponseWriter, r *http.Request) { cfg, err := LoadFile(*configFile) if err != nil { msg := fmt.Sprintf("Error parsing config file: %s", err) http.Error(w, msg, 400) log.Errorf(msg) return } target := r.URL.Query().Get("target") if target == "" { http.Error(w, "'target' parameter must be specified", 400) snmpRequestErrors.Inc() return } moduleName := r.URL.Query().Get("module") if moduleName == "" { moduleName = "default" } module, ok := (*cfg)[moduleName] if !ok { http.Error(w, fmt.Sprintf("Unkown module '%s'", moduleName), 400) snmpRequestErrors.Inc() return } log.Debugf("Scraping target '%s' with module '%s'", target, moduleName) start := time.Now() registry := prometheus.NewRegistry() collector := collector{target: target, module: module} registry.MustRegister(collector) // Delegate http serving to Promethues client library, which will call collector.Collect. h := promhttp.HandlerFor(registry, promhttp.HandlerOpts{}) h.ServeHTTP(w, r) duration := float64(time.Since(start).Seconds()) snmpDuration.WithLabelValues(moduleName).Observe(duration) log.Debugf("Scrape of target '%s' with module '%s' took %f seconds", target, moduleName, duration) }
func TestToReader(t *testing.T) { cntVec := prometheus.NewCounterVec( prometheus.CounterOpts{ Name: "name", Help: "docstring", ConstLabels: prometheus.Labels{"constname": "constvalue"}, }, []string{"labelname"}, ) cntVec.WithLabelValues("val1").Inc() cntVec.WithLabelValues("val2").Inc() reg := prometheus.NewRegistry() reg.MustRegister(cntVec) want := `prefix.name.constname.constvalue.labelname.val1 1 1477043 prefix.name.constname.constvalue.labelname.val2 1 1477043 ` mfs, err := reg.Gather() if err != nil { t.Fatalf("error: %v", err) } now := model.Time(1477043083) var buf bytes.Buffer err = writeMetrics(&buf, mfs, "prefix", now) if err != nil { t.Fatalf("error: %v", err) } if got := buf.String(); want != got { t.Fatalf("wanted \n%s\n, got \n%s\n", want, got) } }
func ExampleAddFromGatherer() { registry := prometheus.NewRegistry() registry.MustRegister(completionTime, duration, records) // Note that successTime is not registered at this time. start := time.Now() n, err := performBackup() records.Set(float64(n)) duration.Set(time.Since(start).Seconds()) completionTime.SetToCurrentTime() if err != nil { fmt.Println("DB backup failed:", err) } else { // Only now register successTime. registry.MustRegister(successTime) successTime.SetToCurrentTime() } // AddFromGatherer is used here rather than FromGatherer to not delete a // previously pushed success timestamp in case of a failure of this // backup. if err := push.AddFromGatherer( "db_backup", nil, "http://pushgateway:9091", registry, ); err != nil { fmt.Println("Could not push to Pushgateway:", err) } }
func TestPush(t *testing.T) { reg := prometheus.NewRegistry() cntVec := prometheus.NewCounterVec( prometheus.CounterOpts{ Name: "name", Help: "docstring", ConstLabels: prometheus.Labels{"constname": "constvalue"}, }, []string{"labelname"}, ) cntVec.WithLabelValues("val1").Inc() cntVec.WithLabelValues("val2").Inc() reg.MustRegister(cntVec) host := "localhost" port := ":56789" b, err := NewBridge(&Config{ URL: host + port, Gatherer: reg, Prefix: "prefix", }) if err != nil { t.Fatalf("error creating bridge: %v", err) } nmg, err := newMockGraphite(port) if err != nil { t.Fatalf("error creating mock graphite: %v", err) } defer nmg.Close() err = b.Push() if err != nil { t.Fatalf("error pushing: %v", err) } wants := []string{ "prefix.name.constname.constvalue.labelname.val1 1", "prefix.name.constname.constvalue.labelname.val2 1", } select { case got := <-nmg.readc: for _, want := range wants { matched, err := regexp.MatchString(want, got) if err != nil { t.Fatalf("error pushing: %v", err) } if !matched { t.Fatalf("missing metric:\nno match for %s received by server:\n%s", want, got) } } return case err := <-nmg.errc: t.Fatalf("error reading push: %v", err) case <-time.After(50 * time.Millisecond): t.Fatalf("no result from graphite server") } }
func pushCollectors(job string, grouping map[string]string, url, method string, collectors ...prometheus.Collector) error { r := prometheus.NewRegistry() for _, collector := range collectors { if err := r.Register(collector); err != nil { return err } } return push(job, grouping, url, r, method) }
func TestWriteHistogram(t *testing.T) { histVec := prometheus.NewHistogramVec( prometheus.HistogramOpts{ Name: "name", Help: "docstring", ConstLabels: prometheus.Labels{"constname": "constvalue"}, Buckets: []float64{0.01, 0.02, 0.05, 0.1}, }, []string{"labelname"}, ) histVec.WithLabelValues("val1").Observe(float64(10)) histVec.WithLabelValues("val1").Observe(float64(20)) histVec.WithLabelValues("val1").Observe(float64(30)) histVec.WithLabelValues("val2").Observe(float64(20)) histVec.WithLabelValues("val2").Observe(float64(30)) histVec.WithLabelValues("val2").Observe(float64(40)) reg := prometheus.NewRegistry() reg.MustRegister(histVec) mfs, err := reg.Gather() if err != nil { t.Fatalf("error: %v", err) } now := model.Time(1477043083) var buf bytes.Buffer err = writeMetrics(&buf, mfs, "prefix", now) if err != nil { t.Fatalf("error: %v", err) } want := `prefix.name_bucket.constname.constvalue.labelname.val1.le.0_01 0 1477043 prefix.name_bucket.constname.constvalue.labelname.val1.le.0_02 0 1477043 prefix.name_bucket.constname.constvalue.labelname.val1.le.0_05 0 1477043 prefix.name_bucket.constname.constvalue.labelname.val1.le.0_1 0 1477043 prefix.name_sum.constname.constvalue.labelname.val1 60 1477043 prefix.name_count.constname.constvalue.labelname.val1 3 1477043 prefix.name_bucket.constname.constvalue.labelname.val1.le._Inf 3 1477043 prefix.name_bucket.constname.constvalue.labelname.val2.le.0_01 0 1477043 prefix.name_bucket.constname.constvalue.labelname.val2.le.0_02 0 1477043 prefix.name_bucket.constname.constvalue.labelname.val2.le.0_05 0 1477043 prefix.name_bucket.constname.constvalue.labelname.val2.le.0_1 0 1477043 prefix.name_sum.constname.constvalue.labelname.val2 90 1477043 prefix.name_count.constname.constvalue.labelname.val2 3 1477043 prefix.name_bucket.constname.constvalue.labelname.val2.le._Inf 3 1477043 ` if got := buf.String(); want != got { t.Fatalf("wanted \n%s\n, got \n%s\n", want, got) } }
func TestWriteSummary(t *testing.T) { sumVec := prometheus.NewSummaryVec( prometheus.SummaryOpts{ Name: "name", Help: "docstring", ConstLabels: prometheus.Labels{"constname": "constvalue"}, Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, }, []string{"labelname"}, ) sumVec.WithLabelValues("val1").Observe(float64(10)) sumVec.WithLabelValues("val1").Observe(float64(20)) sumVec.WithLabelValues("val1").Observe(float64(30)) sumVec.WithLabelValues("val2").Observe(float64(20)) sumVec.WithLabelValues("val2").Observe(float64(30)) sumVec.WithLabelValues("val2").Observe(float64(40)) reg := prometheus.NewRegistry() reg.MustRegister(sumVec) mfs, err := reg.Gather() if err != nil { t.Fatalf("error: %v", err) } now := model.Time(1477043083) var buf bytes.Buffer err = writeMetrics(&buf, mfs, "prefix", now) if err != nil { t.Fatalf("error: %v", err) } want := `prefix.name.constname.constvalue.labelname.val1.quantile.0_5 20 1477043 prefix.name.constname.constvalue.labelname.val1.quantile.0_9 30 1477043 prefix.name.constname.constvalue.labelname.val1.quantile.0_99 30 1477043 prefix.name_sum.constname.constvalue.labelname.val1 60 1477043 prefix.name_count.constname.constvalue.labelname.val1 3 1477043 prefix.name.constname.constvalue.labelname.val2.quantile.0_5 30 1477043 prefix.name.constname.constvalue.labelname.val2.quantile.0_9 40 1477043 prefix.name.constname.constvalue.labelname.val2.quantile.0_99 40 1477043 prefix.name_sum.constname.constvalue.labelname.val2 90 1477043 prefix.name_count.constname.constvalue.labelname.val2 3 1477043 ` if got := buf.String(); want != got { t.Fatalf("wanted \n%s\n, got \n%s\n", want, got) } }
func ExampleRegistry() { registry := prometheus.NewRegistry() completionTime := prometheus.NewGauge(prometheus.GaugeOpts{ Name: "db_backup_last_completion_timestamp_seconds", Help: "The timestamp of the last succesful completion of a DB backup.", }) registry.MustRegister(completionTime) completionTime.Set(float64(time.Now().Unix())) if err := push.FromGatherer( "db_backup", push.HostnameGroupingKey(), "http://pushgateway:9091", registry, ); err != nil { fmt.Println("Could not push completion time to Pushgateway:", err) } }
func collect(c prometheus.Collector) ([]byte, error) { r := prometheus.NewRegistry() if err := r.Register(c); err != nil { return nil, err } m, err := r.Gather() if err != nil { return nil, err } var b bytes.Buffer enc := expfmt.NewEncoder(&b, expfmt.FmtText) for _, f := range m { if err := enc.Encode(f); err != nil { return nil, err } } return b.Bytes(), nil }
func TestRegisterWithOrGet(t *testing.T) { // Replace the default registerer just to be sure. This is bad, but this // whole test will go away once RegisterOrGet is removed. oldRegisterer := prometheus.DefaultRegisterer defer func() { prometheus.DefaultRegisterer = oldRegisterer }() prometheus.DefaultRegisterer = prometheus.NewRegistry() original := prometheus.NewCounterVec( prometheus.CounterOpts{ Name: "test", Help: "help", }, []string{"foo", "bar"}, ) equalButNotSame := prometheus.NewCounterVec( prometheus.CounterOpts{ Name: "test", Help: "help", }, []string{"foo", "bar"}, ) var err error if err = prometheus.Register(original); err != nil { t.Fatal(err) } if err = prometheus.Register(equalButNotSame); err == nil { t.Fatal("expected error when registringe equal collector") } if are, ok := err.(prometheus.AlreadyRegisteredError); ok { if are.ExistingCollector != original { t.Error("expected original collector but got something else") } if are.ExistingCollector == equalButNotSame { t.Error("expected original callector but got new one") } } else { t.Error("unexpected error:", err) } }
func TestPush(t *testing.T) { var ( lastMethod string lastBody []byte lastPath string ) host, err := os.Hostname() if err != nil { t.Error(err) } // Fake a Pushgateway that always responds with 202. pgwOK := httptest.NewServer( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { lastMethod = r.Method var err error lastBody, err = ioutil.ReadAll(r.Body) if err != nil { t.Fatal(err) } lastPath = r.URL.EscapedPath() w.Header().Set("Content-Type", `text/plain; charset=utf-8`) w.WriteHeader(http.StatusAccepted) }), ) defer pgwOK.Close() // Fake a Pushgateway that always responds with 500. pgwErr := httptest.NewServer( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Error(w, "fake error", http.StatusInternalServerError) }), ) defer pgwErr.Close() metric1 := prometheus.NewCounter(prometheus.CounterOpts{ Name: "testname1", Help: "testhelp1", }) metric2 := prometheus.NewGauge(prometheus.GaugeOpts{ Name: "testname2", Help: "testhelp2", ConstLabels: prometheus.Labels{"foo": "bar", "dings": "bums"}, }) reg := prometheus.NewRegistry() reg.MustRegister(metric1) reg.MustRegister(metric2) mfs, err := reg.Gather() if err != nil { t.Fatal(err) } buf := &bytes.Buffer{} enc := expfmt.NewEncoder(buf, expfmt.FmtProtoDelim) for _, mf := range mfs { if err := enc.Encode(mf); err != nil { t.Fatal(err) } } wantBody := buf.Bytes() // PushCollectors, all good. if err := Collectors("testjob", HostnameGroupingKey(), pgwOK.URL, metric1, metric2); err != nil { t.Fatal(err) } if lastMethod != "PUT" { t.Error("want method PUT for PushCollectors, got", lastMethod) } if bytes.Compare(lastBody, wantBody) != 0 { t.Errorf("got body %v, want %v", lastBody, wantBody) } if lastPath != "/metrics/job/testjob/instance/"+host { t.Error("unexpected path:", lastPath) } // PushAddCollectors, with nil grouping, all good. if err := AddCollectors("testjob", nil, pgwOK.URL, metric1, metric2); err != nil { t.Fatal(err) } if lastMethod != "POST" { t.Error("want method POST for PushAddCollectors, got", lastMethod) } if bytes.Compare(lastBody, wantBody) != 0 { t.Errorf("got body %v, want %v", lastBody, wantBody) } if lastPath != "/metrics/job/testjob" { t.Error("unexpected path:", lastPath) } // PushCollectors with a broken PGW. if err := Collectors("testjob", nil, pgwErr.URL, metric1, metric2); err == nil { t.Error("push to broken Pushgateway succeeded") } else { if got, want := err.Error(), "unexpected status code 500 while pushing to "+pgwErr.URL+"/metrics/job/testjob: fake error\n"; got != want { t.Errorf("got error %q, want %q", got, want) } } // PushCollectors with invalid grouping or job. if err := Collectors("testjob", map[string]string{"foo": "bums"}, pgwErr.URL, metric1, metric2); err == nil { t.Error("push with grouping contained in metrics succeeded") } if err := Collectors("test/job", nil, pgwErr.URL, metric1, metric2); err == nil { t.Error("push with invalid job value succeeded") } if err := Collectors("testjob", map[string]string{"foo/bar": "bums"}, pgwErr.URL, metric1, metric2); err == nil { t.Error("push with invalid grouping succeeded") } if err := Collectors("testjob", map[string]string{"foo-bar": "bums"}, pgwErr.URL, metric1, metric2); err == nil { t.Error("push with invalid grouping succeeded") } // Push registry, all good. if err := FromGatherer("testjob", HostnameGroupingKey(), pgwOK.URL, reg); err != nil { t.Fatal(err) } if lastMethod != "PUT" { t.Error("want method PUT for Push, got", lastMethod) } if bytes.Compare(lastBody, wantBody) != 0 { t.Errorf("got body %v, want %v", lastBody, wantBody) } // PushAdd registry, all good. if err := AddFromGatherer("testjob", map[string]string{"a": "x", "b": "y"}, pgwOK.URL, reg); err != nil { t.Fatal(err) } if lastMethod != "POST" { t.Error("want method POSTT for PushAdd, got", lastMethod) } if bytes.Compare(lastBody, wantBody) != 0 { t.Errorf("got body %v, want %v", lastBody, wantBody) } if lastPath != "/metrics/job/testjob/a/x/b/y" && lastPath != "/metrics/job/testjob/b/y/a/x" { t.Error("unexpected path:", lastPath) } }
func TestHandlerErrorHandling(t *testing.T) { // Create a registry that collects a MetricFamily with two elements, // another with one, and reports an error. reg := prometheus.NewRegistry() cnt := prometheus.NewCounter(prometheus.CounterOpts{ Name: "the_count", Help: "Ah-ah-ah! Thunder and lightning!", }) reg.MustRegister(cnt) cntVec := prometheus.NewCounterVec( prometheus.CounterOpts{ Name: "name", Help: "docstring", ConstLabels: prometheus.Labels{"constname": "constvalue"}, }, []string{"labelname"}, ) cntVec.WithLabelValues("val1").Inc() cntVec.WithLabelValues("val2").Inc() reg.MustRegister(cntVec) reg.MustRegister(errorCollector{}) logBuf := &bytes.Buffer{} logger := log.New(logBuf, "", 0) writer := httptest.NewRecorder() request, _ := http.NewRequest("GET", "/", nil) request.Header.Add("Accept", "test/plain") errorHandler := HandlerFor(reg, HandlerOpts{ ErrorLog: logger, ErrorHandling: HTTPErrorOnError, }) continueHandler := HandlerFor(reg, HandlerOpts{ ErrorLog: logger, ErrorHandling: ContinueOnError, }) panicHandler := HandlerFor(reg, HandlerOpts{ ErrorLog: logger, ErrorHandling: PanicOnError, }) wantMsg := `error gathering metrics: error collecting metric Desc{fqName: "invalid_metric", help: "not helpful", constLabels: {}, variableLabels: []}: collect error ` wantErrorBody := `An error has occurred during metrics gathering: error collecting metric Desc{fqName: "invalid_metric", help: "not helpful", constLabels: {}, variableLabels: []}: collect error ` wantOKBody := `# HELP name docstring # TYPE name counter name{constname="constvalue",labelname="val1"} 1 name{constname="constvalue",labelname="val2"} 1 # HELP the_count Ah-ah-ah! Thunder and lightning! # TYPE the_count counter the_count 0 ` errorHandler.ServeHTTP(writer, request) if got, want := writer.Code, http.StatusInternalServerError; got != want { t.Errorf("got HTTP status code %d, want %d", got, want) } if got := logBuf.String(); got != wantMsg { t.Errorf("got log message:\n%s\nwant log mesage:\n%s\n", got, wantMsg) } if got := writer.Body.String(); got != wantErrorBody { t.Errorf("got body:\n%s\nwant body:\n%s\n", got, wantErrorBody) } logBuf.Reset() writer.Body.Reset() writer.Code = http.StatusOK continueHandler.ServeHTTP(writer, request) if got, want := writer.Code, http.StatusOK; got != want { t.Errorf("got HTTP status code %d, want %d", got, want) } if got := logBuf.String(); got != wantMsg { t.Errorf("got log message %q, want %q", got, wantMsg) } if got := writer.Body.String(); got != wantOKBody { t.Errorf("got body %q, want %q", got, wantOKBody) } defer func() { if err := recover(); err == nil { t.Error("expected panic from panicHandler") } }() panicHandler.ServeHTTP(writer, request) }
func ExampleGatherers() { reg := prometheus.NewRegistry() temp := prometheus.NewGaugeVec( prometheus.GaugeOpts{ Name: "temperature_kelvin", Help: "Temperature in Kelvin.", }, []string{"location"}, ) reg.MustRegister(temp) temp.WithLabelValues("outside").Set(273.14) temp.WithLabelValues("inside").Set(298.44) var parser expfmt.TextParser text := ` # TYPE humidity_percent gauge # HELP humidity_percent Humidity in %. humidity_percent{location="outside"} 45.4 humidity_percent{location="inside"} 33.2 # TYPE temperature_kelvin gauge # HELP temperature_kelvin Temperature in Kelvin. temperature_kelvin{location="somewhere else"} 4.5 ` parseText := func() ([]*dto.MetricFamily, error) { parsed, err := parser.TextToMetricFamilies(strings.NewReader(text)) if err != nil { return nil, err } var result []*dto.MetricFamily for _, mf := range parsed { result = append(result, mf) } return result, nil } gatherers := prometheus.Gatherers{ reg, prometheus.GathererFunc(parseText), } gathering, err := gatherers.Gather() if err != nil { fmt.Println(err) } out := &bytes.Buffer{} for _, mf := range gathering { if _, err := expfmt.MetricFamilyToText(out, mf); err != nil { panic(err) } } fmt.Print(out.String()) fmt.Println("----------") // Note how the temperature_kelvin metric family has been merged from // different sources. Now try text = ` # TYPE humidity_percent gauge # HELP humidity_percent Humidity in %. humidity_percent{location="outside"} 45.4 humidity_percent{location="inside"} 33.2 # TYPE temperature_kelvin gauge # HELP temperature_kelvin Temperature in Kelvin. # Duplicate metric: temperature_kelvin{location="outside"} 265.3 # Wrong labels: temperature_kelvin 4.5 ` gathering, err = gatherers.Gather() if err != nil { fmt.Println(err) } // Note that still as many metrics as possible are returned: out.Reset() for _, mf := range gathering { if _, err := expfmt.MetricFamilyToText(out, mf); err != nil { panic(err) } } fmt.Print(out.String()) // Output: // # HELP humidity_percent Humidity in %. // # TYPE humidity_percent gauge // humidity_percent{location="inside"} 33.2 // humidity_percent{location="outside"} 45.4 // # HELP temperature_kelvin Temperature in Kelvin. // # TYPE temperature_kelvin gauge // temperature_kelvin{location="inside"} 298.44 // temperature_kelvin{location="outside"} 273.14 // temperature_kelvin{location="somewhere else"} 4.5 // ---------- // 2 error(s) occurred: // * collected metric temperature_kelvin label:<name:"location" value:"outside" > gauge:<value:265.3 > was collected before with the same name and label values // * collected metric temperature_kelvin gauge:<value:4.5 > has label dimensions inconsistent with previously collected metrics in the same metric family // # HELP humidity_percent Humidity in %. // # TYPE humidity_percent gauge // humidity_percent{location="inside"} 33.2 // humidity_percent{location="outside"} 45.4 // # HELP temperature_kelvin Temperature in Kelvin. // # TYPE temperature_kelvin gauge // temperature_kelvin{location="inside"} 298.44 // temperature_kelvin{location="outside"} 273.14 }
func ExampleSummaryVec() { temps := prometheus.NewSummaryVec( prometheus.SummaryOpts{ Name: "pond_temperature_celsius", Help: "The temperature of the frog pond.", Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, }, []string{"species"}, ) // Simulate some observations. for i := 0; i < 1000; i++ { temps.WithLabelValues("litoria-caerulea").Observe(30 + math.Floor(120*math.Sin(float64(i)*0.1))/10) temps.WithLabelValues("lithobates-catesbeianus").Observe(32 + math.Floor(100*math.Cos(float64(i)*0.11))/10) } // Create a Summary without any observations. temps.WithLabelValues("leiopelma-hochstetteri") // Just for demonstration, let's check the state of the summary vector // by registering it with a custom registry and then let it collect the // metrics. reg := prometheus.NewRegistry() reg.MustRegister(temps) metricFamilies, err := reg.Gather() if err != nil || len(metricFamilies) != 1 { panic("unexpected behavior of custom test registry") } fmt.Println(proto.MarshalTextString(metricFamilies[0])) // Output: // name: "pond_temperature_celsius" // help: "The temperature of the frog pond." // type: SUMMARY // metric: < // label: < // name: "species" // value: "leiopelma-hochstetteri" // > // summary: < // sample_count: 0 // sample_sum: 0 // quantile: < // quantile: 0.5 // value: nan // > // quantile: < // quantile: 0.9 // value: nan // > // quantile: < // quantile: 0.99 // value: nan // > // > // > // metric: < // label: < // name: "species" // value: "lithobates-catesbeianus" // > // summary: < // sample_count: 1000 // sample_sum: 31956.100000000017 // quantile: < // quantile: 0.5 // value: 32.4 // > // quantile: < // quantile: 0.9 // value: 41.4 // > // quantile: < // quantile: 0.99 // value: 41.9 // > // > // > // metric: < // label: < // name: "species" // value: "litoria-caerulea" // > // summary: < // sample_count: 1000 // sample_sum: 29969.50000000001 // quantile: < // quantile: 0.5 // value: 31.1 // > // quantile: < // quantile: 0.9 // value: 41.3 // > // quantile: < // quantile: 0.99 // value: 41.9 // > // > // > }