func (r *registry) Push(job, instance, pushURL, method string) error { if !strings.Contains(pushURL, "://") { pushURL = "http://" + pushURL } pushURL = fmt.Sprintf("%s/metrics/jobs/%s", pushURL, url.QueryEscape(job)) if instance != "" { pushURL += "/instances/" + url.QueryEscape(instance) } buf := r.getBuf() defer r.giveBuf(buf) if err := r.writePB(expfmt.NewEncoder(buf, expfmt.FmtProtoDelim)); err != nil { if r.panicOnCollectError { panic(err) } return err } req, err := http.NewRequest(method, pushURL, buf) if err != nil { return err } req.Header.Set(contentTypeHeader, DelimitedTelemetryContentType) resp, err := http.DefaultClient.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != 202 { return fmt.Errorf("unexpected status code %d while pushing to %s", resp.StatusCode, pushURL) } return nil }
func (fed *Federation) ServeHTTP(w http.ResponseWriter, req *http.Request) { req.ParseForm() metrics := map[model.Fingerprint]metric.Metric{} for _, s := range req.Form["match[]"] { matchers, err := promql.ParseMetricSelector(s) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } for fp, met := range fed.Storage.MetricsForLabelMatchers(matchers...) { metrics[fp] = met } } format := expfmt.Negotiate(req.Header) w.Header().Set("Content-Type", string(format)) enc := expfmt.NewEncoder(w, format) protMetric := &dto.Metric{ Label: []*dto.LabelPair{}, Untyped: &dto.Untyped{}, } protMetricFam := &dto.MetricFamily{ Metric: []*dto.Metric{protMetric}, Type: dto.MetricType_UNTYPED.Enum(), } for fp, met := range metrics { sp := fed.Storage.LastSamplePairForFingerprint(fp) if sp == nil { continue } // Reset label slice. protMetric.Label = protMetric.Label[:0] for ln, lv := range met.Metric { if ln == model.MetricNameLabel { protMetricFam.Name = proto.String(string(lv)) continue } protMetric.Label = append(protMetric.Label, &dto.LabelPair{ Name: proto.String(string(ln)), Value: proto.String(string(lv)), }) } protMetric.TimestampMs = (*int64)(&sp.Timestamp) protMetric.Untyped.Value = (*float64)(&sp.Value) if err := enc.Encode(protMetricFam); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } }
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 (r *registry) ServeHTTP(w http.ResponseWriter, req *http.Request) { contentType := expfmt.Negotiate(req.Header) buf := r.getBuf() defer r.giveBuf(buf) writer, encoding := decorateWriter(req, buf) if err := r.writePB(expfmt.NewEncoder(writer, contentType)); err != nil { if r.panicOnCollectError { panic(err) } http.Error(w, "An error has occurred:\n\n"+err.Error(), http.StatusInternalServerError) return } if closer, ok := writer.(io.Closer); ok { closer.Close() } header := w.Header() header.Set(contentTypeHeader, string(contentType)) header.Set(contentLengthHeader, fmt.Sprint(buf.Len())) if encoding != "" { header.Set(contentEncodingHeader, encoding) } w.Write(buf.Bytes()) }
// UninstrumentedHandler returns an HTTP handler for the DefaultGatherer. // // Deprecated: Use promhttp.Handler instead. See there for further documentation. func UninstrumentedHandler() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { mfs, err := DefaultGatherer.Gather() if err != nil { http.Error(w, "An error has occurred during metrics collection:\n\n"+err.Error(), http.StatusInternalServerError) return } contentType := expfmt.Negotiate(req.Header) buf := getBuf() defer giveBuf(buf) writer, encoding := decorateWriter(req, buf) enc := expfmt.NewEncoder(writer, contentType) var lastErr error for _, mf := range mfs { if err := enc.Encode(mf); err != nil { lastErr = err http.Error(w, "An error has occurred during metrics encoding:\n\n"+err.Error(), http.StatusInternalServerError) return } } if closer, ok := writer.(io.Closer); ok { closer.Close() } if lastErr != nil && buf.Len() == 0 { http.Error(w, "No metrics encoded, last error:\n\n"+err.Error(), http.StatusInternalServerError) return } header := w.Header() header.Set(contentTypeHeader, string(contentType)) header.Set(contentLengthHeader, fmt.Sprint(buf.Len())) if encoding != "" { header.Set(contentEncodingHeader, encoding) } w.Write(buf.Bytes()) }) }
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 (h *Handler) federation(w http.ResponseWriter, req *http.Request) { h.mtx.RLock() defer h.mtx.RUnlock() req.ParseForm() var matcherSets []metric.LabelMatchers for _, s := range req.Form["match[]"] { matchers, err := promql.ParseMetricSelector(s) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } matcherSets = append(matcherSets, matchers) } var ( minTimestamp = h.now().Add(-promql.StalenessDelta) format = expfmt.Negotiate(req.Header) enc = expfmt.NewEncoder(w, format) ) w.Header().Set("Content-Type", string(format)) q, err := h.storage.Querier() if err != nil { federationErrors.Inc() http.Error(w, err.Error(), http.StatusInternalServerError) return } defer q.Close() vector, err := q.LastSampleForLabelMatchers(h.context, minTimestamp, matcherSets...) if err != nil { federationErrors.Inc() http.Error(w, err.Error(), http.StatusInternalServerError) return } sort.Sort(byName(vector)) var ( lastMetricName model.LabelValue protMetricFam *dto.MetricFamily ) for _, s := range vector { nameSeen := false globalUsed := map[model.LabelName]struct{}{} protMetric := &dto.Metric{ Untyped: &dto.Untyped{}, } for ln, lv := range s.Metric { if lv == "" { // No value means unset. Never consider those labels. // This is also important to protect against nameless metrics. continue } if ln == model.MetricNameLabel { nameSeen = true if lv == lastMetricName { // We already have the name in the current MetricFamily, // and we ignore nameless metrics. continue } // Need to start a new MetricFamily. Ship off the old one (if any) before // creating the new one. if protMetricFam != nil { if err := enc.Encode(protMetricFam); err != nil { federationErrors.Inc() log.With("err", err).Error("federation failed") return } } protMetricFam = &dto.MetricFamily{ Type: dto.MetricType_UNTYPED.Enum(), Name: proto.String(string(lv)), } lastMetricName = lv continue } protMetric.Label = append(protMetric.Label, &dto.LabelPair{ Name: proto.String(string(ln)), Value: proto.String(string(lv)), }) if _, ok := h.externalLabels[ln]; ok { globalUsed[ln] = struct{}{} } } if !nameSeen { log.With("metric", s.Metric).Warn("Ignoring nameless metric during federation.") continue } // Attach global labels if they do not exist yet. for ln, lv := range h.externalLabels { if _, ok := globalUsed[ln]; !ok { protMetric.Label = append(protMetric.Label, &dto.LabelPair{ Name: proto.String(string(ln)), Value: proto.String(string(lv)), }) } } protMetric.TimestampMs = proto.Int64(int64(s.Timestamp)) protMetric.Untyped.Value = proto.Float64(float64(s.Value)) protMetricFam.Metric = append(protMetricFam.Metric, protMetric) } // Still have to ship off the last MetricFamily, if any. if protMetricFam != nil { if err := enc.Encode(protMetricFam); err != nil { federationErrors.Inc() log.With("err", err).Error("federation failed") } } }
func testHandler(t testing.TB) { metricVec := prometheus.NewCounterVec( prometheus.CounterOpts{ Name: "name", Help: "docstring", ConstLabels: prometheus.Labels{"constname": "constvalue"}, }, []string{"labelname"}, ) metricVec.WithLabelValues("val1").Inc() metricVec.WithLabelValues("val2").Inc() externalMetricFamily := &dto.MetricFamily{ Name: proto.String("externalname"), Help: proto.String("externaldocstring"), Type: dto.MetricType_COUNTER.Enum(), Metric: []*dto.Metric{ { Label: []*dto.LabelPair{ { Name: proto.String("externalconstname"), Value: proto.String("externalconstvalue"), }, { Name: proto.String("externallabelname"), Value: proto.String("externalval1"), }, }, Counter: &dto.Counter{ Value: proto.Float64(1), }, }, }, } externalBuf := &bytes.Buffer{} enc := expfmt.NewEncoder(externalBuf, expfmt.FmtProtoDelim) if err := enc.Encode(externalMetricFamily); err != nil { t.Fatal(err) } externalMetricFamilyAsBytes := externalBuf.Bytes() externalMetricFamilyAsText := []byte(`# HELP externalname externaldocstring # TYPE externalname counter externalname{externalconstname="externalconstvalue",externallabelname="externalval1"} 1 `) externalMetricFamilyAsProtoText := []byte(`name: "externalname" help: "externaldocstring" type: COUNTER metric: < label: < name: "externalconstname" value: "externalconstvalue" > label: < name: "externallabelname" value: "externalval1" > counter: < value: 1 > > `) externalMetricFamilyAsProtoCompactText := []byte(`name:"externalname" help:"externaldocstring" type:COUNTER metric:<label:<name:"externalconstname" value:"externalconstvalue" > label:<name:"externallabelname" value:"externalval1" > counter:<value:1 > > `) expectedMetricFamily := &dto.MetricFamily{ Name: proto.String("name"), Help: proto.String("docstring"), Type: dto.MetricType_COUNTER.Enum(), Metric: []*dto.Metric{ { Label: []*dto.LabelPair{ { Name: proto.String("constname"), Value: proto.String("constvalue"), }, { Name: proto.String("labelname"), Value: proto.String("val1"), }, }, Counter: &dto.Counter{ Value: proto.Float64(1), }, }, { Label: []*dto.LabelPair{ { Name: proto.String("constname"), Value: proto.String("constvalue"), }, { Name: proto.String("labelname"), Value: proto.String("val2"), }, }, Counter: &dto.Counter{ Value: proto.Float64(1), }, }, }, } buf := &bytes.Buffer{} enc = expfmt.NewEncoder(buf, expfmt.FmtProtoDelim) if err := enc.Encode(expectedMetricFamily); err != nil { t.Fatal(err) } expectedMetricFamilyAsBytes := buf.Bytes() expectedMetricFamilyAsText := []byte(`# HELP name docstring # TYPE name counter name{constname="constvalue",labelname="val1"} 1 name{constname="constvalue",labelname="val2"} 1 `) expectedMetricFamilyAsProtoText := []byte(`name: "name" help: "docstring" type: COUNTER metric: < label: < name: "constname" value: "constvalue" > label: < name: "labelname" value: "val1" > counter: < value: 1 > > metric: < label: < name: "constname" value: "constvalue" > label: < name: "labelname" value: "val2" > counter: < value: 1 > > `) expectedMetricFamilyAsProtoCompactText := []byte(`name:"name" help:"docstring" type:COUNTER metric:<label:<name:"constname" value:"constvalue" > label:<name:"labelname" value:"val1" > counter:<value:1 > > metric:<label:<name:"constname" value:"constvalue" > label:<name:"labelname" value:"val2" > counter:<value:1 > > `) externalMetricFamilyWithSameName := &dto.MetricFamily{ Name: proto.String("name"), Help: proto.String("docstring"), Type: dto.MetricType_COUNTER.Enum(), Metric: []*dto.Metric{ { Label: []*dto.LabelPair{ { Name: proto.String("constname"), Value: proto.String("constvalue"), }, { Name: proto.String("labelname"), Value: proto.String("different_val"), }, }, Counter: &dto.Counter{ Value: proto.Float64(42), }, }, }, } expectedMetricFamilyMergedWithExternalAsProtoCompactText := []byte(`name:"name" help:"docstring" type:COUNTER metric:<label:<name:"constname" value:"constvalue" > label:<name:"labelname" value:"different_val" > counter:<value:42 > > metric:<label:<name:"constname" value:"constvalue" > label:<name:"labelname" value:"val1" > counter:<value:1 > > metric:<label:<name:"constname" value:"constvalue" > label:<name:"labelname" value:"val2" > counter:<value:1 > > `) type output struct { headers map[string]string body []byte } var scenarios = []struct { headers map[string]string out output collector prometheus.Collector externalMF []*dto.MetricFamily }{ { // 0 headers: map[string]string{ "Accept": "foo/bar;q=0.2, dings/bums;q=0.8", }, out: output{ headers: map[string]string{ "Content-Type": `text/plain; version=0.0.4`, }, body: []byte{}, }, }, { // 1 headers: map[string]string{ "Accept": "foo/bar;q=0.2, application/quark;q=0.8", }, out: output{ headers: map[string]string{ "Content-Type": `text/plain; version=0.0.4`, }, body: []byte{}, }, }, { // 2 headers: map[string]string{ "Accept": "foo/bar;q=0.2, application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=bla;q=0.8", }, out: output{ headers: map[string]string{ "Content-Type": `text/plain; version=0.0.4`, }, body: []byte{}, }, }, { // 3 headers: map[string]string{ "Accept": "text/plain;q=0.2, application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited;q=0.8", }, out: output{ headers: map[string]string{ "Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited`, }, body: []byte{}, }, }, { // 4 headers: map[string]string{ "Accept": "application/json", }, out: output{ headers: map[string]string{ "Content-Type": `text/plain; version=0.0.4`, }, body: expectedMetricFamilyAsText, }, collector: metricVec, }, { // 5 headers: map[string]string{ "Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited", }, out: output{ headers: map[string]string{ "Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited`, }, body: expectedMetricFamilyAsBytes, }, collector: metricVec, }, { // 6 headers: map[string]string{ "Accept": "application/json", }, out: output{ headers: map[string]string{ "Content-Type": `text/plain; version=0.0.4`, }, body: externalMetricFamilyAsText, }, externalMF: []*dto.MetricFamily{externalMetricFamily}, }, { // 7 headers: map[string]string{ "Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited", }, out: output{ headers: map[string]string{ "Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited`, }, body: externalMetricFamilyAsBytes, }, externalMF: []*dto.MetricFamily{externalMetricFamily}, }, { // 8 headers: map[string]string{ "Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited", }, out: output{ headers: map[string]string{ "Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited`, }, body: bytes.Join( [][]byte{ externalMetricFamilyAsBytes, expectedMetricFamilyAsBytes, }, []byte{}, ), }, collector: metricVec, externalMF: []*dto.MetricFamily{externalMetricFamily}, }, { // 9 headers: map[string]string{ "Accept": "text/plain", }, out: output{ headers: map[string]string{ "Content-Type": `text/plain; version=0.0.4`, }, body: []byte{}, }, }, { // 10 headers: map[string]string{ "Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=bla;q=0.2, text/plain;q=0.5", }, out: output{ headers: map[string]string{ "Content-Type": `text/plain; version=0.0.4`, }, body: expectedMetricFamilyAsText, }, collector: metricVec, }, { // 11 headers: map[string]string{ "Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=bla;q=0.2, text/plain;q=0.5;version=0.0.4", }, out: output{ headers: map[string]string{ "Content-Type": `text/plain; version=0.0.4`, }, body: bytes.Join( [][]byte{ externalMetricFamilyAsText, expectedMetricFamilyAsText, }, []byte{}, ), }, collector: metricVec, externalMF: []*dto.MetricFamily{externalMetricFamily}, }, { // 12 headers: map[string]string{ "Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited;q=0.2, text/plain;q=0.5;version=0.0.2", }, out: output{ headers: map[string]string{ "Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited`, }, body: bytes.Join( [][]byte{ externalMetricFamilyAsBytes, expectedMetricFamilyAsBytes, }, []byte{}, ), }, collector: metricVec, externalMF: []*dto.MetricFamily{externalMetricFamily}, }, { // 13 headers: map[string]string{ "Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=text;q=0.5, application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited;q=0.4", }, out: output{ headers: map[string]string{ "Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=text`, }, body: bytes.Join( [][]byte{ externalMetricFamilyAsProtoText, expectedMetricFamilyAsProtoText, }, []byte{}, ), }, collector: metricVec, externalMF: []*dto.MetricFamily{externalMetricFamily}, }, { // 14 headers: map[string]string{ "Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=compact-text", }, out: output{ headers: map[string]string{ "Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=compact-text`, }, body: bytes.Join( [][]byte{ externalMetricFamilyAsProtoCompactText, expectedMetricFamilyAsProtoCompactText, }, []byte{}, ), }, collector: metricVec, externalMF: []*dto.MetricFamily{externalMetricFamily}, }, { // 15 headers: map[string]string{ "Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=compact-text", }, out: output{ headers: map[string]string{ "Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=compact-text`, }, body: bytes.Join( [][]byte{ externalMetricFamilyAsProtoCompactText, expectedMetricFamilyMergedWithExternalAsProtoCompactText, }, []byte{}, ), }, collector: metricVec, externalMF: []*dto.MetricFamily{ externalMetricFamily, externalMetricFamilyWithSameName, }, }, } for i, scenario := range scenarios { registry := prometheus.NewPedanticRegistry() gatherer := prometheus.Gatherer(registry) if scenario.externalMF != nil { gatherer = prometheus.Gatherers{ registry, prometheus.GathererFunc(func() ([]*dto.MetricFamily, error) { return scenario.externalMF, nil }), } } if scenario.collector != nil { registry.Register(scenario.collector) } writer := httptest.NewRecorder() handler := prometheus.InstrumentHandler("prometheus", promhttp.HandlerFor(gatherer, promhttp.HandlerOpts{})) request, _ := http.NewRequest("GET", "/", nil) for key, value := range scenario.headers { request.Header.Add(key, value) } handler(writer, request) for key, value := range scenario.out.headers { if writer.HeaderMap.Get(key) != value { t.Errorf( "%d. expected %q for header %q, got %q", i, value, key, writer.Header().Get(key), ) } } if !bytes.Equal(scenario.out.body, writer.Body.Bytes()) { t.Errorf( "%d. expected body:\n%s\ngot body:\n%s\n", i, scenario.out.body, writer.Body.Bytes(), ) } } }
func (h *Handler) federation(w http.ResponseWriter, req *http.Request) { h.mtx.RLock() defer h.mtx.RUnlock() req.ParseForm() metrics := map[model.Fingerprint]metric.Metric{} for _, s := range req.Form["match[]"] { matchers, err := promql.ParseMetricSelector(s) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } for fp, met := range h.storage.MetricsForLabelMatchers(matchers...) { metrics[fp] = met } } format := expfmt.Negotiate(req.Header) w.Header().Set("Content-Type", string(format)) enc := expfmt.NewEncoder(w, format) protMetric := &dto.Metric{ Label: []*dto.LabelPair{}, Untyped: &dto.Untyped{}, } protMetricFam := &dto.MetricFamily{ Metric: []*dto.Metric{protMetric}, Type: dto.MetricType_UNTYPED.Enum(), } for fp, met := range metrics { globalUsed := map[model.LabelName]struct{}{} sp := h.storage.LastSamplePairForFingerprint(fp) if sp == nil { continue } // Reset label slice. protMetric.Label = protMetric.Label[:0] for ln, lv := range met.Metric { if ln == model.MetricNameLabel { protMetricFam.Name = proto.String(string(lv)) continue } protMetric.Label = append(protMetric.Label, &dto.LabelPair{ Name: proto.String(string(ln)), Value: proto.String(string(lv)), }) if _, ok := h.globalLabels[ln]; ok { globalUsed[ln] = struct{}{} } } // Attach global labels if they do not exist yet. for ln, lv := range h.globalLabels { if _, ok := globalUsed[ln]; !ok { protMetric.Label = append(protMetric.Label, &dto.LabelPair{ Name: proto.String(string(ln)), Value: proto.String(string(lv)), }) } } protMetric.TimestampMs = (*int64)(&sp.Timestamp) protMetric.Untyped.Value = (*float64)(&sp.Value) if err := enc.Encode(protMetricFam); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } }
func (h *Handler) federation(w http.ResponseWriter, req *http.Request) { h.mtx.RLock() defer h.mtx.RUnlock() req.ParseForm() fps := map[model.Fingerprint]struct{}{} for _, s := range req.Form["match[]"] { matchers, err := promql.ParseMetricSelector(s) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } for fp := range h.storage.MetricsForLabelMatchers( model.Now().Add(-promql.StalenessDelta), model.Latest, matchers..., ) { fps[fp] = struct{}{} } } var ( minTimestamp = model.Now().Add(-promql.StalenessDelta) format = expfmt.Negotiate(req.Header) enc = expfmt.NewEncoder(w, format) ) w.Header().Set("Content-Type", string(format)) protMetric := &dto.Metric{ Label: []*dto.LabelPair{}, Untyped: &dto.Untyped{}, } protMetricFam := &dto.MetricFamily{ Metric: []*dto.Metric{protMetric}, Type: dto.MetricType_UNTYPED.Enum(), } for fp := range fps { globalUsed := map[model.LabelName]struct{}{} s := h.storage.LastSampleForFingerprint(fp) // Discard if sample does not exist or lays before the staleness interval. if s.Timestamp.Before(minTimestamp) { continue } // Reset label slice. protMetric.Label = protMetric.Label[:0] for ln, lv := range s.Metric { if ln == model.MetricNameLabel { protMetricFam.Name = proto.String(string(lv)) continue } protMetric.Label = append(protMetric.Label, &dto.LabelPair{ Name: proto.String(string(ln)), Value: proto.String(string(lv)), }) if _, ok := h.externalLabels[ln]; ok { globalUsed[ln] = struct{}{} } } // Attach global labels if they do not exist yet. for ln, lv := range h.externalLabels { if _, ok := globalUsed[ln]; !ok { protMetric.Label = append(protMetric.Label, &dto.LabelPair{ Name: proto.String(string(ln)), Value: proto.String(string(lv)), }) } } protMetric.TimestampMs = proto.Int64(int64(s.Timestamp)) protMetric.Untyped.Value = proto.Float64(float64(s.Value)) if err := enc.Encode(protMetricFam); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } }
func push(job string, grouping map[string]string, pushURL string, g prometheus.Gatherer, method string) error { if !strings.Contains(pushURL, "://") { pushURL = "http://" + pushURL } if strings.HasSuffix(pushURL, "/") { pushURL = pushURL[:len(pushURL)-1] } if strings.Contains(job, "/") { return fmt.Errorf("job contains '/': %s", job) } urlComponents := []string{url.QueryEscape(job)} for ln, lv := range grouping { if !model.LabelNameRE.MatchString(ln) { return fmt.Errorf("grouping label has invalid name: %s", ln) } if strings.Contains(lv, "/") { return fmt.Errorf("value of grouping label %s contains '/': %s", ln, lv) } urlComponents = append(urlComponents, ln, lv) } pushURL = fmt.Sprintf("%s/metrics/job/%s", pushURL, strings.Join(urlComponents, "/")) mfs, err := g.Gather() if err != nil { return err } buf := &bytes.Buffer{} enc := expfmt.NewEncoder(buf, expfmt.FmtProtoDelim) // Check for pre-existing grouping labels: for _, mf := range mfs { for _, m := range mf.GetMetric() { for _, l := range m.GetLabel() { if l.GetName() == "job" { return fmt.Errorf("pushed metric %s (%s) already contains a job label", mf.GetName(), m) } if _, ok := grouping[l.GetName()]; ok { return fmt.Errorf( "pushed metric %s (%s) already contains grouping label %s", mf.GetName(), m, l.GetName(), ) } } } enc.Encode(mf) } req, err := http.NewRequest(method, pushURL, buf) if err != nil { return err } req.Header.Set(contentTypeHeader, string(expfmt.FmtProtoDelim)) resp, err := http.DefaultClient.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != 202 { body, _ := ioutil.ReadAll(resp.Body) // Ignore any further error as this is for an error message only. return fmt.Errorf("unexpected status code %d while pushing to %s: %s", resp.StatusCode, pushURL, body) } return nil }
// HandlerFor returns an http.Handler for the provided Gatherer. The behavior // of the Handler is defined by the provided HandlerOpts. func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { mfs, err := reg.Gather() if err != nil { if opts.ErrorLog != nil { opts.ErrorLog.Println("error gathering metrics:", err) } switch opts.ErrorHandling { case PanicOnError: panic(err) case ContinueOnError: if len(mfs) == 0 { http.Error(w, "No metrics gathered, last error:\n\n"+err.Error(), http.StatusInternalServerError) return } case HTTPErrorOnError: http.Error(w, "An error has occurred during metrics gathering:\n\n"+err.Error(), http.StatusInternalServerError) return } } contentType := expfmt.Negotiate(req.Header) buf := getBuf() defer giveBuf(buf) writer, encoding := decorateWriter(req, buf, opts.DisableCompression) enc := expfmt.NewEncoder(writer, contentType) var lastErr error for _, mf := range mfs { if err := enc.Encode(mf); err != nil { lastErr = err if opts.ErrorLog != nil { opts.ErrorLog.Println("error encoding metric family:", err) } switch opts.ErrorHandling { case PanicOnError: panic(err) case ContinueOnError: // Handled later. case HTTPErrorOnError: http.Error(w, "An error has occurred during metrics encoding:\n\n"+err.Error(), http.StatusInternalServerError) return } } } if closer, ok := writer.(io.Closer); ok { closer.Close() } if lastErr != nil && buf.Len() == 0 { http.Error(w, "No metrics encoded, last error:\n\n"+err.Error(), http.StatusInternalServerError) return } header := w.Header() header.Set(contentTypeHeader, string(contentType)) header.Set(contentLengthHeader, fmt.Sprint(buf.Len())) if encoding != "" { header.Set(contentEncodingHeader, encoding) } w.Write(buf.Bytes()) // TODO(beorn7): Consider streaming serving of metrics. }) }