diff --git a/prometheus/promhttp/http.go b/prometheus/promhttp/http.go index 09b8d2fbe..6d2842e2b 100644 --- a/prometheus/promhttp/http.go +++ b/prometheus/promhttp/http.go @@ -379,6 +379,9 @@ type HandlerOpts struct { // NOTE: This feature is experimental and not covered by OpenMetrics or Prometheus // exposition format. ProcessStartTime time.Time + // If true, Counters, Summaries and Histograms will be exposed with additional + // information about the timestamp when a particular timeseries was first initialized. + EnableCreatedTimestamps bool } // gzipAccepted returns whether the client will accept gzip-encoded content. diff --git a/prometheus/promhttp/http_test.go b/prometheus/promhttp/http_test.go index 8ca192748..068d4b417 100644 --- a/prometheus/promhttp/http_test.go +++ b/prometheus/promhttp/http_test.go @@ -17,6 +17,7 @@ import ( "bytes" "errors" "fmt" + "io/ioutil" "log" "net/http" "net/http/httptest" @@ -25,6 +26,7 @@ import ( "time" dto "github.com/prometheus/client_model/go" + "google.golang.org/protobuf/proto" "github.com/prometheus/client_golang/prometheus" ) @@ -331,3 +333,55 @@ func TestHandlerTimeout(t *testing.T) { close(c.Block) // To not leak a goroutine. } + +func TestHandlerWithCreatedTimestamps(t *testing.T) { + reg := prometheus.NewRegistry() + handlerWithCT := HandlerFor(reg, HandlerOpts{EnableCreatedTimestamps: true}) + handlerWithoutCT := HandlerFor(reg, HandlerOpts{EnableCreatedTimestamps: false}) + wWithCT := httptest.NewRecorder() + wWithoutCT := httptest.NewRecorder() + counter := prometheus.NewCounterVec(prometheus.CounterOpts{ + Name: "test", + Help: "help test", + }, []string{"label"}) + + reg.MustRegister(counter) + counter.WithLabelValues("test1").Inc() + counter.WithLabelValues("test2").Add(2) + + request, _ := http.NewRequest("GET", "/", nil) + request.Header.Add("Accept", "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited;q=0.8") + + handlerWithCT.ServeHTTP(wWithCT, request) + rspWithCt := wWithCT.Result() + handlerWithoutCT.ServeHTTP(wWithoutCT, request) + rspWithoutCt := wWithoutCT.Result() + + bodyWithCT, err := ioutil.ReadAll(rspWithCt.Body) + if err != nil { + t.Errorf("Error reading response body: %s", err.Error()) + } + bodyWithoutCT, err := ioutil.ReadAll(rspWithoutCt.Body) + if err != nil { + t.Errorf("Error reading response body: %s", err.Error()) + } + + var mfWithCT, mfWithoutCT dto.MetricFamily + err = proto.Unmarshal(bodyWithCT, &mfWithCT) + if err != nil { + t.Errorf("Error unmarshaling response: %s", err.Error()) + } + + err = proto.Unmarshal(bodyWithoutCT, &mfWithoutCT) + if err != nil { + t.Errorf("Error unmarshaling response: %s", err.Error()) + } + + if mfWithCT.Metric[0].Counter.CreatedTimestamp == nil { + t.Error("expected created timestamp, but there isn't") + } + + if mfWithoutCT.Metric[0].Counter.CreatedTimestamp != nil { + t.Error("Did not expected created timestamp, but there is") + } +}