├── VERSION ├── prometheus ├── .gitignore ├── README.md ├── desc_test.go ├── fnv.go ├── value_test.go ├── metric_test.go ├── promhttp │ ├── delegator_pre_1_8.go │ ├── instrument_client.go │ ├── instrument_client_1_8.go │ ├── instrument_client_1_8_test.go │ ├── delegator_1_8.go │ ├── delegator.go │ └── http_test.go ├── push │ ├── examples_test.go │ ├── example_add_from_gatherer_test.go │ ├── push_test.go │ ├── deprecated.go │ └── push.go ├── example_timer_test.go ├── labels.go ├── untyped.go ├── example_timer_gauge_test.go ├── timer.go ├── process_collector_test.go ├── observer.go ├── example_timer_complex_test.go ├── go_collector_test.go ├── expvar_collector_test.go ├── collector.go ├── process_collector.go ├── expvar_collector.go ├── example_clustermanager_test.go ├── timer_test.go ├── http_test.go ├── benchmark_test.go ├── value.go ├── gauge_test.go ├── metric.go ├── counter_test.go ├── desc.go ├── graphite │ ├── bridge.go │ └── bridge_test.go ├── histogram_test.go └── go_collector.go ├── MAINTAINERS.md ├── .travis.yml ├── ISSUE_TEMPLATE.md ├── .gitignore ├── NOTICE ├── CONTRIBUTING.md ├── Dockerfile ├── examples ├── simple │ └── main.go └── random │ └── main.go ├── api ├── client_test.go └── client.go ├── README.md └── CHANGELOG.md /VERSION: -------------------------------------------------------------------------------- 1 | 0.8.0 2 | -------------------------------------------------------------------------------- /prometheus/.gitignore: -------------------------------------------------------------------------------- 1 | command-line-arguments.test 2 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | * Krasi Georgiev for `api/...` 2 | * Björn Rabenstein for everything else 3 | -------------------------------------------------------------------------------- /prometheus/README.md: -------------------------------------------------------------------------------- 1 | See [![go-doc](https://godoc.org/github.com/prometheus/client_golang/prometheus?status.svg)](https://godoc.org/github.com/prometheus/client_golang/prometheus). 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: go 3 | 4 | go: 5 | - 1.7.x # See README.md for current minimum version. 6 | - 1.8.x 7 | - 1.9.x 8 | - 1.10.x 9 | 10 | script: 11 | - go test -short ./... 12 | -------------------------------------------------------------------------------- /prometheus/desc_test.go: -------------------------------------------------------------------------------- 1 | package prometheus 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestNewDescInvalidLabelValues(t *testing.T) { 8 | desc := NewDesc( 9 | "sample_label", 10 | "sample label", 11 | nil, 12 | Labels{"a": "\xFF"}, 13 | ) 14 | if desc.err == nil { 15 | t.Errorf("NewDesc: expected error because: %s", desc.err) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Examples 11 | /examples/simple/simple 12 | /examples/random/random 13 | 14 | # Architecture specific extensions/prefixes 15 | *.[568vq] 16 | [568vq].out 17 | 18 | *.cgo1.go 19 | *.cgo2.c 20 | _cgo_defun.c 21 | _cgo_gotypes.go 22 | _cgo_export.* 23 | 24 | _testmain.go 25 | 26 | *.exe 27 | 28 | *~ 29 | *# 30 | .build 31 | -------------------------------------------------------------------------------- /prometheus/fnv.go: -------------------------------------------------------------------------------- 1 | package prometheus 2 | 3 | // Inline and byte-free variant of hash/fnv's fnv64a. 4 | 5 | const ( 6 | offset64 = 14695981039346656037 7 | prime64 = 1099511628211 8 | ) 9 | 10 | // hashNew initializies a new fnv64a hash value. 11 | func hashNew() uint64 { 12 | return offset64 13 | } 14 | 15 | // hashAdd adds a string to a fnv64a hash value, returning the updated hash. 16 | func hashAdd(h uint64, s string) uint64 { 17 | for i := 0; i < len(s); i++ { 18 | h ^= uint64(s[i]) 19 | h *= prime64 20 | } 21 | return h 22 | } 23 | 24 | // hashAddByte adds a byte to a fnv64a hash value, returning the updated hash. 25 | func hashAddByte(h uint64, b byte) uint64 { 26 | h ^= uint64(b) 27 | h *= prime64 28 | return h 29 | } 30 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Prometheus instrumentation library for Go applications 2 | Copyright 2012-2015 The Prometheus Authors 3 | 4 | This product includes software developed at 5 | SoundCloud Ltd. (http://soundcloud.com/). 6 | 7 | 8 | The following components are included in this product: 9 | 10 | perks - a fork of https://github.com/bmizerany/perks 11 | https://github.com/beorn7/perks 12 | Copyright 2013-2015 Blake Mizerany, Björn Rabenstein 13 | See https://github.com/beorn7/perks/blob/master/README.md for license details. 14 | 15 | Go support for Protocol Buffers - Google's data interchange format 16 | http://github.com/golang/protobuf/ 17 | Copyright 2010 The Go Authors 18 | See source code for license details. 19 | 20 | Support for streaming Protocol Buffer messages for the Go language (golang). 21 | https://github.com/matttproud/golang_protobuf_extensions 22 | Copyright 2013 Matt T. Proud 23 | Licensed under the Apache License, Version 2.0 24 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Prometheus uses GitHub to manage reviews of pull requests. 4 | 5 | * If you have a trivial fix or improvement, go ahead and create a pull request, 6 | addressing (with `@...`) the maintainer of this repository (see 7 | [MAINTAINERS.md](MAINTAINERS.md)) in the description of the pull request. 8 | 9 | * If you plan to do something more involved, first discuss your ideas 10 | on our [mailing list](https://groups.google.com/forum/?fromgroups#!forum/prometheus-developers). 11 | This will avoid unnecessary work and surely give you and us a good deal 12 | of inspiration. 13 | 14 | * Relevant coding style guidelines are the [Go Code Review 15 | Comments](https://code.google.com/p/go-wiki/wiki/CodeReviewComments) 16 | and the _Formatting and style_ section of Peter Bourgon's [Go: Best 17 | Practices for Production 18 | Environments](http://peter.bourgon.org/go-in-production/#formatting-and-style). 19 | 20 | * Be sure to sign off on the [DCO](https://github.com/probot/dco#how-it-works) 21 | -------------------------------------------------------------------------------- /prometheus/value_test.go: -------------------------------------------------------------------------------- 1 | package prometheus 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestNewConstMetricInvalidLabelValues(t *testing.T) { 9 | testCases := []struct { 10 | desc string 11 | labels Labels 12 | }{ 13 | { 14 | desc: "non utf8 label value", 15 | labels: Labels{"a": "\xFF"}, 16 | }, 17 | { 18 | desc: "not enough label values", 19 | labels: Labels{}, 20 | }, 21 | { 22 | desc: "too many label values", 23 | labels: Labels{"a": "1", "b": "2"}, 24 | }, 25 | } 26 | 27 | for _, test := range testCases { 28 | metricDesc := NewDesc( 29 | "sample_value", 30 | "sample value", 31 | []string{"a"}, 32 | Labels{}, 33 | ) 34 | 35 | expectPanic(t, func() { 36 | MustNewConstMetric(metricDesc, CounterValue, 0.3, "\xFF") 37 | }, fmt.Sprintf("WithLabelValues: expected panic because: %s", test.desc)) 38 | 39 | if _, err := NewConstMetric(metricDesc, CounterValue, 0.3, "\xFF"); err == nil { 40 | t.Errorf("NewConstMetric: expected error because: %s", test.desc) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # This Dockerfile builds an image for a client_golang example. 2 | # 3 | # Use as (from the root for the client_golang repository): 4 | # docker build -f examples/$name/Dockerfile -t prometheus/golang-example-$name . 5 | 6 | # Builder image, where we build the example. 7 | FROM golang:1 AS builder 8 | WORKDIR /go/src/github.com/prometheus/client_golang 9 | COPY . . 10 | WORKDIR /go/src/github.com/prometheus/client_golang/prometheus 11 | RUN go get -d 12 | WORKDIR /go/src/github.com/prometheus/client_golang/examples/random 13 | RUN CGO_ENABLED=0 GOOS=linux go build -a -tags netgo -ldflags '-w' 14 | WORKDIR /go/src/github.com/prometheus/client_golang/examples/simple 15 | RUN CGO_ENABLED=0 GOOS=linux go build -a -tags netgo -ldflags '-w' 16 | 17 | # Final image. 18 | FROM prom/busybox 19 | LABEL maintainer "The Prometheus Authors " 20 | COPY --from=builder /go/src/github.com/prometheus/client_golang/examples/random \ 21 | /go/src/github.com/prometheus/client_golang/examples/simple ./ 22 | EXPOSE 8080 23 | CMD echo Please run an example. Either /random or /simple 24 | -------------------------------------------------------------------------------- /examples/simple/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | // A minimal example of how to include Prometheus instrumentation. 15 | package main 16 | 17 | import ( 18 | "flag" 19 | "log" 20 | "net/http" 21 | 22 | "github.com/prometheus/client_golang/prometheus/promhttp" 23 | ) 24 | 25 | var addr = flag.String("listen-address", ":8080", "The address to listen on for HTTP requests.") 26 | 27 | func main() { 28 | flag.Parse() 29 | http.Handle("/metrics", promhttp.Handler()) 30 | log.Fatal(http.ListenAndServe(*addr, nil)) 31 | } 32 | -------------------------------------------------------------------------------- /prometheus/metric_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package prometheus 15 | 16 | import "testing" 17 | 18 | func TestBuildFQName(t *testing.T) { 19 | scenarios := []struct{ namespace, subsystem, name, result string }{ 20 | {"a", "b", "c", "a_b_c"}, 21 | {"", "b", "c", "b_c"}, 22 | {"a", "", "c", "a_c"}, 23 | {"", "", "c", "c"}, 24 | {"a", "b", "", ""}, 25 | {"a", "", "", ""}, 26 | {"", "b", "", ""}, 27 | {" ", "", "", ""}, 28 | } 29 | 30 | for i, s := range scenarios { 31 | if want, got := s.result, BuildFQName(s.namespace, s.subsystem, s.name); want != got { 32 | t.Errorf("%d. want %s, got %s", i, want, got) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /prometheus/promhttp/delegator_pre_1_8.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | // +build !go1.8 15 | 16 | package promhttp 17 | 18 | import ( 19 | "io" 20 | "net/http" 21 | ) 22 | 23 | func newDelegator(w http.ResponseWriter, observeWriteHeaderFunc func(int)) delegator { 24 | d := &responseWriterDelegator{ 25 | ResponseWriter: w, 26 | observeWriteHeader: observeWriteHeaderFunc, 27 | } 28 | 29 | id := 0 30 | if _, ok := w.(http.CloseNotifier); ok { 31 | id += closeNotifier 32 | } 33 | if _, ok := w.(http.Flusher); ok { 34 | id += flusher 35 | } 36 | if _, ok := w.(http.Hijacker); ok { 37 | id += hijacker 38 | } 39 | if _, ok := w.(io.ReaderFrom); ok { 40 | id += readerFrom 41 | } 42 | 43 | return pickDelegator[id](d) 44 | } 45 | -------------------------------------------------------------------------------- /prometheus/push/examples_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package push_test 15 | 16 | import ( 17 | "fmt" 18 | 19 | "github.com/prometheus/client_golang/prometheus" 20 | "github.com/prometheus/client_golang/prometheus/push" 21 | ) 22 | 23 | func ExamplePusher_Push() { 24 | completionTime := prometheus.NewGauge(prometheus.GaugeOpts{ 25 | Name: "db_backup_last_completion_timestamp_seconds", 26 | Help: "The timestamp of the last successful completion of a DB backup.", 27 | }) 28 | completionTime.SetToCurrentTime() 29 | if err := push.New("http://pushgateway:9091", "db_backup"). 30 | Collector(completionTime). 31 | Grouping("db", "customers"). 32 | Push(); err != nil { 33 | fmt.Println("Could not push completion time to Pushgateway:", err) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /prometheus/example_timer_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package prometheus_test 15 | 16 | import ( 17 | "math/rand" 18 | "time" 19 | 20 | "github.com/prometheus/client_golang/prometheus" 21 | ) 22 | 23 | var ( 24 | requestDuration = prometheus.NewHistogram(prometheus.HistogramOpts{ 25 | Name: "example_request_duration_seconds", 26 | Help: "Histogram for the runtime of a simple example function.", 27 | Buckets: prometheus.LinearBuckets(0.01, 0.01, 10), 28 | }) 29 | ) 30 | 31 | func ExampleTimer() { 32 | // timer times this example function. It uses a Histogram, but a Summary 33 | // would also work, as both implement Observer. Check out 34 | // https://prometheus.io/docs/practices/histograms/ for differences. 35 | timer := prometheus.NewTimer(requestDuration) 36 | defer timer.ObserveDuration() 37 | 38 | // Do something here that takes time. 39 | time.Sleep(time.Duration(rand.NormFloat64()*10000+50000) * time.Microsecond) 40 | } 41 | -------------------------------------------------------------------------------- /prometheus/labels.go: -------------------------------------------------------------------------------- 1 | package prometheus 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strings" 7 | "unicode/utf8" 8 | 9 | "github.com/prometheus/common/model" 10 | ) 11 | 12 | // Labels represents a collection of label name -> value mappings. This type is 13 | // commonly used with the With(Labels) and GetMetricWith(Labels) methods of 14 | // metric vector Collectors, e.g.: 15 | // myVec.With(Labels{"code": "404", "method": "GET"}).Add(42) 16 | // 17 | // The other use-case is the specification of constant label pairs in Opts or to 18 | // create a Desc. 19 | type Labels map[string]string 20 | 21 | // reservedLabelPrefix is a prefix which is not legal in user-supplied 22 | // label names. 23 | const reservedLabelPrefix = "__" 24 | 25 | var errInconsistentCardinality = errors.New("inconsistent label cardinality") 26 | 27 | func validateValuesInLabels(labels Labels, expectedNumberOfValues int) error { 28 | if len(labels) != expectedNumberOfValues { 29 | return errInconsistentCardinality 30 | } 31 | 32 | for name, val := range labels { 33 | if !utf8.ValidString(val) { 34 | return fmt.Errorf("label %s: value %q is not valid UTF-8", name, val) 35 | } 36 | } 37 | 38 | return nil 39 | } 40 | 41 | func validateLabelValues(vals []string, expectedNumberOfValues int) error { 42 | if len(vals) != expectedNumberOfValues { 43 | return errInconsistentCardinality 44 | } 45 | 46 | for _, val := range vals { 47 | if !utf8.ValidString(val) { 48 | return fmt.Errorf("label value %q is not valid UTF-8", val) 49 | } 50 | } 51 | 52 | return nil 53 | } 54 | 55 | func checkLabelName(l string) bool { 56 | return model.LabelName(l).IsValid() && !strings.HasPrefix(l, reservedLabelPrefix) 57 | } 58 | -------------------------------------------------------------------------------- /prometheus/untyped.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package prometheus 15 | 16 | // UntypedOpts is an alias for Opts. See there for doc comments. 17 | type UntypedOpts Opts 18 | 19 | // UntypedFunc works like GaugeFunc but the collected metric is of type 20 | // "Untyped". UntypedFunc is useful to mirror an external metric of unknown 21 | // type. 22 | // 23 | // To create UntypedFunc instances, use NewUntypedFunc. 24 | type UntypedFunc interface { 25 | Metric 26 | Collector 27 | } 28 | 29 | // NewUntypedFunc creates a new UntypedFunc based on the provided 30 | // UntypedOpts. The value reported is determined by calling the given function 31 | // from within the Write method. Take into account that metric collection may 32 | // happen concurrently. If that results in concurrent calls to Write, like in 33 | // the case where an UntypedFunc is directly registered with Prometheus, the 34 | // provided function must be concurrency-safe. 35 | func NewUntypedFunc(opts UntypedOpts, function func() float64) UntypedFunc { 36 | return newValueFunc(NewDesc( 37 | BuildFQName(opts.Namespace, opts.Subsystem, opts.Name), 38 | opts.Help, 39 | nil, 40 | opts.ConstLabels, 41 | ), UntypedValue, function) 42 | } 43 | -------------------------------------------------------------------------------- /prometheus/example_timer_gauge_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package prometheus_test 15 | 16 | import ( 17 | "os" 18 | 19 | "github.com/prometheus/client_golang/prometheus" 20 | ) 21 | 22 | var ( 23 | // If a function is called rarely (i.e. not more often than scrapes 24 | // happen) or ideally only once (like in a batch job), it can make sense 25 | // to use a Gauge for timing the function call. For timing a batch job 26 | // and pushing the result to a Pushgateway, see also the comprehensive 27 | // example in the push package. 28 | funcDuration = prometheus.NewGauge(prometheus.GaugeOpts{ 29 | Name: "example_function_duration_seconds", 30 | Help: "Duration of the last call of an example function.", 31 | }) 32 | ) 33 | 34 | func run() error { 35 | // The Set method of the Gauge is used to observe the duration. 36 | timer := prometheus.NewTimer(prometheus.ObserverFunc(funcDuration.Set)) 37 | defer timer.ObserveDuration() 38 | 39 | // Do something. Return errors as encountered. The use of 'defer' above 40 | // makes sure the function is still timed properly. 41 | return nil 42 | } 43 | 44 | func ExampleTimer_gauge() { 45 | if err := run(); err != nil { 46 | os.Exit(1) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /prometheus/timer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package prometheus 15 | 16 | import "time" 17 | 18 | // Timer is a helper type to time functions. Use NewTimer to create new 19 | // instances. 20 | type Timer struct { 21 | begin time.Time 22 | observer Observer 23 | } 24 | 25 | // NewTimer creates a new Timer. The provided Observer is used to observe a 26 | // duration in seconds. Timer is usually used to time a function call in the 27 | // following way: 28 | // func TimeMe() { 29 | // timer := NewTimer(myHistogram) 30 | // defer timer.ObserveDuration() 31 | // // Do actual work. 32 | // } 33 | func NewTimer(o Observer) *Timer { 34 | return &Timer{ 35 | begin: time.Now(), 36 | observer: o, 37 | } 38 | } 39 | 40 | // ObserveDuration records the duration passed since the Timer was created with 41 | // NewTimer. It calls the Observe method of the Observer provided during 42 | // construction with the duration in seconds as an argument. ObserveDuration is 43 | // usually called with a defer statement. 44 | // 45 | // Note that this method is only guaranteed to never observe negative durations 46 | // if used with Go1.9+. 47 | func (t *Timer) ObserveDuration() { 48 | if t.observer != nil { 49 | t.observer.Observe(time.Since(t.begin).Seconds()) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /prometheus/process_collector_test.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package prometheus 4 | 5 | import ( 6 | "bytes" 7 | "os" 8 | "regexp" 9 | "testing" 10 | 11 | "github.com/prometheus/common/expfmt" 12 | "github.com/prometheus/procfs" 13 | ) 14 | 15 | func TestProcessCollector(t *testing.T) { 16 | if _, err := procfs.Self(); err != nil { 17 | t.Skipf("skipping TestProcessCollector, procfs not available: %s", err) 18 | } 19 | 20 | registry := NewRegistry() 21 | if err := registry.Register(NewProcessCollector(os.Getpid(), "")); err != nil { 22 | t.Fatal(err) 23 | } 24 | if err := registry.Register(NewProcessCollectorPIDFn( 25 | func() (int, error) { return os.Getpid(), nil }, "foobar"), 26 | ); err != nil { 27 | t.Fatal(err) 28 | } 29 | 30 | mfs, err := registry.Gather() 31 | if err != nil { 32 | t.Fatal(err) 33 | } 34 | 35 | var buf bytes.Buffer 36 | for _, mf := range mfs { 37 | if _, err := expfmt.MetricFamilyToText(&buf, mf); err != nil { 38 | t.Fatal(err) 39 | } 40 | } 41 | 42 | for _, re := range []*regexp.Regexp{ 43 | regexp.MustCompile("\nprocess_cpu_seconds_total [0-9]"), 44 | regexp.MustCompile("\nprocess_max_fds [1-9]"), 45 | regexp.MustCompile("\nprocess_open_fds [1-9]"), 46 | regexp.MustCompile("\nprocess_virtual_memory_bytes [1-9]"), 47 | regexp.MustCompile("\nprocess_resident_memory_bytes [1-9]"), 48 | regexp.MustCompile("\nprocess_start_time_seconds [0-9.]{10,}"), 49 | regexp.MustCompile("\nfoobar_process_cpu_seconds_total [0-9]"), 50 | regexp.MustCompile("\nfoobar_process_max_fds [1-9]"), 51 | regexp.MustCompile("\nfoobar_process_open_fds [1-9]"), 52 | regexp.MustCompile("\nfoobar_process_virtual_memory_bytes [1-9]"), 53 | regexp.MustCompile("\nfoobar_process_resident_memory_bytes [1-9]"), 54 | regexp.MustCompile("\nfoobar_process_start_time_seconds [0-9.]{10,}"), 55 | } { 56 | if !re.Match(buf.Bytes()) { 57 | t.Errorf("want body to match %s\n%s", re, buf.String()) 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /prometheus/observer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package prometheus 15 | 16 | // Observer is the interface that wraps the Observe method, which is used by 17 | // Histogram and Summary to add observations. 18 | type Observer interface { 19 | Observe(float64) 20 | } 21 | 22 | // The ObserverFunc type is an adapter to allow the use of ordinary 23 | // functions as Observers. If f is a function with the appropriate 24 | // signature, ObserverFunc(f) is an Observer that calls f. 25 | // 26 | // This adapter is usually used in connection with the Timer type, and there are 27 | // two general use cases: 28 | // 29 | // The most common one is to use a Gauge as the Observer for a Timer. 30 | // See the "Gauge" Timer example. 31 | // 32 | // The more advanced use case is to create a function that dynamically decides 33 | // which Observer to use for observing the duration. See the "Complex" Timer 34 | // example. 35 | type ObserverFunc func(float64) 36 | 37 | // Observe calls f(value). It implements Observer. 38 | func (f ObserverFunc) Observe(value float64) { 39 | f(value) 40 | } 41 | 42 | // ObserverVec is an interface implemented by `HistogramVec` and `SummaryVec`. 43 | type ObserverVec interface { 44 | GetMetricWith(Labels) (Observer, error) 45 | GetMetricWithLabelValues(lvs ...string) (Observer, error) 46 | With(Labels) Observer 47 | WithLabelValues(...string) Observer 48 | CurryWith(Labels) (ObserverVec, error) 49 | MustCurryWith(Labels) ObserverVec 50 | 51 | Collector 52 | } 53 | -------------------------------------------------------------------------------- /prometheus/example_timer_complex_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package prometheus_test 15 | 16 | import ( 17 | "net/http" 18 | 19 | "github.com/prometheus/client_golang/prometheus" 20 | ) 21 | 22 | var ( 23 | // apiRequestDuration tracks the duration separate for each HTTP status 24 | // class (1xx, 2xx, ...). This creates a fair amount of time series on 25 | // the Prometheus server. Usually, you would track the duration of 26 | // serving HTTP request without partitioning by outcome. Do something 27 | // like this only if needed. Also note how only status classes are 28 | // tracked, not every single status code. The latter would create an 29 | // even larger amount of time series. Request counters partitioned by 30 | // status code are usually OK as each counter only creates one time 31 | // series. Histograms are way more expensive, so partition with care and 32 | // only where you really need separate latency tracking. Partitioning by 33 | // status class is only an example. In concrete cases, other partitions 34 | // might make more sense. 35 | apiRequestDuration = prometheus.NewHistogramVec( 36 | prometheus.HistogramOpts{ 37 | Name: "api_request_duration_seconds", 38 | Help: "Histogram for the request duration of the public API, partitioned by status class.", 39 | Buckets: prometheus.ExponentialBuckets(0.1, 1.5, 5), 40 | }, 41 | []string{"status_class"}, 42 | ) 43 | ) 44 | 45 | func handler(w http.ResponseWriter, r *http.Request) { 46 | status := http.StatusOK 47 | // The ObserverFunc gets called by the deferred ObserveDuration and 48 | // decides which Histogram's Observe method is called. 49 | timer := prometheus.NewTimer(prometheus.ObserverFunc(func(v float64) { 50 | switch { 51 | case status >= 500: // Server error. 52 | apiRequestDuration.WithLabelValues("5xx").Observe(v) 53 | case status >= 400: // Client error. 54 | apiRequestDuration.WithLabelValues("4xx").Observe(v) 55 | case status >= 300: // Redirection. 56 | apiRequestDuration.WithLabelValues("3xx").Observe(v) 57 | case status >= 200: // Success. 58 | apiRequestDuration.WithLabelValues("2xx").Observe(v) 59 | default: // Informational. 60 | apiRequestDuration.WithLabelValues("1xx").Observe(v) 61 | } 62 | })) 63 | defer timer.ObserveDuration() 64 | 65 | // Handle the request. Set status accordingly. 66 | // ... 67 | } 68 | 69 | func ExampleTimer_complex() { 70 | http.HandleFunc("/api", handler) 71 | } 72 | -------------------------------------------------------------------------------- /prometheus/push/example_add_from_gatherer_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package push_test 15 | 16 | import ( 17 | "fmt" 18 | "time" 19 | 20 | "github.com/prometheus/client_golang/prometheus" 21 | "github.com/prometheus/client_golang/prometheus/push" 22 | ) 23 | 24 | var ( 25 | completionTime = prometheus.NewGauge(prometheus.GaugeOpts{ 26 | Name: "db_backup_last_completion_timestamp_seconds", 27 | Help: "The timestamp of the last completion of a DB backup, successful or not.", 28 | }) 29 | successTime = prometheus.NewGauge(prometheus.GaugeOpts{ 30 | Name: "db_backup_last_success_timestamp_seconds", 31 | Help: "The timestamp of the last successful completion of a DB backup.", 32 | }) 33 | duration = prometheus.NewGauge(prometheus.GaugeOpts{ 34 | Name: "db_backup_duration_seconds", 35 | Help: "The duration of the last DB backup in seconds.", 36 | }) 37 | records = prometheus.NewGauge(prometheus.GaugeOpts{ 38 | Name: "db_backup_records_processed", 39 | Help: "The number of records processed in the last DB backup.", 40 | }) 41 | ) 42 | 43 | func performBackup() (int, error) { 44 | // Perform the backup and return the number of backed up records and any 45 | // applicable error. 46 | // ... 47 | return 42, nil 48 | } 49 | 50 | func ExamplePusher_Add() { 51 | // We use a registry here to benefit from the consistency checks that 52 | // happen during registration. 53 | registry := prometheus.NewRegistry() 54 | registry.MustRegister(completionTime, duration, records) 55 | // Note that successTime is not registered. 56 | 57 | pusher := push.New("http://pushgateway:9091", "db_backup").Gatherer(registry) 58 | 59 | start := time.Now() 60 | n, err := performBackup() 61 | records.Set(float64(n)) 62 | // Note that time.Since only uses a monotonic clock in Go1.9+. 63 | duration.Set(time.Since(start).Seconds()) 64 | completionTime.SetToCurrentTime() 65 | if err != nil { 66 | fmt.Println("DB backup failed:", err) 67 | } else { 68 | // Add successTime to pusher only in case of success. 69 | // We could as well register it with the registry. 70 | // This example, however, demonstrates that you can 71 | // mix Gatherers and Collectors when handling a Pusher. 72 | pusher.Collector(successTime) 73 | successTime.SetToCurrentTime() 74 | } 75 | // Add is used here rather than Push to not delete a previously pushed 76 | // success timestamp in case of a failure of this backup. 77 | if err := pusher.Add(); err != nil { 78 | fmt.Println("Could not push to Pushgateway:", err) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /prometheus/go_collector_test.go: -------------------------------------------------------------------------------- 1 | package prometheus 2 | 3 | import ( 4 | "runtime" 5 | "testing" 6 | "time" 7 | 8 | dto "github.com/prometheus/client_model/go" 9 | ) 10 | 11 | func TestGoCollector(t *testing.T) { 12 | var ( 13 | c = NewGoCollector() 14 | ch = make(chan Metric) 15 | waitc = make(chan struct{}) 16 | closec = make(chan struct{}) 17 | old = -1 18 | ) 19 | defer close(closec) 20 | 21 | go func() { 22 | c.Collect(ch) 23 | go func(c <-chan struct{}) { 24 | <-c 25 | }(closec) 26 | <-waitc 27 | c.Collect(ch) 28 | }() 29 | 30 | for { 31 | select { 32 | case m := <-ch: 33 | // m can be Gauge or Counter, 34 | // currently just test the go_goroutines Gauge 35 | // and ignore others. 36 | if m.Desc().fqName != "go_goroutines" { 37 | continue 38 | } 39 | pb := &dto.Metric{} 40 | m.Write(pb) 41 | if pb.GetGauge() == nil { 42 | continue 43 | } 44 | 45 | if old == -1 { 46 | old = int(pb.GetGauge().GetValue()) 47 | close(waitc) 48 | continue 49 | } 50 | 51 | if diff := int(pb.GetGauge().GetValue()) - old; diff != 1 { 52 | // TODO: This is flaky in highly concurrent situations. 53 | t.Errorf("want 1 new goroutine, got %d", diff) 54 | } 55 | 56 | // GoCollector performs three sends per call. 57 | // On line 27 we need to receive three more sends 58 | // to shut down cleanly. 59 | <-ch 60 | <-ch 61 | <-ch 62 | return 63 | case <-time.After(1 * time.Second): 64 | t.Fatalf("expected collect timed out") 65 | } 66 | } 67 | } 68 | 69 | func TestGCCollector(t *testing.T) { 70 | var ( 71 | c = NewGoCollector() 72 | ch = make(chan Metric) 73 | waitc = make(chan struct{}) 74 | closec = make(chan struct{}) 75 | oldGC uint64 76 | oldPause float64 77 | ) 78 | defer close(closec) 79 | 80 | go func() { 81 | c.Collect(ch) 82 | // force GC 83 | runtime.GC() 84 | <-waitc 85 | c.Collect(ch) 86 | }() 87 | 88 | first := true 89 | for { 90 | select { 91 | case metric := <-ch: 92 | pb := &dto.Metric{} 93 | metric.Write(pb) 94 | if pb.GetSummary() == nil { 95 | continue 96 | } 97 | if len(pb.GetSummary().Quantile) != 5 { 98 | t.Errorf("expected 4 buckets, got %d", len(pb.GetSummary().Quantile)) 99 | } 100 | for idx, want := range []float64{0.0, 0.25, 0.5, 0.75, 1.0} { 101 | if *pb.GetSummary().Quantile[idx].Quantile != want { 102 | t.Errorf("bucket #%d is off, got %f, want %f", idx, *pb.GetSummary().Quantile[idx].Quantile, want) 103 | } 104 | } 105 | if first { 106 | first = false 107 | oldGC = *pb.GetSummary().SampleCount 108 | oldPause = *pb.GetSummary().SampleSum 109 | close(waitc) 110 | continue 111 | } 112 | if diff := *pb.GetSummary().SampleCount - oldGC; diff != 1 { 113 | t.Errorf("want 1 new garbage collection run, got %d", diff) 114 | } 115 | if diff := *pb.GetSummary().SampleSum - oldPause; diff <= 0 { 116 | t.Errorf("want moar pause, got %f", diff) 117 | } 118 | return 119 | case <-time.After(1 * time.Second): 120 | t.Fatalf("expected collect timed out") 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /api/client_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | // +build go1.7 15 | 16 | package api 17 | 18 | import ( 19 | "net/http" 20 | "net/url" 21 | "testing" 22 | ) 23 | 24 | func TestConfig(t *testing.T) { 25 | c := Config{} 26 | if c.roundTripper() != DefaultRoundTripper { 27 | t.Fatalf("expected default roundtripper for nil RoundTripper field") 28 | } 29 | } 30 | 31 | func TestClientURL(t *testing.T) { 32 | tests := []struct { 33 | address string 34 | endpoint string 35 | args map[string]string 36 | expected string 37 | }{ 38 | { 39 | address: "http://localhost:9090", 40 | endpoint: "/test", 41 | expected: "http://localhost:9090/test", 42 | }, 43 | { 44 | address: "http://localhost", 45 | endpoint: "/test", 46 | expected: "http://localhost/test", 47 | }, 48 | { 49 | address: "http://localhost:9090", 50 | endpoint: "test", 51 | expected: "http://localhost:9090/test", 52 | }, 53 | { 54 | address: "http://localhost:9090/prefix", 55 | endpoint: "/test", 56 | expected: "http://localhost:9090/prefix/test", 57 | }, 58 | { 59 | address: "https://localhost:9090/", 60 | endpoint: "/test/", 61 | expected: "https://localhost:9090/test", 62 | }, 63 | { 64 | address: "http://localhost:9090", 65 | endpoint: "/test/:param", 66 | args: map[string]string{ 67 | "param": "content", 68 | }, 69 | expected: "http://localhost:9090/test/content", 70 | }, 71 | { 72 | address: "http://localhost:9090", 73 | endpoint: "/test/:param/more/:param", 74 | args: map[string]string{ 75 | "param": "content", 76 | }, 77 | expected: "http://localhost:9090/test/content/more/content", 78 | }, 79 | { 80 | address: "http://localhost:9090", 81 | endpoint: "/test/:param/more/:foo", 82 | args: map[string]string{ 83 | "param": "content", 84 | "foo": "bar", 85 | }, 86 | expected: "http://localhost:9090/test/content/more/bar", 87 | }, 88 | { 89 | address: "http://localhost:9090", 90 | endpoint: "/test/:param", 91 | args: map[string]string{ 92 | "nonexistent": "content", 93 | }, 94 | expected: "http://localhost:9090/test/:param", 95 | }, 96 | } 97 | 98 | for _, test := range tests { 99 | ep, err := url.Parse(test.address) 100 | if err != nil { 101 | t.Fatal(err) 102 | } 103 | 104 | hclient := &httpClient{ 105 | endpoint: ep, 106 | client: http.Client{Transport: DefaultRoundTripper}, 107 | } 108 | 109 | u := hclient.URL(test.endpoint, test.args) 110 | if u.String() != test.expected { 111 | t.Errorf("unexpected result: got %s, want %s", u, test.expected) 112 | continue 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /api/client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | // +build go1.7 15 | 16 | // Package api provides clients for the HTTP APIs. 17 | package api 18 | 19 | import ( 20 | "context" 21 | "io/ioutil" 22 | "net" 23 | "net/http" 24 | "net/url" 25 | "path" 26 | "strings" 27 | "time" 28 | ) 29 | 30 | // DefaultRoundTripper is used if no RoundTripper is set in Config. 31 | var DefaultRoundTripper http.RoundTripper = &http.Transport{ 32 | Proxy: http.ProxyFromEnvironment, 33 | DialContext: (&net.Dialer{ 34 | Timeout: 30 * time.Second, 35 | KeepAlive: 30 * time.Second, 36 | }).DialContext, 37 | TLSHandshakeTimeout: 10 * time.Second, 38 | } 39 | 40 | // Config defines configuration parameters for a new client. 41 | type Config struct { 42 | // The address of the Prometheus to connect to. 43 | Address string 44 | 45 | // RoundTripper is used by the Client to drive HTTP requests. If not 46 | // provided, DefaultRoundTripper will be used. 47 | RoundTripper http.RoundTripper 48 | } 49 | 50 | func (cfg *Config) roundTripper() http.RoundTripper { 51 | if cfg.RoundTripper == nil { 52 | return DefaultRoundTripper 53 | } 54 | return cfg.RoundTripper 55 | } 56 | 57 | // Client is the interface for an API client. 58 | type Client interface { 59 | URL(ep string, args map[string]string) *url.URL 60 | Do(context.Context, *http.Request) (*http.Response, []byte, error) 61 | } 62 | 63 | // NewClient returns a new Client. 64 | // 65 | // It is safe to use the returned Client from multiple goroutines. 66 | func NewClient(cfg Config) (Client, error) { 67 | u, err := url.Parse(cfg.Address) 68 | if err != nil { 69 | return nil, err 70 | } 71 | u.Path = strings.TrimRight(u.Path, "/") 72 | 73 | return &httpClient{ 74 | endpoint: u, 75 | client: http.Client{Transport: cfg.roundTripper()}, 76 | }, nil 77 | } 78 | 79 | type httpClient struct { 80 | endpoint *url.URL 81 | client http.Client 82 | } 83 | 84 | func (c *httpClient) URL(ep string, args map[string]string) *url.URL { 85 | p := path.Join(c.endpoint.Path, ep) 86 | 87 | for arg, val := range args { 88 | arg = ":" + arg 89 | p = strings.Replace(p, arg, val, -1) 90 | } 91 | 92 | u := *c.endpoint 93 | u.Path = p 94 | 95 | return &u 96 | } 97 | 98 | func (c *httpClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, error) { 99 | if ctx != nil { 100 | req = req.WithContext(ctx) 101 | } 102 | resp, err := c.client.Do(req) 103 | defer func() { 104 | if resp != nil { 105 | resp.Body.Close() 106 | } 107 | }() 108 | 109 | if err != nil { 110 | return nil, nil, err 111 | } 112 | 113 | var body []byte 114 | done := make(chan struct{}) 115 | go func() { 116 | body, err = ioutil.ReadAll(resp.Body) 117 | close(done) 118 | }() 119 | 120 | select { 121 | case <-ctx.Done(): 122 | err = resp.Body.Close() 123 | <-done 124 | if err == nil { 125 | err = ctx.Err() 126 | } 127 | case <-done: 128 | } 129 | 130 | return resp, body, err 131 | } 132 | -------------------------------------------------------------------------------- /prometheus/expvar_collector_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package prometheus_test 15 | 16 | import ( 17 | "expvar" 18 | "fmt" 19 | "sort" 20 | "strings" 21 | 22 | dto "github.com/prometheus/client_model/go" 23 | 24 | "github.com/prometheus/client_golang/prometheus" 25 | ) 26 | 27 | func ExampleNewExpvarCollector() { 28 | expvarCollector := prometheus.NewExpvarCollector(map[string]*prometheus.Desc{ 29 | "memstats": prometheus.NewDesc( 30 | "expvar_memstats", 31 | "All numeric memstats as one metric family. Not a good role-model, actually... ;-)", 32 | []string{"type"}, nil, 33 | ), 34 | "lone-int": prometheus.NewDesc( 35 | "expvar_lone_int", 36 | "Just an expvar int as an example.", 37 | nil, nil, 38 | ), 39 | "http-request-map": prometheus.NewDesc( 40 | "expvar_http_request_total", 41 | "How many http requests processed, partitioned by status code and http method.", 42 | []string{"code", "method"}, nil, 43 | ), 44 | }) 45 | prometheus.MustRegister(expvarCollector) 46 | 47 | // The Prometheus part is done here. But to show that this example is 48 | // doing anything, we have to manually export something via expvar. In 49 | // real-life use-cases, some library would already have exported via 50 | // expvar what we want to re-export as Prometheus metrics. 51 | expvar.NewInt("lone-int").Set(42) 52 | expvarMap := expvar.NewMap("http-request-map") 53 | var ( 54 | expvarMap1, expvarMap2 expvar.Map 55 | expvarInt11, expvarInt12, expvarInt21, expvarInt22 expvar.Int 56 | ) 57 | expvarMap1.Init() 58 | expvarMap2.Init() 59 | expvarInt11.Set(3) 60 | expvarInt12.Set(13) 61 | expvarInt21.Set(11) 62 | expvarInt22.Set(212) 63 | expvarMap1.Set("POST", &expvarInt11) 64 | expvarMap1.Set("GET", &expvarInt12) 65 | expvarMap2.Set("POST", &expvarInt21) 66 | expvarMap2.Set("GET", &expvarInt22) 67 | expvarMap.Set("404", &expvarMap1) 68 | expvarMap.Set("200", &expvarMap2) 69 | // Results in the following expvar map: 70 | // "http-request-count": {"200": {"POST": 11, "GET": 212}, "404": {"POST": 3, "GET": 13}} 71 | 72 | // Let's see what the scrape would yield, but exclude the memstats metrics. 73 | metricStrings := []string{} 74 | metric := dto.Metric{} 75 | metricChan := make(chan prometheus.Metric) 76 | go func() { 77 | expvarCollector.Collect(metricChan) 78 | close(metricChan) 79 | }() 80 | for m := range metricChan { 81 | if !strings.Contains(m.Desc().String(), "expvar_memstats") { 82 | metric.Reset() 83 | m.Write(&metric) 84 | metricStrings = append(metricStrings, metric.String()) 85 | } 86 | } 87 | sort.Strings(metricStrings) 88 | for _, s := range metricStrings { 89 | fmt.Println(strings.TrimRight(s, " ")) 90 | } 91 | // Output: 92 | // label: label: untyped: 93 | // label: label: untyped: 94 | // label: label: untyped: 95 | // label: label: untyped: 96 | // untyped: 97 | } 98 | -------------------------------------------------------------------------------- /prometheus/collector.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package prometheus 15 | 16 | // Collector is the interface implemented by anything that can be used by 17 | // Prometheus to collect metrics. A Collector has to be registered for 18 | // collection. See Registerer.Register. 19 | // 20 | // The stock metrics provided by this package (Gauge, Counter, Summary, 21 | // Histogram, Untyped) are also Collectors (which only ever collect one metric, 22 | // namely itself). An implementer of Collector may, however, collect multiple 23 | // metrics in a coordinated fashion and/or create metrics on the fly. Examples 24 | // for collectors already implemented in this library are the metric vectors 25 | // (i.e. collection of multiple instances of the same Metric but with different 26 | // label values) like GaugeVec or SummaryVec, and the ExpvarCollector. 27 | type Collector interface { 28 | // Describe sends the super-set of all possible descriptors of metrics 29 | // collected by this Collector to the provided channel and returns once 30 | // the last descriptor has been sent. The sent descriptors fulfill the 31 | // consistency and uniqueness requirements described in the Desc 32 | // documentation. 33 | // 34 | // It is valid if one and the same Collector sends duplicate 35 | // descriptors. Those duplicates are simply ignored. However, two 36 | // different Collectors must not send duplicate descriptors. 37 | // 38 | // Sending no descriptor at all marks the Collector as “unchecked”, 39 | // i.e. no checks will be performed at registration time, and the 40 | // Collector may yield any Metric it sees fit in its Collect method. 41 | // 42 | // This method idempotently sends the same descriptors throughout the 43 | // lifetime of the Collector. 44 | // 45 | // If a Collector encounters an error while executing this method, it 46 | // must send an invalid descriptor (created with NewInvalidDesc) to 47 | // signal the error to the registry. 48 | Describe(chan<- *Desc) 49 | // Collect is called by the Prometheus registry when collecting 50 | // metrics. The implementation sends each collected metric via the 51 | // provided channel and returns once the last metric has been sent. The 52 | // descriptor of each sent metric is one of those returned by Describe 53 | // (unless the Collector is unchecked, see above). Returned metrics that 54 | // share the same descriptor must differ in their variable label 55 | // values. 56 | // 57 | // This method may be called concurrently and must therefore be 58 | // implemented in a concurrency safe way. Blocking occurs at the expense 59 | // of total performance of rendering all registered metrics. Ideally, 60 | // Collector implementations support concurrent readers. 61 | Collect(chan<- Metric) 62 | } 63 | 64 | // selfCollector implements Collector for a single Metric so that the Metric 65 | // collects itself. Add it as an anonymous field to a struct that implements 66 | // Metric, and call init with the Metric itself as an argument. 67 | type selfCollector struct { 68 | self Metric 69 | } 70 | 71 | // init provides the selfCollector with a reference to the metric it is supposed 72 | // to collect. It is usually called within the factory function to create a 73 | // metric. See example. 74 | func (c *selfCollector) init(self Metric) { 75 | c.self = self 76 | } 77 | 78 | // Describe implements Collector. 79 | func (c *selfCollector) Describe(ch chan<- *Desc) { 80 | ch <- c.self.Desc() 81 | } 82 | 83 | // Collect implements Collector. 84 | func (c *selfCollector) Collect(ch chan<- Metric) { 85 | ch <- c.self 86 | } 87 | -------------------------------------------------------------------------------- /examples/random/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | // A simple example exposing fictional RPC latencies with different types of 15 | // random distributions (uniform, normal, and exponential) as Prometheus 16 | // metrics. 17 | package main 18 | 19 | import ( 20 | "flag" 21 | "log" 22 | "math" 23 | "math/rand" 24 | "net/http" 25 | "time" 26 | 27 | "github.com/prometheus/client_golang/prometheus" 28 | "github.com/prometheus/client_golang/prometheus/promhttp" 29 | ) 30 | 31 | var ( 32 | addr = flag.String("listen-address", ":8080", "The address to listen on for HTTP requests.") 33 | uniformDomain = flag.Float64("uniform.domain", 0.0002, "The domain for the uniform distribution.") 34 | normDomain = flag.Float64("normal.domain", 0.0002, "The domain for the normal distribution.") 35 | normMean = flag.Float64("normal.mean", 0.00001, "The mean for the normal distribution.") 36 | oscillationPeriod = flag.Duration("oscillation-period", 10*time.Minute, "The duration of the rate oscillation period.") 37 | ) 38 | 39 | var ( 40 | // Create a summary to track fictional interservice RPC latencies for three 41 | // distinct services with different latency distributions. These services are 42 | // differentiated via a "service" label. 43 | rpcDurations = prometheus.NewSummaryVec( 44 | prometheus.SummaryOpts{ 45 | Name: "rpc_durations_seconds", 46 | Help: "RPC latency distributions.", 47 | Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, 48 | }, 49 | []string{"service"}, 50 | ) 51 | // The same as above, but now as a histogram, and only for the normal 52 | // distribution. The buckets are targeted to the parameters of the 53 | // normal distribution, with 20 buckets centered on the mean, each 54 | // half-sigma wide. 55 | rpcDurationsHistogram = prometheus.NewHistogram(prometheus.HistogramOpts{ 56 | Name: "rpc_durations_histogram_seconds", 57 | Help: "RPC latency distributions.", 58 | Buckets: prometheus.LinearBuckets(*normMean-5**normDomain, .5**normDomain, 20), 59 | }) 60 | ) 61 | 62 | func init() { 63 | // Register the summary and the histogram with Prometheus's default registry. 64 | prometheus.MustRegister(rpcDurations) 65 | prometheus.MustRegister(rpcDurationsHistogram) 66 | } 67 | 68 | func main() { 69 | flag.Parse() 70 | 71 | start := time.Now() 72 | 73 | oscillationFactor := func() float64 { 74 | return 2 + math.Sin(math.Sin(2*math.Pi*float64(time.Since(start))/float64(*oscillationPeriod))) 75 | } 76 | 77 | // Periodically record some sample latencies for the three services. 78 | go func() { 79 | for { 80 | v := rand.Float64() * *uniformDomain 81 | rpcDurations.WithLabelValues("uniform").Observe(v) 82 | time.Sleep(time.Duration(100*oscillationFactor()) * time.Millisecond) 83 | } 84 | }() 85 | 86 | go func() { 87 | for { 88 | v := (rand.NormFloat64() * *normDomain) + *normMean 89 | rpcDurations.WithLabelValues("normal").Observe(v) 90 | rpcDurationsHistogram.Observe(v) 91 | time.Sleep(time.Duration(75*oscillationFactor()) * time.Millisecond) 92 | } 93 | }() 94 | 95 | go func() { 96 | for { 97 | v := rand.ExpFloat64() / 1e6 98 | rpcDurations.WithLabelValues("exponential").Observe(v) 99 | time.Sleep(time.Duration(50*oscillationFactor()) * time.Millisecond) 100 | } 101 | }() 102 | 103 | // Expose the registered metrics via HTTP. 104 | http.Handle("/metrics", promhttp.Handler()) 105 | log.Fatal(http.ListenAndServe(*addr, nil)) 106 | } 107 | -------------------------------------------------------------------------------- /prometheus/promhttp/instrument_client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package promhttp 15 | 16 | import ( 17 | "net/http" 18 | "time" 19 | 20 | "github.com/prometheus/client_golang/prometheus" 21 | ) 22 | 23 | // The RoundTripperFunc type is an adapter to allow the use of ordinary 24 | // functions as RoundTrippers. If f is a function with the appropriate 25 | // signature, RountTripperFunc(f) is a RoundTripper that calls f. 26 | type RoundTripperFunc func(req *http.Request) (*http.Response, error) 27 | 28 | // RoundTrip implements the RoundTripper interface. 29 | func (rt RoundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error) { 30 | return rt(r) 31 | } 32 | 33 | // InstrumentRoundTripperInFlight is a middleware that wraps the provided 34 | // http.RoundTripper. It sets the provided prometheus.Gauge to the number of 35 | // requests currently handled by the wrapped http.RoundTripper. 36 | // 37 | // See the example for ExampleInstrumentRoundTripperDuration for example usage. 38 | func InstrumentRoundTripperInFlight(gauge prometheus.Gauge, next http.RoundTripper) RoundTripperFunc { 39 | return RoundTripperFunc(func(r *http.Request) (*http.Response, error) { 40 | gauge.Inc() 41 | defer gauge.Dec() 42 | return next.RoundTrip(r) 43 | }) 44 | } 45 | 46 | // InstrumentRoundTripperCounter is a middleware that wraps the provided 47 | // http.RoundTripper to observe the request result with the provided CounterVec. 48 | // The CounterVec must have zero, one, or two non-const non-curried labels. For 49 | // those, the only allowed label names are "code" and "method". The function 50 | // panics otherwise. Partitioning of the CounterVec happens by HTTP status code 51 | // and/or HTTP method if the respective instance label names are present in the 52 | // CounterVec. For unpartitioned counting, use a CounterVec with zero labels. 53 | // 54 | // If the wrapped RoundTripper panics or returns a non-nil error, the Counter 55 | // is not incremented. 56 | // 57 | // See the example for ExampleInstrumentRoundTripperDuration for example usage. 58 | func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.RoundTripper) RoundTripperFunc { 59 | code, method := checkLabels(counter) 60 | 61 | return RoundTripperFunc(func(r *http.Request) (*http.Response, error) { 62 | resp, err := next.RoundTrip(r) 63 | if err == nil { 64 | counter.With(labels(code, method, r.Method, resp.StatusCode)).Inc() 65 | } 66 | return resp, err 67 | }) 68 | } 69 | 70 | // InstrumentRoundTripperDuration is a middleware that wraps the provided 71 | // http.RoundTripper to observe the request duration with the provided 72 | // ObserverVec. The ObserverVec must have zero, one, or two non-const 73 | // non-curried labels. For those, the only allowed label names are "code" and 74 | // "method". The function panics otherwise. The Observe method of the Observer 75 | // in the ObserverVec is called with the request duration in 76 | // seconds. Partitioning happens by HTTP status code and/or HTTP method if the 77 | // respective instance label names are present in the ObserverVec. For 78 | // unpartitioned observations, use an ObserverVec with zero labels. Note that 79 | // partitioning of Histograms is expensive and should be used judiciously. 80 | // 81 | // If the wrapped RoundTripper panics or returns a non-nil error, no values are 82 | // reported. 83 | // 84 | // Note that this method is only guaranteed to never observe negative durations 85 | // if used with Go1.9+. 86 | func InstrumentRoundTripperDuration(obs prometheus.ObserverVec, next http.RoundTripper) RoundTripperFunc { 87 | code, method := checkLabels(obs) 88 | 89 | return RoundTripperFunc(func(r *http.Request) (*http.Response, error) { 90 | start := time.Now() 91 | resp, err := next.RoundTrip(r) 92 | if err == nil { 93 | obs.With(labels(code, method, r.Method, resp.StatusCode)).Observe(time.Since(start).Seconds()) 94 | } 95 | return resp, err 96 | }) 97 | } 98 | -------------------------------------------------------------------------------- /prometheus/process_collector.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package prometheus 15 | 16 | import "github.com/prometheus/procfs" 17 | 18 | type processCollector struct { 19 | collectFn func(chan<- Metric) 20 | pidFn func() (int, error) 21 | cpuTotal *Desc 22 | openFDs, maxFDs *Desc 23 | vsize, rss *Desc 24 | startTime *Desc 25 | } 26 | 27 | // NewProcessCollector returns a collector which exports the current state of 28 | // process metrics including CPU, memory and file descriptor usage as well as 29 | // the process start time for the given process ID under the given namespace. 30 | // 31 | // Currently, the collector depends on a Linux-style proc filesystem and 32 | // therefore only exports metrics for Linux. 33 | func NewProcessCollector(pid int, namespace string) Collector { 34 | return NewProcessCollectorPIDFn( 35 | func() (int, error) { return pid, nil }, 36 | namespace, 37 | ) 38 | } 39 | 40 | // NewProcessCollectorPIDFn works like NewProcessCollector but the process ID is 41 | // determined on each collect anew by calling the given pidFn function. 42 | func NewProcessCollectorPIDFn( 43 | pidFn func() (int, error), 44 | namespace string, 45 | ) Collector { 46 | ns := "" 47 | if len(namespace) > 0 { 48 | ns = namespace + "_" 49 | } 50 | 51 | c := processCollector{ 52 | pidFn: pidFn, 53 | collectFn: func(chan<- Metric) {}, 54 | 55 | cpuTotal: NewDesc( 56 | ns+"process_cpu_seconds_total", 57 | "Total user and system CPU time spent in seconds.", 58 | nil, nil, 59 | ), 60 | openFDs: NewDesc( 61 | ns+"process_open_fds", 62 | "Number of open file descriptors.", 63 | nil, nil, 64 | ), 65 | maxFDs: NewDesc( 66 | ns+"process_max_fds", 67 | "Maximum number of open file descriptors.", 68 | nil, nil, 69 | ), 70 | vsize: NewDesc( 71 | ns+"process_virtual_memory_bytes", 72 | "Virtual memory size in bytes.", 73 | nil, nil, 74 | ), 75 | rss: NewDesc( 76 | ns+"process_resident_memory_bytes", 77 | "Resident memory size in bytes.", 78 | nil, nil, 79 | ), 80 | startTime: NewDesc( 81 | ns+"process_start_time_seconds", 82 | "Start time of the process since unix epoch in seconds.", 83 | nil, nil, 84 | ), 85 | } 86 | 87 | // Set up process metric collection if supported by the runtime. 88 | if _, err := procfs.NewStat(); err == nil { 89 | c.collectFn = c.processCollect 90 | } 91 | 92 | return &c 93 | } 94 | 95 | // Describe returns all descriptions of the collector. 96 | func (c *processCollector) Describe(ch chan<- *Desc) { 97 | ch <- c.cpuTotal 98 | ch <- c.openFDs 99 | ch <- c.maxFDs 100 | ch <- c.vsize 101 | ch <- c.rss 102 | ch <- c.startTime 103 | } 104 | 105 | // Collect returns the current state of all metrics of the collector. 106 | func (c *processCollector) Collect(ch chan<- Metric) { 107 | c.collectFn(ch) 108 | } 109 | 110 | // TODO(ts): Bring back error reporting by reverting 7faf9e7 as soon as the 111 | // client allows users to configure the error behavior. 112 | func (c *processCollector) processCollect(ch chan<- Metric) { 113 | pid, err := c.pidFn() 114 | if err != nil { 115 | return 116 | } 117 | 118 | p, err := procfs.NewProc(pid) 119 | if err != nil { 120 | return 121 | } 122 | 123 | if stat, err := p.NewStat(); err == nil { 124 | ch <- MustNewConstMetric(c.cpuTotal, CounterValue, stat.CPUTime()) 125 | ch <- MustNewConstMetric(c.vsize, GaugeValue, float64(stat.VirtualMemory())) 126 | ch <- MustNewConstMetric(c.rss, GaugeValue, float64(stat.ResidentMemory())) 127 | if startTime, err := stat.StartTime(); err == nil { 128 | ch <- MustNewConstMetric(c.startTime, GaugeValue, startTime) 129 | } 130 | } 131 | 132 | if fds, err := p.FileDescriptorsLen(); err == nil { 133 | ch <- MustNewConstMetric(c.openFDs, GaugeValue, float64(fds)) 134 | } 135 | 136 | if limits, err := p.NewLimits(); err == nil { 137 | ch <- MustNewConstMetric(c.maxFDs, GaugeValue, float64(limits.OpenFiles)) 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /prometheus/expvar_collector.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package prometheus 15 | 16 | import ( 17 | "encoding/json" 18 | "expvar" 19 | ) 20 | 21 | type expvarCollector struct { 22 | exports map[string]*Desc 23 | } 24 | 25 | // NewExpvarCollector returns a newly allocated expvar Collector that still has 26 | // to be registered with a Prometheus registry. 27 | // 28 | // An expvar Collector collects metrics from the expvar interface. It provides a 29 | // quick way to expose numeric values that are already exported via expvar as 30 | // Prometheus metrics. Note that the data models of expvar and Prometheus are 31 | // fundamentally different, and that the expvar Collector is inherently slower 32 | // than native Prometheus metrics. Thus, the expvar Collector is probably great 33 | // for experiments and prototying, but you should seriously consider a more 34 | // direct implementation of Prometheus metrics for monitoring production 35 | // systems. 36 | // 37 | // The exports map has the following meaning: 38 | // 39 | // The keys in the map correspond to expvar keys, i.e. for every expvar key you 40 | // want to export as Prometheus metric, you need an entry in the exports 41 | // map. The descriptor mapped to each key describes how to export the expvar 42 | // value. It defines the name and the help string of the Prometheus metric 43 | // proxying the expvar value. The type will always be Untyped. 44 | // 45 | // For descriptors without variable labels, the expvar value must be a number or 46 | // a bool. The number is then directly exported as the Prometheus sample 47 | // value. (For a bool, 'false' translates to 0 and 'true' to 1). Expvar values 48 | // that are not numbers or bools are silently ignored. 49 | // 50 | // If the descriptor has one variable label, the expvar value must be an expvar 51 | // map. The keys in the expvar map become the various values of the one 52 | // Prometheus label. The values in the expvar map must be numbers or bools again 53 | // as above. 54 | // 55 | // For descriptors with more than one variable label, the expvar must be a 56 | // nested expvar map, i.e. where the values of the topmost map are maps again 57 | // etc. until a depth is reached that corresponds to the number of labels. The 58 | // leaves of that structure must be numbers or bools as above to serve as the 59 | // sample values. 60 | // 61 | // Anything that does not fit into the scheme above is silently ignored. 62 | func NewExpvarCollector(exports map[string]*Desc) Collector { 63 | return &expvarCollector{ 64 | exports: exports, 65 | } 66 | } 67 | 68 | // Describe implements Collector. 69 | func (e *expvarCollector) Describe(ch chan<- *Desc) { 70 | for _, desc := range e.exports { 71 | ch <- desc 72 | } 73 | } 74 | 75 | // Collect implements Collector. 76 | func (e *expvarCollector) Collect(ch chan<- Metric) { 77 | for name, desc := range e.exports { 78 | var m Metric 79 | expVar := expvar.Get(name) 80 | if expVar == nil { 81 | continue 82 | } 83 | var v interface{} 84 | labels := make([]string, len(desc.variableLabels)) 85 | if err := json.Unmarshal([]byte(expVar.String()), &v); err != nil { 86 | ch <- NewInvalidMetric(desc, err) 87 | continue 88 | } 89 | var processValue func(v interface{}, i int) 90 | processValue = func(v interface{}, i int) { 91 | if i >= len(labels) { 92 | copiedLabels := append(make([]string, 0, len(labels)), labels...) 93 | switch v := v.(type) { 94 | case float64: 95 | m = MustNewConstMetric(desc, UntypedValue, v, copiedLabels...) 96 | case bool: 97 | if v { 98 | m = MustNewConstMetric(desc, UntypedValue, 1, copiedLabels...) 99 | } else { 100 | m = MustNewConstMetric(desc, UntypedValue, 0, copiedLabels...) 101 | } 102 | default: 103 | return 104 | } 105 | ch <- m 106 | return 107 | } 108 | vm, ok := v.(map[string]interface{}) 109 | if !ok { 110 | return 111 | } 112 | for lv, val := range vm { 113 | labels[i] = lv 114 | processValue(val, i+1) 115 | } 116 | } 117 | processValue(v, 0) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /prometheus/example_clustermanager_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package prometheus_test 15 | 16 | import "github.com/prometheus/client_golang/prometheus" 17 | 18 | // ClusterManager is an example for a system that might have been built without 19 | // Prometheus in mind. It models a central manager of jobs running in a 20 | // cluster. To turn it into something that collects Prometheus metrics, we 21 | // simply add the two methods required for the Collector interface. 22 | // 23 | // An additional challenge is that multiple instances of the ClusterManager are 24 | // run within the same binary, each in charge of a different zone. We need to 25 | // make use of ConstLabels to be able to register each ClusterManager instance 26 | // with Prometheus. 27 | type ClusterManager struct { 28 | Zone string 29 | OOMCountDesc *prometheus.Desc 30 | RAMUsageDesc *prometheus.Desc 31 | // ... many more fields 32 | } 33 | 34 | // ReallyExpensiveAssessmentOfTheSystemState is a mock for the data gathering a 35 | // real cluster manager would have to do. Since it may actually be really 36 | // expensive, it must only be called once per collection. This implementation, 37 | // obviously, only returns some made-up data. 38 | func (c *ClusterManager) ReallyExpensiveAssessmentOfTheSystemState() ( 39 | oomCountByHost map[string]int, ramUsageByHost map[string]float64, 40 | ) { 41 | // Just example fake data. 42 | oomCountByHost = map[string]int{ 43 | "foo.example.org": 42, 44 | "bar.example.org": 2001, 45 | } 46 | ramUsageByHost = map[string]float64{ 47 | "foo.example.org": 6.023e23, 48 | "bar.example.org": 3.14, 49 | } 50 | return 51 | } 52 | 53 | // Describe simply sends the two Descs in the struct to the channel. 54 | func (c *ClusterManager) Describe(ch chan<- *prometheus.Desc) { 55 | ch <- c.OOMCountDesc 56 | ch <- c.RAMUsageDesc 57 | } 58 | 59 | // Collect first triggers the ReallyExpensiveAssessmentOfTheSystemState. Then it 60 | // creates constant metrics for each host on the fly based on the returned data. 61 | // 62 | // Note that Collect could be called concurrently, so we depend on 63 | // ReallyExpensiveAssessmentOfTheSystemState to be concurrency-safe. 64 | func (c *ClusterManager) Collect(ch chan<- prometheus.Metric) { 65 | oomCountByHost, ramUsageByHost := c.ReallyExpensiveAssessmentOfTheSystemState() 66 | for host, oomCount := range oomCountByHost { 67 | ch <- prometheus.MustNewConstMetric( 68 | c.OOMCountDesc, 69 | prometheus.CounterValue, 70 | float64(oomCount), 71 | host, 72 | ) 73 | } 74 | for host, ramUsage := range ramUsageByHost { 75 | ch <- prometheus.MustNewConstMetric( 76 | c.RAMUsageDesc, 77 | prometheus.GaugeValue, 78 | ramUsage, 79 | host, 80 | ) 81 | } 82 | } 83 | 84 | // NewClusterManager creates the two Descs OOMCountDesc and RAMUsageDesc. Note 85 | // that the zone is set as a ConstLabel. (It's different in each instance of the 86 | // ClusterManager, but constant over the lifetime of an instance.) Then there is 87 | // a variable label "host", since we want to partition the collected metrics by 88 | // host. Since all Descs created in this way are consistent across instances, 89 | // with a guaranteed distinction by the "zone" label, we can register different 90 | // ClusterManager instances with the same registry. 91 | func NewClusterManager(zone string) *ClusterManager { 92 | return &ClusterManager{ 93 | Zone: zone, 94 | OOMCountDesc: prometheus.NewDesc( 95 | "clustermanager_oom_crashes_total", 96 | "Number of OOM crashes.", 97 | []string{"host"}, 98 | prometheus.Labels{"zone": zone}, 99 | ), 100 | RAMUsageDesc: prometheus.NewDesc( 101 | "clustermanager_ram_usage_bytes", 102 | "RAM usage as reported to the cluster manager.", 103 | []string{"host"}, 104 | prometheus.Labels{"zone": zone}, 105 | ), 106 | } 107 | } 108 | 109 | func ExampleCollector() { 110 | workerDB := NewClusterManager("db") 111 | workerCA := NewClusterManager("ca") 112 | 113 | // Since we are dealing with custom Collector implementations, it might 114 | // be a good idea to try it out with a pedantic registry. 115 | reg := prometheus.NewPedanticRegistry() 116 | reg.MustRegister(workerDB) 117 | reg.MustRegister(workerCA) 118 | } 119 | -------------------------------------------------------------------------------- /prometheus/timer_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package prometheus 15 | 16 | import ( 17 | "testing" 18 | 19 | dto "github.com/prometheus/client_model/go" 20 | ) 21 | 22 | func TestTimerObserve(t *testing.T) { 23 | var ( 24 | his = NewHistogram(HistogramOpts{Name: "test_histogram"}) 25 | sum = NewSummary(SummaryOpts{Name: "test_summary"}) 26 | gauge = NewGauge(GaugeOpts{Name: "test_gauge"}) 27 | ) 28 | 29 | func() { 30 | hisTimer := NewTimer(his) 31 | sumTimer := NewTimer(sum) 32 | gaugeTimer := NewTimer(ObserverFunc(gauge.Set)) 33 | defer hisTimer.ObserveDuration() 34 | defer sumTimer.ObserveDuration() 35 | defer gaugeTimer.ObserveDuration() 36 | }() 37 | 38 | m := &dto.Metric{} 39 | his.Write(m) 40 | if want, got := uint64(1), m.GetHistogram().GetSampleCount(); want != got { 41 | t.Errorf("want %d observations for histogram, got %d", want, got) 42 | } 43 | m.Reset() 44 | sum.Write(m) 45 | if want, got := uint64(1), m.GetSummary().GetSampleCount(); want != got { 46 | t.Errorf("want %d observations for summary, got %d", want, got) 47 | } 48 | m.Reset() 49 | gauge.Write(m) 50 | if got := m.GetGauge().GetValue(); got <= 0 { 51 | t.Errorf("want value > 0 for gauge, got %f", got) 52 | } 53 | } 54 | 55 | func TestTimerEmpty(t *testing.T) { 56 | emptyTimer := NewTimer(nil) 57 | emptyTimer.ObserveDuration() 58 | // Do nothing, just demonstrate it works without panic. 59 | } 60 | 61 | func TestTimerConditionalTiming(t *testing.T) { 62 | var ( 63 | his = NewHistogram(HistogramOpts{ 64 | Name: "test_histogram", 65 | }) 66 | timeMe = true 67 | m = &dto.Metric{} 68 | ) 69 | 70 | timedFunc := func() { 71 | timer := NewTimer(ObserverFunc(func(v float64) { 72 | if timeMe { 73 | his.Observe(v) 74 | } 75 | })) 76 | defer timer.ObserveDuration() 77 | } 78 | 79 | timedFunc() // This will time. 80 | his.Write(m) 81 | if want, got := uint64(1), m.GetHistogram().GetSampleCount(); want != got { 82 | t.Errorf("want %d observations for histogram, got %d", want, got) 83 | } 84 | 85 | timeMe = false 86 | timedFunc() // This will not time again. 87 | m.Reset() 88 | his.Write(m) 89 | if want, got := uint64(1), m.GetHistogram().GetSampleCount(); want != got { 90 | t.Errorf("want %d observations for histogram, got %d", want, got) 91 | } 92 | } 93 | 94 | func TestTimerByOutcome(t *testing.T) { 95 | var ( 96 | his = NewHistogramVec( 97 | HistogramOpts{Name: "test_histogram"}, 98 | []string{"outcome"}, 99 | ) 100 | outcome = "foo" 101 | m = &dto.Metric{} 102 | ) 103 | 104 | timedFunc := func() { 105 | timer := NewTimer(ObserverFunc(func(v float64) { 106 | his.WithLabelValues(outcome).Observe(v) 107 | })) 108 | defer timer.ObserveDuration() 109 | 110 | if outcome == "foo" { 111 | outcome = "bar" 112 | return 113 | } 114 | outcome = "foo" 115 | } 116 | 117 | timedFunc() 118 | his.WithLabelValues("foo").(Histogram).Write(m) 119 | if want, got := uint64(0), m.GetHistogram().GetSampleCount(); want != got { 120 | t.Errorf("want %d observations for 'foo' histogram, got %d", want, got) 121 | } 122 | m.Reset() 123 | his.WithLabelValues("bar").(Histogram).Write(m) 124 | if want, got := uint64(1), m.GetHistogram().GetSampleCount(); want != got { 125 | t.Errorf("want %d observations for 'bar' histogram, got %d", want, got) 126 | } 127 | 128 | timedFunc() 129 | m.Reset() 130 | his.WithLabelValues("foo").(Histogram).Write(m) 131 | if want, got := uint64(1), m.GetHistogram().GetSampleCount(); want != got { 132 | t.Errorf("want %d observations for 'foo' histogram, got %d", want, got) 133 | } 134 | m.Reset() 135 | his.WithLabelValues("bar").(Histogram).Write(m) 136 | if want, got := uint64(1), m.GetHistogram().GetSampleCount(); want != got { 137 | t.Errorf("want %d observations for 'bar' histogram, got %d", want, got) 138 | } 139 | 140 | timedFunc() 141 | m.Reset() 142 | his.WithLabelValues("foo").(Histogram).Write(m) 143 | if want, got := uint64(1), m.GetHistogram().GetSampleCount(); want != got { 144 | t.Errorf("want %d observations for 'foo' histogram, got %d", want, got) 145 | } 146 | m.Reset() 147 | his.WithLabelValues("bar").(Histogram).Write(m) 148 | if want, got := uint64(2), m.GetHistogram().GetSampleCount(); want != got { 149 | t.Errorf("want %d observations for 'bar' histogram, got %d", want, got) 150 | } 151 | 152 | } 153 | -------------------------------------------------------------------------------- /prometheus/promhttp/instrument_client_1_8.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | // +build go1.8 15 | 16 | package promhttp 17 | 18 | import ( 19 | "context" 20 | "crypto/tls" 21 | "net/http" 22 | "net/http/httptrace" 23 | "time" 24 | ) 25 | 26 | // InstrumentTrace is used to offer flexibility in instrumenting the available 27 | // httptrace.ClientTrace hook functions. Each function is passed a float64 28 | // representing the time in seconds since the start of the http request. A user 29 | // may choose to use separately buckets Histograms, or implement custom 30 | // instance labels on a per function basis. 31 | type InstrumentTrace struct { 32 | GotConn func(float64) 33 | PutIdleConn func(float64) 34 | GotFirstResponseByte func(float64) 35 | Got100Continue func(float64) 36 | DNSStart func(float64) 37 | DNSDone func(float64) 38 | ConnectStart func(float64) 39 | ConnectDone func(float64) 40 | TLSHandshakeStart func(float64) 41 | TLSHandshakeDone func(float64) 42 | WroteHeaders func(float64) 43 | Wait100Continue func(float64) 44 | WroteRequest func(float64) 45 | } 46 | 47 | // InstrumentRoundTripperTrace is a middleware that wraps the provided 48 | // RoundTripper and reports times to hook functions provided in the 49 | // InstrumentTrace struct. Hook functions that are not present in the provided 50 | // InstrumentTrace struct are ignored. Times reported to the hook functions are 51 | // time since the start of the request. Only with Go1.9+, those times are 52 | // guaranteed to never be negative. (Earlier Go versions are not using a 53 | // monotonic clock.) Note that partitioning of Histograms is expensive and 54 | // should be used judiciously. 55 | // 56 | // For hook functions that receive an error as an argument, no observations are 57 | // made in the event of a non-nil error value. 58 | // 59 | // See the example for ExampleInstrumentRoundTripperDuration for example usage. 60 | func InstrumentRoundTripperTrace(it *InstrumentTrace, next http.RoundTripper) RoundTripperFunc { 61 | return RoundTripperFunc(func(r *http.Request) (*http.Response, error) { 62 | start := time.Now() 63 | 64 | trace := &httptrace.ClientTrace{ 65 | GotConn: func(_ httptrace.GotConnInfo) { 66 | if it.GotConn != nil { 67 | it.GotConn(time.Since(start).Seconds()) 68 | } 69 | }, 70 | PutIdleConn: func(err error) { 71 | if err != nil { 72 | return 73 | } 74 | if it.PutIdleConn != nil { 75 | it.PutIdleConn(time.Since(start).Seconds()) 76 | } 77 | }, 78 | DNSStart: func(_ httptrace.DNSStartInfo) { 79 | if it.DNSStart != nil { 80 | it.DNSStart(time.Since(start).Seconds()) 81 | } 82 | }, 83 | DNSDone: func(_ httptrace.DNSDoneInfo) { 84 | if it.DNSDone != nil { 85 | it.DNSDone(time.Since(start).Seconds()) 86 | } 87 | }, 88 | ConnectStart: func(_, _ string) { 89 | if it.ConnectStart != nil { 90 | it.ConnectStart(time.Since(start).Seconds()) 91 | } 92 | }, 93 | ConnectDone: func(_, _ string, err error) { 94 | if err != nil { 95 | return 96 | } 97 | if it.ConnectDone != nil { 98 | it.ConnectDone(time.Since(start).Seconds()) 99 | } 100 | }, 101 | GotFirstResponseByte: func() { 102 | if it.GotFirstResponseByte != nil { 103 | it.GotFirstResponseByte(time.Since(start).Seconds()) 104 | } 105 | }, 106 | Got100Continue: func() { 107 | if it.Got100Continue != nil { 108 | it.Got100Continue(time.Since(start).Seconds()) 109 | } 110 | }, 111 | TLSHandshakeStart: func() { 112 | if it.TLSHandshakeStart != nil { 113 | it.TLSHandshakeStart(time.Since(start).Seconds()) 114 | } 115 | }, 116 | TLSHandshakeDone: func(_ tls.ConnectionState, err error) { 117 | if err != nil { 118 | return 119 | } 120 | if it.TLSHandshakeDone != nil { 121 | it.TLSHandshakeDone(time.Since(start).Seconds()) 122 | } 123 | }, 124 | WroteHeaders: func() { 125 | if it.WroteHeaders != nil { 126 | it.WroteHeaders(time.Since(start).Seconds()) 127 | } 128 | }, 129 | Wait100Continue: func() { 130 | if it.Wait100Continue != nil { 131 | it.Wait100Continue(time.Since(start).Seconds()) 132 | } 133 | }, 134 | WroteRequest: func(_ httptrace.WroteRequestInfo) { 135 | if it.WroteRequest != nil { 136 | it.WroteRequest(time.Since(start).Seconds()) 137 | } 138 | }, 139 | } 140 | r = r.WithContext(httptrace.WithClientTrace(context.Background(), trace)) 141 | 142 | return next.RoundTrip(r) 143 | }) 144 | } 145 | -------------------------------------------------------------------------------- /prometheus/http_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package prometheus 15 | 16 | import ( 17 | "net/http" 18 | "net/http/httptest" 19 | "testing" 20 | "time" 21 | 22 | dto "github.com/prometheus/client_model/go" 23 | ) 24 | 25 | type respBody string 26 | 27 | func (b respBody) ServeHTTP(w http.ResponseWriter, r *http.Request) { 28 | w.WriteHeader(http.StatusTeapot) 29 | w.Write([]byte(b)) 30 | } 31 | 32 | func nowSeries(t ...time.Time) nower { 33 | return nowFunc(func() time.Time { 34 | defer func() { 35 | t = t[1:] 36 | }() 37 | 38 | return t[0] 39 | }) 40 | } 41 | 42 | func TestInstrumentHandler(t *testing.T) { 43 | defer func(n nower) { 44 | now = n.(nower) 45 | }(now) 46 | 47 | instant := time.Now() 48 | end := instant.Add(30 * time.Second) 49 | now = nowSeries(instant, end) 50 | body := respBody("Howdy there!") 51 | 52 | hndlr := InstrumentHandler("test-handler", body) 53 | 54 | opts := SummaryOpts{ 55 | Subsystem: "http", 56 | ConstLabels: Labels{"handler": "test-handler"}, 57 | Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, 58 | } 59 | 60 | reqCnt := NewCounterVec( 61 | CounterOpts{ 62 | Namespace: opts.Namespace, 63 | Subsystem: opts.Subsystem, 64 | Name: "requests_total", 65 | Help: "Total number of HTTP requests made.", 66 | ConstLabels: opts.ConstLabels, 67 | }, 68 | instLabels, 69 | ) 70 | err := Register(reqCnt) 71 | if err == nil { 72 | t.Fatal("expected reqCnt to be registered already") 73 | } 74 | if are, ok := err.(AlreadyRegisteredError); ok { 75 | reqCnt = are.ExistingCollector.(*CounterVec) 76 | } else { 77 | t.Fatal("unexpected registration error:", err) 78 | } 79 | 80 | opts.Name = "request_duration_microseconds" 81 | opts.Help = "The HTTP request latencies in microseconds." 82 | reqDur := NewSummary(opts) 83 | err = Register(reqDur) 84 | if err == nil { 85 | t.Fatal("expected reqDur to be registered already") 86 | } 87 | if are, ok := err.(AlreadyRegisteredError); ok { 88 | reqDur = are.ExistingCollector.(Summary) 89 | } else { 90 | t.Fatal("unexpected registration error:", err) 91 | } 92 | 93 | opts.Name = "request_size_bytes" 94 | opts.Help = "The HTTP request sizes in bytes." 95 | reqSz := NewSummary(opts) 96 | err = Register(reqSz) 97 | if err == nil { 98 | t.Fatal("expected reqSz to be registered already") 99 | } 100 | if _, ok := err.(AlreadyRegisteredError); !ok { 101 | t.Fatal("unexpected registration error:", err) 102 | } 103 | 104 | opts.Name = "response_size_bytes" 105 | opts.Help = "The HTTP response sizes in bytes." 106 | resSz := NewSummary(opts) 107 | err = Register(resSz) 108 | if err == nil { 109 | t.Fatal("expected resSz to be registered already") 110 | } 111 | if _, ok := err.(AlreadyRegisteredError); !ok { 112 | t.Fatal("unexpected registration error:", err) 113 | } 114 | 115 | reqCnt.Reset() 116 | 117 | resp := httptest.NewRecorder() 118 | req := &http.Request{ 119 | Method: "GET", 120 | } 121 | 122 | hndlr.ServeHTTP(resp, req) 123 | 124 | if resp.Code != http.StatusTeapot { 125 | t.Fatalf("expected status %d, got %d", http.StatusTeapot, resp.Code) 126 | } 127 | if resp.Body.String() != "Howdy there!" { 128 | t.Fatalf("expected body %s, got %s", "Howdy there!", resp.Body.String()) 129 | } 130 | 131 | out := &dto.Metric{} 132 | reqDur.Write(out) 133 | if want, got := "test-handler", out.Label[0].GetValue(); want != got { 134 | t.Errorf("want label value %q in reqDur, got %q", want, got) 135 | } 136 | if want, got := uint64(1), out.Summary.GetSampleCount(); want != got { 137 | t.Errorf("want sample count %d in reqDur, got %d", want, got) 138 | } 139 | 140 | out.Reset() 141 | if want, got := 1, len(reqCnt.metricMap.metrics); want != got { 142 | t.Errorf("want %d children in reqCnt, got %d", want, got) 143 | } 144 | cnt, err := reqCnt.GetMetricWithLabelValues("get", "418") 145 | if err != nil { 146 | t.Fatal(err) 147 | } 148 | cnt.Write(out) 149 | if want, got := "418", out.Label[0].GetValue(); want != got { 150 | t.Errorf("want label value %q in reqCnt, got %q", want, got) 151 | } 152 | if want, got := "test-handler", out.Label[1].GetValue(); want != got { 153 | t.Errorf("want label value %q in reqCnt, got %q", want, got) 154 | } 155 | if want, got := "get", out.Label[2].GetValue(); want != got { 156 | t.Errorf("want label value %q in reqCnt, got %q", want, got) 157 | } 158 | if out.Counter == nil { 159 | t.Fatal("expected non-nil counter in reqCnt") 160 | } 161 | if want, got := 1., out.Counter.GetValue(); want != got { 162 | t.Errorf("want reqCnt of %f, got %f", want, got) 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /prometheus/benchmark_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package prometheus 15 | 16 | import ( 17 | "sync" 18 | "testing" 19 | ) 20 | 21 | func BenchmarkCounterWithLabelValues(b *testing.B) { 22 | m := NewCounterVec( 23 | CounterOpts{ 24 | Name: "benchmark_counter", 25 | Help: "A counter to benchmark it.", 26 | }, 27 | []string{"one", "two", "three"}, 28 | ) 29 | b.ReportAllocs() 30 | b.ResetTimer() 31 | for i := 0; i < b.N; i++ { 32 | m.WithLabelValues("eins", "zwei", "drei").Inc() 33 | } 34 | } 35 | 36 | func BenchmarkCounterWithLabelValuesConcurrent(b *testing.B) { 37 | m := NewCounterVec( 38 | CounterOpts{ 39 | Name: "benchmark_counter", 40 | Help: "A counter to benchmark it.", 41 | }, 42 | []string{"one", "two", "three"}, 43 | ) 44 | b.ReportAllocs() 45 | b.ResetTimer() 46 | wg := sync.WaitGroup{} 47 | for i := 0; i < 10; i++ { 48 | wg.Add(1) 49 | go func() { 50 | for j := 0; j < b.N/10; j++ { 51 | m.WithLabelValues("eins", "zwei", "drei").Inc() 52 | } 53 | wg.Done() 54 | }() 55 | } 56 | wg.Wait() 57 | } 58 | 59 | func BenchmarkCounterWithMappedLabels(b *testing.B) { 60 | m := NewCounterVec( 61 | CounterOpts{ 62 | Name: "benchmark_counter", 63 | Help: "A counter to benchmark it.", 64 | }, 65 | []string{"one", "two", "three"}, 66 | ) 67 | b.ReportAllocs() 68 | b.ResetTimer() 69 | for i := 0; i < b.N; i++ { 70 | m.With(Labels{"two": "zwei", "one": "eins", "three": "drei"}).Inc() 71 | } 72 | } 73 | 74 | func BenchmarkCounterWithPreparedMappedLabels(b *testing.B) { 75 | m := NewCounterVec( 76 | CounterOpts{ 77 | Name: "benchmark_counter", 78 | Help: "A counter to benchmark it.", 79 | }, 80 | []string{"one", "two", "three"}, 81 | ) 82 | b.ReportAllocs() 83 | b.ResetTimer() 84 | labels := Labels{"two": "zwei", "one": "eins", "three": "drei"} 85 | for i := 0; i < b.N; i++ { 86 | m.With(labels).Inc() 87 | } 88 | } 89 | 90 | func BenchmarkCounterNoLabels(b *testing.B) { 91 | m := NewCounter(CounterOpts{ 92 | Name: "benchmark_counter", 93 | Help: "A counter to benchmark it.", 94 | }) 95 | b.ReportAllocs() 96 | b.ResetTimer() 97 | for i := 0; i < b.N; i++ { 98 | m.Inc() 99 | } 100 | } 101 | 102 | func BenchmarkGaugeWithLabelValues(b *testing.B) { 103 | m := NewGaugeVec( 104 | GaugeOpts{ 105 | Name: "benchmark_gauge", 106 | Help: "A gauge to benchmark it.", 107 | }, 108 | []string{"one", "two", "three"}, 109 | ) 110 | b.ReportAllocs() 111 | b.ResetTimer() 112 | for i := 0; i < b.N; i++ { 113 | m.WithLabelValues("eins", "zwei", "drei").Set(3.1415) 114 | } 115 | } 116 | 117 | func BenchmarkGaugeNoLabels(b *testing.B) { 118 | m := NewGauge(GaugeOpts{ 119 | Name: "benchmark_gauge", 120 | Help: "A gauge to benchmark it.", 121 | }) 122 | b.ReportAllocs() 123 | b.ResetTimer() 124 | for i := 0; i < b.N; i++ { 125 | m.Set(3.1415) 126 | } 127 | } 128 | 129 | func BenchmarkSummaryWithLabelValues(b *testing.B) { 130 | m := NewSummaryVec( 131 | SummaryOpts{ 132 | Name: "benchmark_summary", 133 | Help: "A summary to benchmark it.", 134 | Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, 135 | }, 136 | []string{"one", "two", "three"}, 137 | ) 138 | b.ReportAllocs() 139 | b.ResetTimer() 140 | for i := 0; i < b.N; i++ { 141 | m.WithLabelValues("eins", "zwei", "drei").Observe(3.1415) 142 | } 143 | } 144 | 145 | func BenchmarkSummaryNoLabels(b *testing.B) { 146 | m := NewSummary(SummaryOpts{ 147 | Name: "benchmark_summary", 148 | Help: "A summary to benchmark it.", 149 | Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, 150 | }, 151 | ) 152 | b.ReportAllocs() 153 | b.ResetTimer() 154 | for i := 0; i < b.N; i++ { 155 | m.Observe(3.1415) 156 | } 157 | } 158 | 159 | func BenchmarkHistogramWithLabelValues(b *testing.B) { 160 | m := NewHistogramVec( 161 | HistogramOpts{ 162 | Name: "benchmark_histogram", 163 | Help: "A histogram to benchmark it.", 164 | }, 165 | []string{"one", "two", "three"}, 166 | ) 167 | b.ReportAllocs() 168 | b.ResetTimer() 169 | for i := 0; i < b.N; i++ { 170 | m.WithLabelValues("eins", "zwei", "drei").Observe(3.1415) 171 | } 172 | } 173 | 174 | func BenchmarkHistogramNoLabels(b *testing.B) { 175 | m := NewHistogram(HistogramOpts{ 176 | Name: "benchmark_histogram", 177 | Help: "A histogram to benchmark it.", 178 | }, 179 | ) 180 | b.ReportAllocs() 181 | b.ResetTimer() 182 | for i := 0; i < b.N; i++ { 183 | m.Observe(3.1415) 184 | } 185 | } 186 | 187 | func BenchmarkParallelCounter(b *testing.B) { 188 | c := NewCounter(CounterOpts{ 189 | Name: "benchmark_counter", 190 | Help: "A Counter to benchmark it.", 191 | }) 192 | b.ReportAllocs() 193 | b.ResetTimer() 194 | b.RunParallel(func(pb *testing.PB) { 195 | for pb.Next() { 196 | c.Inc() 197 | } 198 | }) 199 | } 200 | -------------------------------------------------------------------------------- /prometheus/value.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package prometheus 15 | 16 | import ( 17 | "fmt" 18 | "sort" 19 | 20 | dto "github.com/prometheus/client_model/go" 21 | 22 | "github.com/golang/protobuf/proto" 23 | ) 24 | 25 | // ValueType is an enumeration of metric types that represent a simple value. 26 | type ValueType int 27 | 28 | // Possible values for the ValueType enum. 29 | const ( 30 | _ ValueType = iota 31 | CounterValue 32 | GaugeValue 33 | UntypedValue 34 | ) 35 | 36 | // valueFunc is a generic metric for simple values retrieved on collect time 37 | // from a function. It implements Metric and Collector. Its effective type is 38 | // determined by ValueType. This is a low-level building block used by the 39 | // library to back the implementations of CounterFunc, GaugeFunc, and 40 | // UntypedFunc. 41 | type valueFunc struct { 42 | selfCollector 43 | 44 | desc *Desc 45 | valType ValueType 46 | function func() float64 47 | labelPairs []*dto.LabelPair 48 | } 49 | 50 | // newValueFunc returns a newly allocated valueFunc with the given Desc and 51 | // ValueType. The value reported is determined by calling the given function 52 | // from within the Write method. Take into account that metric collection may 53 | // happen concurrently. If that results in concurrent calls to Write, like in 54 | // the case where a valueFunc is directly registered with Prometheus, the 55 | // provided function must be concurrency-safe. 56 | func newValueFunc(desc *Desc, valueType ValueType, function func() float64) *valueFunc { 57 | result := &valueFunc{ 58 | desc: desc, 59 | valType: valueType, 60 | function: function, 61 | labelPairs: makeLabelPairs(desc, nil), 62 | } 63 | result.init(result) 64 | return result 65 | } 66 | 67 | func (v *valueFunc) Desc() *Desc { 68 | return v.desc 69 | } 70 | 71 | func (v *valueFunc) Write(out *dto.Metric) error { 72 | return populateMetric(v.valType, v.function(), v.labelPairs, out) 73 | } 74 | 75 | // NewConstMetric returns a metric with one fixed value that cannot be 76 | // changed. Users of this package will not have much use for it in regular 77 | // operations. However, when implementing custom Collectors, it is useful as a 78 | // throw-away metric that is generated on the fly to send it to Prometheus in 79 | // the Collect method. NewConstMetric returns an error if the length of 80 | // labelValues is not consistent with the variable labels in Desc. 81 | func NewConstMetric(desc *Desc, valueType ValueType, value float64, labelValues ...string) (Metric, error) { 82 | if err := validateLabelValues(labelValues, len(desc.variableLabels)); err != nil { 83 | return nil, err 84 | } 85 | return &constMetric{ 86 | desc: desc, 87 | valType: valueType, 88 | val: value, 89 | labelPairs: makeLabelPairs(desc, labelValues), 90 | }, nil 91 | } 92 | 93 | // MustNewConstMetric is a version of NewConstMetric that panics where 94 | // NewConstMetric would have returned an error. 95 | func MustNewConstMetric(desc *Desc, valueType ValueType, value float64, labelValues ...string) Metric { 96 | m, err := NewConstMetric(desc, valueType, value, labelValues...) 97 | if err != nil { 98 | panic(err) 99 | } 100 | return m 101 | } 102 | 103 | type constMetric struct { 104 | desc *Desc 105 | valType ValueType 106 | val float64 107 | labelPairs []*dto.LabelPair 108 | } 109 | 110 | func (m *constMetric) Desc() *Desc { 111 | return m.desc 112 | } 113 | 114 | func (m *constMetric) Write(out *dto.Metric) error { 115 | return populateMetric(m.valType, m.val, m.labelPairs, out) 116 | } 117 | 118 | func populateMetric( 119 | t ValueType, 120 | v float64, 121 | labelPairs []*dto.LabelPair, 122 | m *dto.Metric, 123 | ) error { 124 | m.Label = labelPairs 125 | switch t { 126 | case CounterValue: 127 | m.Counter = &dto.Counter{Value: proto.Float64(v)} 128 | case GaugeValue: 129 | m.Gauge = &dto.Gauge{Value: proto.Float64(v)} 130 | case UntypedValue: 131 | m.Untyped = &dto.Untyped{Value: proto.Float64(v)} 132 | default: 133 | return fmt.Errorf("encountered unknown type %v", t) 134 | } 135 | return nil 136 | } 137 | 138 | func makeLabelPairs(desc *Desc, labelValues []string) []*dto.LabelPair { 139 | totalLen := len(desc.variableLabels) + len(desc.constLabelPairs) 140 | if totalLen == 0 { 141 | // Super fast path. 142 | return nil 143 | } 144 | if len(desc.variableLabels) == 0 { 145 | // Moderately fast path. 146 | return desc.constLabelPairs 147 | } 148 | labelPairs := make([]*dto.LabelPair, 0, totalLen) 149 | for i, n := range desc.variableLabels { 150 | labelPairs = append(labelPairs, &dto.LabelPair{ 151 | Name: proto.String(n), 152 | Value: proto.String(labelValues[i]), 153 | }) 154 | } 155 | labelPairs = append(labelPairs, desc.constLabelPairs...) 156 | sort.Sort(LabelPairSorter(labelPairs)) 157 | return labelPairs 158 | } 159 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Prometheus Go client library 2 | 3 | [![Build Status](https://travis-ci.org/prometheus/client_golang.svg?branch=master)](https://travis-ci.org/prometheus/client_golang) 4 | [![Go Report Card](https://goreportcard.com/badge/github.com/prometheus/client_golang)](https://goreportcard.com/report/github.com/prometheus/client_golang) 5 | [![go-doc](https://godoc.org/github.com/prometheus/client_golang?status.svg)](https://godoc.org/github.com/prometheus/client_golang) 6 | 7 | This is the [Go](http://golang.org) client library for 8 | [Prometheus](http://prometheus.io). It has two separate parts, one for 9 | instrumenting application code, and one for creating clients that talk to the 10 | Prometheus HTTP API. 11 | 12 | __This library requires Go1.7 or later.__ 13 | 14 | ## Important note about releases, versioning, tagging, stability, and your favorite Go dependency management tool 15 | 16 | While our goal is to follow [Semantic Versioning](https://semver.org/), this 17 | repository is still pre-1.0.0. To quote the 18 | [Semantic Versioning spec](https://semver.org/#spec-item-4): “Anything may 19 | change at any time. The public API should not be considered stable.” We know 20 | that this is at odds with the widespread use of this library. However, just 21 | declaring something 1.0.0 doesn't make it 1.0.0. Instead, we are working 22 | towards a 1.0.0 release that actually deserves its major version number. 23 | 24 | Having said that, we aim for always keeping the tip of master in a workable 25 | state and for only introducing ”mildly” breaking changes up to and including 26 | [v0.9.0](https://github.com/prometheus/client_golang/milestone/1). After that, 27 | a number of ”hard” breaking changes are planned, see the 28 | [v0.10.0 milestone](https://github.com/prometheus/client_golang/milestone/2), 29 | which should get the library much closer to 1.0.0 state. 30 | 31 | Dependency management in Go projects is still in flux, and there are many tools 32 | floating around. While [dep](https://golang.github.io/dep/) might develop into 33 | the de-facto standard tool, it is still officially experimental. The roadmap 34 | for this library has been laid out with a lot of sometimes painful experience 35 | in mind. We really cannot adjust it every other month to the needs of the 36 | currently most popular or most promising Go dependency management tool. The 37 | recommended course of action with dependency management tools is the following: 38 | 39 | - Do not expect strict post-1.0.0 semver semantics prior to the 1.0.0 40 | release. If your dependency management tool expects strict post-1.0.0 semver 41 | semantics, you have to wait. Sorry. 42 | - If you want absolute certainty, please lock to a specific commit. You can 43 | also lock to tags, but please don't ask for more tagging. This would suggest 44 | some release or stability testing procedure that simply is not in place. As 45 | said above, we are aiming for stability of the tip of master, but if we 46 | tagged every single commit, locking to tags would be the same as locking to 47 | commits. 48 | - If you want to get the newer features and improvements and are willing to 49 | take the minor risk of newly introduced bugs and “mild” breakage, just always 50 | update to the tip of master (which is essentially the original idea of Go 51 | dependency management). We recommend to not use features marked as 52 | _deprecated_ in this case. 53 | - Once [v0.9.0](https://github.com/prometheus/client_golang/milestone/1) is 54 | out, you could lock to v0.9.x to get bugfixes (and perhaps minor new 55 | features) while avoiding the “hard” breakage that will come with post-0.9 56 | features. 57 | 58 | ## Instrumenting applications 59 | 60 | [![code-coverage](http://gocover.io/_badge/github.com/prometheus/client_golang/prometheus)](http://gocover.io/github.com/prometheus/client_golang/prometheus) [![go-doc](https://godoc.org/github.com/prometheus/client_golang/prometheus?status.svg)](https://godoc.org/github.com/prometheus/client_golang/prometheus) 61 | 62 | The 63 | [`prometheus` directory](https://github.com/prometheus/client_golang/tree/master/prometheus) 64 | contains the instrumentation library. See the 65 | [best practices section](http://prometheus.io/docs/practices/naming/) of the 66 | Prometheus documentation to learn more about instrumenting applications. 67 | 68 | The 69 | [`examples` directory](https://github.com/prometheus/client_golang/tree/master/examples) 70 | contains simple examples of instrumented code. 71 | 72 | ## Client for the Prometheus HTTP API 73 | 74 | [![code-coverage](http://gocover.io/_badge/github.com/prometheus/client_golang/api/prometheus/v1)](http://gocover.io/github.com/prometheus/client_golang/api/prometheus/v1) [![go-doc](https://godoc.org/github.com/prometheus/client_golang/api/prometheus?status.svg)](https://godoc.org/github.com/prometheus/client_golang/api) 75 | 76 | The 77 | [`api/prometheus` directory](https://github.com/prometheus/client_golang/tree/master/api/prometheus) 78 | contains the client for the 79 | [Prometheus HTTP API](http://prometheus.io/docs/querying/api/). It allows you 80 | to write Go applications that query time series data from a Prometheus 81 | server. It is still in alpha stage. 82 | 83 | ## Where is `model`, `extraction`, and `text`? 84 | 85 | The `model` packages has been moved to 86 | [`prometheus/common/model`](https://github.com/prometheus/common/tree/master/model). 87 | 88 | The `extraction` and `text` packages are now contained in 89 | [`prometheus/common/expfmt`](https://github.com/prometheus/common/tree/master/expfmt). 90 | 91 | ## Contributing and community 92 | 93 | See the [contributing guidelines](CONTRIBUTING.md) and the 94 | [Community section](http://prometheus.io/community/) of the homepage. 95 | -------------------------------------------------------------------------------- /prometheus/gauge_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package prometheus 15 | 16 | import ( 17 | "math" 18 | "math/rand" 19 | "sync" 20 | "testing" 21 | "testing/quick" 22 | "time" 23 | 24 | dto "github.com/prometheus/client_model/go" 25 | ) 26 | 27 | func listenGaugeStream(vals, result chan float64, done chan struct{}) { 28 | var sum float64 29 | outer: 30 | for { 31 | select { 32 | case <-done: 33 | close(vals) 34 | for v := range vals { 35 | sum += v 36 | } 37 | break outer 38 | case v := <-vals: 39 | sum += v 40 | } 41 | } 42 | result <- sum 43 | close(result) 44 | } 45 | 46 | func TestGaugeConcurrency(t *testing.T) { 47 | it := func(n uint32) bool { 48 | mutations := int(n % 10000) 49 | concLevel := int(n%15 + 1) 50 | 51 | var start, end sync.WaitGroup 52 | start.Add(1) 53 | end.Add(concLevel) 54 | 55 | sStream := make(chan float64, mutations*concLevel) 56 | result := make(chan float64) 57 | done := make(chan struct{}) 58 | 59 | go listenGaugeStream(sStream, result, done) 60 | go func() { 61 | end.Wait() 62 | close(done) 63 | }() 64 | 65 | gge := NewGauge(GaugeOpts{ 66 | Name: "test_gauge", 67 | Help: "no help can be found here", 68 | }) 69 | for i := 0; i < concLevel; i++ { 70 | vals := make([]float64, mutations) 71 | for j := 0; j < mutations; j++ { 72 | vals[j] = rand.Float64() - 0.5 73 | } 74 | 75 | go func(vals []float64) { 76 | start.Wait() 77 | for _, v := range vals { 78 | sStream <- v 79 | gge.Add(v) 80 | } 81 | end.Done() 82 | }(vals) 83 | } 84 | start.Done() 85 | 86 | if expected, got := <-result, math.Float64frombits(gge.(*gauge).valBits); math.Abs(expected-got) > 0.000001 { 87 | t.Fatalf("expected approx. %f, got %f", expected, got) 88 | return false 89 | } 90 | return true 91 | } 92 | 93 | if err := quick.Check(it, nil); err != nil { 94 | t.Fatal(err) 95 | } 96 | } 97 | 98 | func TestGaugeVecConcurrency(t *testing.T) { 99 | it := func(n uint32) bool { 100 | mutations := int(n % 10000) 101 | concLevel := int(n%15 + 1) 102 | vecLength := int(n%5 + 1) 103 | 104 | var start, end sync.WaitGroup 105 | start.Add(1) 106 | end.Add(concLevel) 107 | 108 | sStreams := make([]chan float64, vecLength) 109 | results := make([]chan float64, vecLength) 110 | done := make(chan struct{}) 111 | 112 | for i := 0; i < vecLength; i++ { 113 | sStreams[i] = make(chan float64, mutations*concLevel) 114 | results[i] = make(chan float64) 115 | go listenGaugeStream(sStreams[i], results[i], done) 116 | } 117 | 118 | go func() { 119 | end.Wait() 120 | close(done) 121 | }() 122 | 123 | gge := NewGaugeVec( 124 | GaugeOpts{ 125 | Name: "test_gauge", 126 | Help: "no help can be found here", 127 | }, 128 | []string{"label"}, 129 | ) 130 | for i := 0; i < concLevel; i++ { 131 | vals := make([]float64, mutations) 132 | pick := make([]int, mutations) 133 | for j := 0; j < mutations; j++ { 134 | vals[j] = rand.Float64() - 0.5 135 | pick[j] = rand.Intn(vecLength) 136 | } 137 | 138 | go func(vals []float64) { 139 | start.Wait() 140 | for i, v := range vals { 141 | sStreams[pick[i]] <- v 142 | gge.WithLabelValues(string('A' + pick[i])).Add(v) 143 | } 144 | end.Done() 145 | }(vals) 146 | } 147 | start.Done() 148 | 149 | for i := range sStreams { 150 | if expected, got := <-results[i], math.Float64frombits(gge.WithLabelValues(string('A'+i)).(*gauge).valBits); math.Abs(expected-got) > 0.000001 { 151 | t.Fatalf("expected approx. %f, got %f", expected, got) 152 | return false 153 | } 154 | } 155 | return true 156 | } 157 | 158 | if err := quick.Check(it, nil); err != nil { 159 | t.Fatal(err) 160 | } 161 | } 162 | 163 | func TestGaugeFunc(t *testing.T) { 164 | gf := NewGaugeFunc( 165 | GaugeOpts{ 166 | Name: "test_name", 167 | Help: "test help", 168 | ConstLabels: Labels{"a": "1", "b": "2"}, 169 | }, 170 | func() float64 { return 3.1415 }, 171 | ) 172 | 173 | if expected, got := `Desc{fqName: "test_name", help: "test help", constLabels: {a="1",b="2"}, variableLabels: []}`, gf.Desc().String(); expected != got { 174 | t.Errorf("expected %q, got %q", expected, got) 175 | } 176 | 177 | m := &dto.Metric{} 178 | gf.Write(m) 179 | 180 | if expected, got := `label: label: gauge: `, m.String(); expected != got { 181 | t.Errorf("expected %q, got %q", expected, got) 182 | } 183 | } 184 | 185 | func TestGaugeSetCurrentTime(t *testing.T) { 186 | g := NewGauge(GaugeOpts{ 187 | Name: "test_name", 188 | Help: "test help", 189 | }) 190 | g.SetToCurrentTime() 191 | unixTime := float64(time.Now().Unix()) 192 | 193 | m := &dto.Metric{} 194 | g.Write(m) 195 | 196 | delta := unixTime - m.GetGauge().GetValue() 197 | // This is just a smoke test to make sure SetToCurrentTime is not 198 | // totally off. Tests with current time involved are hard... 199 | if math.Abs(delta) > 5 { 200 | t.Errorf("Gauge set to current time deviates from current time by more than 5s, delta is %f seconds", delta) 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.8.0 / 2016-08-17 2 | * [CHANGE] Registry is doing more consistency checks. This might break 3 | existing setups that used to export inconsistent metrics. 4 | * [CHANGE] Pushing to Pushgateway moved to package `push` and changed to allow 5 | arbitrary grouping. 6 | * [CHANGE] Removed `SelfCollector`. 7 | * [CHANGE] Removed `PanicOnCollectError` and `EnableCollectChecks` methods. 8 | * [CHANGE] Moved packages to the prometheus/common repo: `text`, `model`, 9 | `extraction`. 10 | * [CHANGE] Deprecated a number of functions. 11 | * [FEATURE] Allow custom registries. Added `Registerer` and `Gatherer` 12 | interfaces. 13 | * [FEATURE] Separated HTTP exposition, allowing custom HTTP handlers (package 14 | `promhttp`) and enabling the creation of other exposition mechanisms. 15 | * [FEATURE] `MustRegister` is variadic now, allowing registration of many 16 | collectors in one call. 17 | * [FEATURE] Added HTTP API v1 package. 18 | * [ENHANCEMENT] Numerous documentation improvements. 19 | * [ENHANCEMENT] Improved metric sorting. 20 | * [ENHANCEMENT] Inlined fnv64a hashing for improved performance. 21 | * [ENHANCEMENT] Several test improvements. 22 | * [BUGFIX] Handle collisions in MetricVec. 23 | 24 | ## 0.7.0 / 2015-07-27 25 | * [CHANGE] Rename ExporterLabelPrefix to ExportedLabelPrefix. 26 | * [BUGFIX] Closed gaps in metric consistency check. 27 | * [BUGFIX] Validate LabelName/LabelSet on JSON unmarshaling. 28 | * [ENHANCEMENT] Document the possibility to create "empty" metrics in 29 | a metric vector. 30 | * [ENHANCEMENT] Fix and clarify various doc comments and the README.md. 31 | * [ENHANCEMENT] (Kind of) solve "The Proxy Problem" of http.InstrumentHandler. 32 | * [ENHANCEMENT] Change responseWriterDelegator.written to int64. 33 | 34 | ## 0.6.0 / 2015-06-01 35 | * [CHANGE] Rename process_goroutines to go_goroutines. 36 | * [ENHANCEMENT] Validate label names during YAML decoding. 37 | * [ENHANCEMENT] Add LabelName regular expression. 38 | * [BUGFIX] Ensure alignment of struct members for 32-bit systems. 39 | 40 | ## 0.5.0 / 2015-05-06 41 | * [BUGFIX] Removed a weakness in the fingerprinting aka signature code. 42 | This makes fingerprinting slower and more allocation-heavy, but the 43 | weakness was too severe to be tolerated. 44 | * [CHANGE] As a result of the above, Metric.Fingerprint is now returning 45 | a different fingerprint. To keep the same fingerprint, the new method 46 | Metric.FastFingerprint was introduced, which will be used by the 47 | Prometheus server for storage purposes (implying that a collision 48 | detection has to be added, too). 49 | * [ENHANCEMENT] The Metric.Equal and Metric.Before do not depend on 50 | fingerprinting anymore, removing the possibility of an undetected 51 | fingerprint collision. 52 | * [FEATURE] The Go collector in the exposition library includes garbage 53 | collection stats. 54 | * [FEATURE] The exposition library allows to create constant "throw-away" 55 | summaries and histograms. 56 | * [CHANGE] A number of new reserved labels and prefixes. 57 | 58 | ## 0.4.0 / 2015-04-08 59 | * [CHANGE] Return NaN when Summaries have no observations yet. 60 | * [BUGFIX] Properly handle Summary decay upon Write(). 61 | * [BUGFIX] Fix the documentation link to the consumption library. 62 | * [FEATURE] Allow the metric family injection hook to merge with existing 63 | metric families. 64 | * [ENHANCEMENT] Removed cgo dependency and conditional compilation of procfs. 65 | * [MAINTENANCE] Adjusted to changes in matttproud/golang_protobuf_extensions. 66 | 67 | ## 0.3.2 / 2015-03-11 68 | * [BUGFIX] Fixed the receiver type of COWMetric.Set(). This method is 69 | only used by the Prometheus server internally. 70 | * [CLEANUP] Added licenses of vendored code left out by godep. 71 | 72 | ## 0.3.1 / 2015-03-04 73 | * [ENHANCEMENT] Switched fingerprinting functions from own free list to 74 | sync.Pool. 75 | * [CHANGE] Makefile uses Go 1.4.2 now (only relevant for examples and tests). 76 | 77 | ## 0.3.0 / 2015-03-03 78 | * [CHANGE] Changed the fingerprinting for metrics. THIS WILL INVALIDATE ALL 79 | PERSISTED FINGERPRINTS. IF YOU COMPILE THE PROMETHEUS SERVER WITH THIS 80 | VERSION, YOU HAVE TO WIPE THE PREVIOUSLY CREATED STORAGE. 81 | * [CHANGE] LabelValuesToSignature removed. (Nobody had used it, and it was 82 | arguably broken.) 83 | * [CHANGE] Vendored dependencies. Those are only used by the Makefile. If 84 | client_golang is used as a library, the vendoring will stay out of your way. 85 | * [BUGFIX] Remove a weakness in the fingerprinting for metrics. (This made 86 | the fingerprinting change above necessary.) 87 | * [FEATURE] Added new fingerprinting functions SignatureForLabels and 88 | SignatureWithoutLabels to be used by the Prometheus server. These functions 89 | require fewer allocations than the ones currently used by the server. 90 | 91 | ## 0.2.0 / 2015-02-23 92 | * [FEATURE] Introduce new Histagram metric type. 93 | * [CHANGE] Ignore process collector errors for now (better error handling 94 | pending). 95 | * [CHANGE] Use clear error interface for process pidFn. 96 | * [BUGFIX] Fix Go download links for several archs and OSes. 97 | * [ENHANCEMENT] Massively improve Gauge and Counter performance. 98 | * [ENHANCEMENT] Catch illegal label names for summaries in histograms. 99 | * [ENHANCEMENT] Reduce allocations during fingerprinting. 100 | * [ENHANCEMENT] Remove cgo dependency. procfs package will only be included if 101 | both cgo is available and the build is for an OS with procfs. 102 | * [CLEANUP] Clean up code style issues. 103 | * [CLEANUP] Mark slow test as such and exclude them from travis. 104 | * [CLEANUP] Update protobuf library package name. 105 | * [CLEANUP] Updated vendoring of beorn7/perks. 106 | 107 | ## 0.1.0 / 2015-02-02 108 | * [CLEANUP] Introduced semantic versioning and changelog. From now on, 109 | changes will be reported in this file. 110 | -------------------------------------------------------------------------------- /prometheus/push/push_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package push 15 | 16 | import ( 17 | "bytes" 18 | "io/ioutil" 19 | "net/http" 20 | "net/http/httptest" 21 | "testing" 22 | 23 | "github.com/prometheus/common/expfmt" 24 | 25 | "github.com/prometheus/client_golang/prometheus" 26 | ) 27 | 28 | func TestPush(t *testing.T) { 29 | 30 | var ( 31 | lastMethod string 32 | lastBody []byte 33 | lastPath string 34 | ) 35 | 36 | // Fake a Pushgateway that always responds with 202. 37 | pgwOK := httptest.NewServer( 38 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 39 | lastMethod = r.Method 40 | var err error 41 | lastBody, err = ioutil.ReadAll(r.Body) 42 | if err != nil { 43 | t.Fatal(err) 44 | } 45 | lastPath = r.URL.EscapedPath() 46 | w.Header().Set("Content-Type", `text/plain; charset=utf-8`) 47 | w.WriteHeader(http.StatusAccepted) 48 | }), 49 | ) 50 | defer pgwOK.Close() 51 | 52 | // Fake a Pushgateway that always responds with 500. 53 | pgwErr := httptest.NewServer( 54 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 55 | http.Error(w, "fake error", http.StatusInternalServerError) 56 | }), 57 | ) 58 | defer pgwErr.Close() 59 | 60 | metric1 := prometheus.NewCounter(prometheus.CounterOpts{ 61 | Name: "testname1", 62 | Help: "testhelp1", 63 | }) 64 | metric2 := prometheus.NewGauge(prometheus.GaugeOpts{ 65 | Name: "testname2", 66 | Help: "testhelp2", 67 | ConstLabels: prometheus.Labels{"foo": "bar", "dings": "bums"}, 68 | }) 69 | 70 | reg := prometheus.NewRegistry() 71 | reg.MustRegister(metric1) 72 | reg.MustRegister(metric2) 73 | 74 | mfs, err := reg.Gather() 75 | if err != nil { 76 | t.Fatal(err) 77 | } 78 | 79 | buf := &bytes.Buffer{} 80 | enc := expfmt.NewEncoder(buf, expfmt.FmtProtoDelim) 81 | 82 | for _, mf := range mfs { 83 | if err := enc.Encode(mf); err != nil { 84 | t.Fatal(err) 85 | } 86 | } 87 | wantBody := buf.Bytes() 88 | 89 | // Push some Collectors, all good. 90 | if err := New(pgwOK.URL, "testjob"). 91 | Collector(metric1). 92 | Collector(metric2). 93 | Push(); err != nil { 94 | t.Fatal(err) 95 | } 96 | if lastMethod != "PUT" { 97 | t.Error("want method PUT for Push, got", lastMethod) 98 | } 99 | if bytes.Compare(lastBody, wantBody) != 0 { 100 | t.Errorf("got body %v, want %v", lastBody, wantBody) 101 | } 102 | if lastPath != "/metrics/job/testjob" { 103 | t.Error("unexpected path:", lastPath) 104 | } 105 | 106 | // Add some Collectors, with nil grouping, all good. 107 | if err := New(pgwOK.URL, "testjob"). 108 | Collector(metric1). 109 | Collector(metric2). 110 | Add(); err != nil { 111 | t.Fatal(err) 112 | } 113 | if lastMethod != "POST" { 114 | t.Error("want method POST for Add, got", lastMethod) 115 | } 116 | if bytes.Compare(lastBody, wantBody) != 0 { 117 | t.Errorf("got body %v, want %v", lastBody, wantBody) 118 | } 119 | if lastPath != "/metrics/job/testjob" { 120 | t.Error("unexpected path:", lastPath) 121 | } 122 | 123 | // Push some Collectors with a broken PGW. 124 | if err := New(pgwErr.URL, "testjob"). 125 | Collector(metric1). 126 | Collector(metric2). 127 | Push(); err == nil { 128 | t.Error("push to broken Pushgateway succeeded") 129 | } else { 130 | if got, want := err.Error(), "unexpected status code 500 while pushing to "+pgwErr.URL+"/metrics/job/testjob: fake error\n"; got != want { 131 | t.Errorf("got error %q, want %q", got, want) 132 | } 133 | } 134 | 135 | // Push some Collectors with invalid grouping or job. 136 | if err := New(pgwOK.URL, "testjob"). 137 | Grouping("foo", "bums"). 138 | Collector(metric1). 139 | Collector(metric2). 140 | Push(); err == nil { 141 | t.Error("push with grouping contained in metrics succeeded") 142 | } 143 | if err := New(pgwOK.URL, "test/job"). 144 | Collector(metric1). 145 | Collector(metric2). 146 | Push(); err == nil { 147 | t.Error("push with invalid job value succeeded") 148 | } 149 | if err := New(pgwOK.URL, "testjob"). 150 | Grouping("foobar", "bu/ms"). 151 | Collector(metric1). 152 | Collector(metric2). 153 | Push(); err == nil { 154 | t.Error("push with invalid grouping succeeded") 155 | } 156 | if err := New(pgwOK.URL, "testjob"). 157 | Grouping("foo-bar", "bums"). 158 | Collector(metric1). 159 | Collector(metric2). 160 | Push(); err == nil { 161 | t.Error("push with invalid grouping succeeded") 162 | } 163 | 164 | // Push registry, all good. 165 | if err := New(pgwOK.URL, "testjob"). 166 | Gatherer(reg). 167 | Push(); err != nil { 168 | t.Fatal(err) 169 | } 170 | if lastMethod != "PUT" { 171 | t.Error("want method PUT for Push, got", lastMethod) 172 | } 173 | if bytes.Compare(lastBody, wantBody) != 0 { 174 | t.Errorf("got body %v, want %v", lastBody, wantBody) 175 | } 176 | 177 | // Add registry, all good. 178 | if err := New(pgwOK.URL, "testjob"). 179 | Grouping("a", "x"). 180 | Grouping("b", "y"). 181 | Gatherer(reg). 182 | Add(); err != nil { 183 | t.Fatal(err) 184 | } 185 | if lastMethod != "POST" { 186 | t.Error("want method POST for Add, got", lastMethod) 187 | } 188 | if bytes.Compare(lastBody, wantBody) != 0 { 189 | t.Errorf("got body %v, want %v", lastBody, wantBody) 190 | } 191 | if lastPath != "/metrics/job/testjob/a/x/b/y" && lastPath != "/metrics/job/testjob/b/y/a/x" { 192 | t.Error("unexpected path:", lastPath) 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /prometheus/metric.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package prometheus 15 | 16 | import ( 17 | "strings" 18 | 19 | dto "github.com/prometheus/client_model/go" 20 | ) 21 | 22 | const separatorByte byte = 255 23 | 24 | // A Metric models a single sample value with its meta data being exported to 25 | // Prometheus. Implementations of Metric in this package are Gauge, Counter, 26 | // Histogram, Summary, and Untyped. 27 | type Metric interface { 28 | // Desc returns the descriptor for the Metric. This method idempotently 29 | // returns the same descriptor throughout the lifetime of the 30 | // Metric. The returned descriptor is immutable by contract. A Metric 31 | // unable to describe itself must return an invalid descriptor (created 32 | // with NewInvalidDesc). 33 | Desc() *Desc 34 | // Write encodes the Metric into a "Metric" Protocol Buffer data 35 | // transmission object. 36 | // 37 | // Metric implementations must observe concurrency safety as reads of 38 | // this metric may occur at any time, and any blocking occurs at the 39 | // expense of total performance of rendering all registered 40 | // metrics. Ideally, Metric implementations should support concurrent 41 | // readers. 42 | // 43 | // While populating dto.Metric, it is the responsibility of the 44 | // implementation to ensure validity of the Metric protobuf (like valid 45 | // UTF-8 strings or syntactically valid metric and label names). It is 46 | // recommended to sort labels lexicographically. (Implementers may find 47 | // LabelPairSorter useful for that.) Callers of Write should still make 48 | // sure of sorting if they depend on it. 49 | Write(*dto.Metric) error 50 | // TODO(beorn7): The original rationale of passing in a pre-allocated 51 | // dto.Metric protobuf to save allocations has disappeared. The 52 | // signature of this method should be changed to "Write() (*dto.Metric, 53 | // error)". 54 | } 55 | 56 | // Opts bundles the options for creating most Metric types. Each metric 57 | // implementation XXX has its own XXXOpts type, but in most cases, it is just be 58 | // an alias of this type (which might change when the requirement arises.) 59 | // 60 | // It is mandatory to set Name and Help to a non-empty string. All other fields 61 | // are optional and can safely be left at their zero value. 62 | type Opts struct { 63 | // Namespace, Subsystem, and Name are components of the fully-qualified 64 | // name of the Metric (created by joining these components with 65 | // "_"). Only Name is mandatory, the others merely help structuring the 66 | // name. Note that the fully-qualified name of the metric must be a 67 | // valid Prometheus metric name. 68 | Namespace string 69 | Subsystem string 70 | Name string 71 | 72 | // Help provides information about this metric. Mandatory! 73 | // 74 | // Metrics with the same fully-qualified name must have the same Help 75 | // string. 76 | Help string 77 | 78 | // ConstLabels are used to attach fixed labels to this metric. Metrics 79 | // with the same fully-qualified name must have the same label names in 80 | // their ConstLabels. 81 | // 82 | // ConstLabels are only used rarely. In particular, do not use them to 83 | // attach the same labels to all your metrics. Those use cases are 84 | // better covered by target labels set by the scraping Prometheus 85 | // server, or by one specific metric (e.g. a build_info or a 86 | // machine_role metric). See also 87 | // https://prometheus.io/docs/instrumenting/writing_exporters/#target-labels,-not-static-scraped-labels 88 | ConstLabels Labels 89 | } 90 | 91 | // BuildFQName joins the given three name components by "_". Empty name 92 | // components are ignored. If the name parameter itself is empty, an empty 93 | // string is returned, no matter what. Metric implementations included in this 94 | // library use this function internally to generate the fully-qualified metric 95 | // name from the name component in their Opts. Users of the library will only 96 | // need this function if they implement their own Metric or instantiate a Desc 97 | // (with NewDesc) directly. 98 | func BuildFQName(namespace, subsystem, name string) string { 99 | if name == "" { 100 | return "" 101 | } 102 | switch { 103 | case namespace != "" && subsystem != "": 104 | return strings.Join([]string{namespace, subsystem, name}, "_") 105 | case namespace != "": 106 | return strings.Join([]string{namespace, name}, "_") 107 | case subsystem != "": 108 | return strings.Join([]string{subsystem, name}, "_") 109 | } 110 | return name 111 | } 112 | 113 | // LabelPairSorter implements sort.Interface. It is used to sort a slice of 114 | // dto.LabelPair pointers. This is useful for implementing the Write method of 115 | // custom metrics. 116 | type LabelPairSorter []*dto.LabelPair 117 | 118 | func (s LabelPairSorter) Len() int { 119 | return len(s) 120 | } 121 | 122 | func (s LabelPairSorter) Swap(i, j int) { 123 | s[i], s[j] = s[j], s[i] 124 | } 125 | 126 | func (s LabelPairSorter) Less(i, j int) bool { 127 | return s[i].GetName() < s[j].GetName() 128 | } 129 | 130 | type invalidMetric struct { 131 | desc *Desc 132 | err error 133 | } 134 | 135 | // NewInvalidMetric returns a metric whose Write method always returns the 136 | // provided error. It is useful if a Collector finds itself unable to collect 137 | // a metric and wishes to report an error to the registry. 138 | func NewInvalidMetric(desc *Desc, err error) Metric { 139 | return &invalidMetric{desc, err} 140 | } 141 | 142 | func (m *invalidMetric) Desc() *Desc { return m.desc } 143 | 144 | func (m *invalidMetric) Write(*dto.Metric) error { return m.err } 145 | -------------------------------------------------------------------------------- /prometheus/promhttp/instrument_client_1_8_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | // +build go1.8 15 | 16 | package promhttp 17 | 18 | import ( 19 | "log" 20 | "net/http" 21 | "testing" 22 | "time" 23 | 24 | "github.com/prometheus/client_golang/prometheus" 25 | ) 26 | 27 | func TestClientMiddlewareAPI(t *testing.T) { 28 | client := http.DefaultClient 29 | client.Timeout = 1 * time.Second 30 | 31 | reg := prometheus.NewRegistry() 32 | 33 | inFlightGauge := prometheus.NewGauge(prometheus.GaugeOpts{ 34 | Name: "client_in_flight_requests", 35 | Help: "A gauge of in-flight requests for the wrapped client.", 36 | }) 37 | 38 | counter := prometheus.NewCounterVec( 39 | prometheus.CounterOpts{ 40 | Name: "client_api_requests_total", 41 | Help: "A counter for requests from the wrapped client.", 42 | }, 43 | []string{"code", "method"}, 44 | ) 45 | 46 | dnsLatencyVec := prometheus.NewHistogramVec( 47 | prometheus.HistogramOpts{ 48 | Name: "dns_duration_seconds", 49 | Help: "Trace dns latency histogram.", 50 | Buckets: []float64{.005, .01, .025, .05}, 51 | }, 52 | []string{"event"}, 53 | ) 54 | 55 | tlsLatencyVec := prometheus.NewHistogramVec( 56 | prometheus.HistogramOpts{ 57 | Name: "tls_duration_seconds", 58 | Help: "Trace tls latency histogram.", 59 | Buckets: []float64{.05, .1, .25, .5}, 60 | }, 61 | []string{"event"}, 62 | ) 63 | 64 | histVec := prometheus.NewHistogramVec( 65 | prometheus.HistogramOpts{ 66 | Name: "request_duration_seconds", 67 | Help: "A histogram of request latencies.", 68 | Buckets: prometheus.DefBuckets, 69 | }, 70 | []string{"method"}, 71 | ) 72 | 73 | reg.MustRegister(counter, tlsLatencyVec, dnsLatencyVec, histVec, inFlightGauge) 74 | 75 | trace := &InstrumentTrace{ 76 | DNSStart: func(t float64) { 77 | dnsLatencyVec.WithLabelValues("dns_start") 78 | }, 79 | DNSDone: func(t float64) { 80 | dnsLatencyVec.WithLabelValues("dns_done") 81 | }, 82 | TLSHandshakeStart: func(t float64) { 83 | tlsLatencyVec.WithLabelValues("tls_handshake_start") 84 | }, 85 | TLSHandshakeDone: func(t float64) { 86 | tlsLatencyVec.WithLabelValues("tls_handshake_done") 87 | }, 88 | } 89 | 90 | client.Transport = InstrumentRoundTripperInFlight(inFlightGauge, 91 | InstrumentRoundTripperCounter(counter, 92 | InstrumentRoundTripperTrace(trace, 93 | InstrumentRoundTripperDuration(histVec, http.DefaultTransport), 94 | ), 95 | ), 96 | ) 97 | 98 | resp, err := client.Get("http://google.com") 99 | if err != nil { 100 | t.Fatalf("%v", err) 101 | } 102 | defer resp.Body.Close() 103 | } 104 | 105 | func ExampleInstrumentRoundTripperDuration() { 106 | client := http.DefaultClient 107 | client.Timeout = 1 * time.Second 108 | 109 | inFlightGauge := prometheus.NewGauge(prometheus.GaugeOpts{ 110 | Name: "client_in_flight_requests", 111 | Help: "A gauge of in-flight requests for the wrapped client.", 112 | }) 113 | 114 | counter := prometheus.NewCounterVec( 115 | prometheus.CounterOpts{ 116 | Name: "client_api_requests_total", 117 | Help: "A counter for requests from the wrapped client.", 118 | }, 119 | []string{"code", "method"}, 120 | ) 121 | 122 | // dnsLatencyVec uses custom buckets based on expected dns durations. 123 | // It has an instance label "event", which is set in the 124 | // DNSStart and DNSDonehook functions defined in the 125 | // InstrumentTrace struct below. 126 | dnsLatencyVec := prometheus.NewHistogramVec( 127 | prometheus.HistogramOpts{ 128 | Name: "dns_duration_seconds", 129 | Help: "Trace dns latency histogram.", 130 | Buckets: []float64{.005, .01, .025, .05}, 131 | }, 132 | []string{"event"}, 133 | ) 134 | 135 | // tlsLatencyVec uses custom buckets based on expected tls durations. 136 | // It has an instance label "event", which is set in the 137 | // TLSHandshakeStart and TLSHandshakeDone hook functions defined in the 138 | // InstrumentTrace struct below. 139 | tlsLatencyVec := prometheus.NewHistogramVec( 140 | prometheus.HistogramOpts{ 141 | Name: "tls_duration_seconds", 142 | Help: "Trace tls latency histogram.", 143 | Buckets: []float64{.05, .1, .25, .5}, 144 | }, 145 | []string{"event"}, 146 | ) 147 | 148 | // histVec has no labels, making it a zero-dimensional ObserverVec. 149 | histVec := prometheus.NewHistogramVec( 150 | prometheus.HistogramOpts{ 151 | Name: "request_duration_seconds", 152 | Help: "A histogram of request latencies.", 153 | Buckets: prometheus.DefBuckets, 154 | }, 155 | []string{}, 156 | ) 157 | 158 | // Register all of the metrics in the standard registry. 159 | prometheus.MustRegister(counter, tlsLatencyVec, dnsLatencyVec, histVec, inFlightGauge) 160 | 161 | // Define functions for the available httptrace.ClientTrace hook 162 | // functions that we want to instrument. 163 | trace := &InstrumentTrace{ 164 | DNSStart: func(t float64) { 165 | dnsLatencyVec.WithLabelValues("dns_start") 166 | }, 167 | DNSDone: func(t float64) { 168 | dnsLatencyVec.WithLabelValues("dns_done") 169 | }, 170 | TLSHandshakeStart: func(t float64) { 171 | tlsLatencyVec.WithLabelValues("tls_handshake_start") 172 | }, 173 | TLSHandshakeDone: func(t float64) { 174 | tlsLatencyVec.WithLabelValues("tls_handshake_done") 175 | }, 176 | } 177 | 178 | // Wrap the default RoundTripper with middleware. 179 | roundTripper := InstrumentRoundTripperInFlight(inFlightGauge, 180 | InstrumentRoundTripperCounter(counter, 181 | InstrumentRoundTripperTrace(trace, 182 | InstrumentRoundTripperDuration(histVec, http.DefaultTransport), 183 | ), 184 | ), 185 | ) 186 | 187 | // Set the RoundTripper on our client. 188 | client.Transport = roundTripper 189 | 190 | resp, err := client.Get("http://google.com") 191 | if err != nil { 192 | log.Printf("error: %v", err) 193 | } 194 | defer resp.Body.Close() 195 | } 196 | -------------------------------------------------------------------------------- /prometheus/promhttp/delegator_1_8.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | // +build go1.8 15 | 16 | package promhttp 17 | 18 | import ( 19 | "io" 20 | "net/http" 21 | ) 22 | 23 | type pusherDelegator struct{ *responseWriterDelegator } 24 | 25 | func (d pusherDelegator) Push(target string, opts *http.PushOptions) error { 26 | return d.ResponseWriter.(http.Pusher).Push(target, opts) 27 | } 28 | 29 | func init() { 30 | pickDelegator[pusher] = func(d *responseWriterDelegator) delegator { // 16 31 | return pusherDelegator{d} 32 | } 33 | pickDelegator[pusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 17 34 | return struct { 35 | *responseWriterDelegator 36 | http.Pusher 37 | http.CloseNotifier 38 | }{d, pusherDelegator{d}, closeNotifierDelegator{d}} 39 | } 40 | pickDelegator[pusher+flusher] = func(d *responseWriterDelegator) delegator { // 18 41 | return struct { 42 | *responseWriterDelegator 43 | http.Pusher 44 | http.Flusher 45 | }{d, pusherDelegator{d}, flusherDelegator{d}} 46 | } 47 | pickDelegator[pusher+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 19 48 | return struct { 49 | *responseWriterDelegator 50 | http.Pusher 51 | http.Flusher 52 | http.CloseNotifier 53 | }{d, pusherDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}} 54 | } 55 | pickDelegator[pusher+hijacker] = func(d *responseWriterDelegator) delegator { // 20 56 | return struct { 57 | *responseWriterDelegator 58 | http.Pusher 59 | http.Hijacker 60 | }{d, pusherDelegator{d}, hijackerDelegator{d}} 61 | } 62 | pickDelegator[pusher+hijacker+closeNotifier] = func(d *responseWriterDelegator) delegator { // 21 63 | return struct { 64 | *responseWriterDelegator 65 | http.Pusher 66 | http.Hijacker 67 | http.CloseNotifier 68 | }{d, pusherDelegator{d}, hijackerDelegator{d}, closeNotifierDelegator{d}} 69 | } 70 | pickDelegator[pusher+hijacker+flusher] = func(d *responseWriterDelegator) delegator { // 22 71 | return struct { 72 | *responseWriterDelegator 73 | http.Pusher 74 | http.Hijacker 75 | http.Flusher 76 | }{d, pusherDelegator{d}, hijackerDelegator{d}, flusherDelegator{d}} 77 | } 78 | pickDelegator[pusher+hijacker+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { //23 79 | return struct { 80 | *responseWriterDelegator 81 | http.Pusher 82 | http.Hijacker 83 | http.Flusher 84 | http.CloseNotifier 85 | }{d, pusherDelegator{d}, hijackerDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}} 86 | } 87 | pickDelegator[pusher+readerFrom] = func(d *responseWriterDelegator) delegator { // 24 88 | return struct { 89 | *responseWriterDelegator 90 | http.Pusher 91 | io.ReaderFrom 92 | }{d, pusherDelegator{d}, readerFromDelegator{d}} 93 | } 94 | pickDelegator[pusher+readerFrom+closeNotifier] = func(d *responseWriterDelegator) delegator { // 25 95 | return struct { 96 | *responseWriterDelegator 97 | http.Pusher 98 | io.ReaderFrom 99 | http.CloseNotifier 100 | }{d, pusherDelegator{d}, readerFromDelegator{d}, closeNotifierDelegator{d}} 101 | } 102 | pickDelegator[pusher+readerFrom+flusher] = func(d *responseWriterDelegator) delegator { // 26 103 | return struct { 104 | *responseWriterDelegator 105 | http.Pusher 106 | io.ReaderFrom 107 | http.Flusher 108 | }{d, pusherDelegator{d}, readerFromDelegator{d}, flusherDelegator{d}} 109 | } 110 | pickDelegator[pusher+readerFrom+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 27 111 | return struct { 112 | *responseWriterDelegator 113 | http.Pusher 114 | io.ReaderFrom 115 | http.Flusher 116 | http.CloseNotifier 117 | }{d, pusherDelegator{d}, readerFromDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}} 118 | } 119 | pickDelegator[pusher+readerFrom+hijacker] = func(d *responseWriterDelegator) delegator { // 28 120 | return struct { 121 | *responseWriterDelegator 122 | http.Pusher 123 | io.ReaderFrom 124 | http.Hijacker 125 | }{d, pusherDelegator{d}, readerFromDelegator{d}, hijackerDelegator{d}} 126 | } 127 | pickDelegator[pusher+readerFrom+hijacker+closeNotifier] = func(d *responseWriterDelegator) delegator { // 29 128 | return struct { 129 | *responseWriterDelegator 130 | http.Pusher 131 | io.ReaderFrom 132 | http.Hijacker 133 | http.CloseNotifier 134 | }{d, pusherDelegator{d}, readerFromDelegator{d}, hijackerDelegator{d}, closeNotifierDelegator{d}} 135 | } 136 | pickDelegator[pusher+readerFrom+hijacker+flusher] = func(d *responseWriterDelegator) delegator { // 30 137 | return struct { 138 | *responseWriterDelegator 139 | http.Pusher 140 | io.ReaderFrom 141 | http.Hijacker 142 | http.Flusher 143 | }{d, pusherDelegator{d}, readerFromDelegator{d}, hijackerDelegator{d}, flusherDelegator{d}} 144 | } 145 | pickDelegator[pusher+readerFrom+hijacker+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 31 146 | return struct { 147 | *responseWriterDelegator 148 | http.Pusher 149 | io.ReaderFrom 150 | http.Hijacker 151 | http.Flusher 152 | http.CloseNotifier 153 | }{d, pusherDelegator{d}, readerFromDelegator{d}, hijackerDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}} 154 | } 155 | } 156 | 157 | func newDelegator(w http.ResponseWriter, observeWriteHeaderFunc func(int)) delegator { 158 | d := &responseWriterDelegator{ 159 | ResponseWriter: w, 160 | observeWriteHeader: observeWriteHeaderFunc, 161 | } 162 | 163 | id := 0 164 | if _, ok := w.(http.CloseNotifier); ok { 165 | id += closeNotifier 166 | } 167 | if _, ok := w.(http.Flusher); ok { 168 | id += flusher 169 | } 170 | if _, ok := w.(http.Hijacker); ok { 171 | id += hijacker 172 | } 173 | if _, ok := w.(io.ReaderFrom); ok { 174 | id += readerFrom 175 | } 176 | if _, ok := w.(http.Pusher); ok { 177 | id += pusher 178 | } 179 | 180 | return pickDelegator[id](d) 181 | } 182 | -------------------------------------------------------------------------------- /prometheus/promhttp/delegator.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package promhttp 15 | 16 | import ( 17 | "bufio" 18 | "io" 19 | "net" 20 | "net/http" 21 | ) 22 | 23 | const ( 24 | closeNotifier = 1 << iota 25 | flusher 26 | hijacker 27 | readerFrom 28 | pusher 29 | ) 30 | 31 | type delegator interface { 32 | http.ResponseWriter 33 | 34 | Status() int 35 | Written() int64 36 | } 37 | 38 | type responseWriterDelegator struct { 39 | http.ResponseWriter 40 | 41 | handler, method string 42 | status int 43 | written int64 44 | wroteHeader bool 45 | observeWriteHeader func(int) 46 | } 47 | 48 | func (r *responseWriterDelegator) Status() int { 49 | return r.status 50 | } 51 | 52 | func (r *responseWriterDelegator) Written() int64 { 53 | return r.written 54 | } 55 | 56 | func (r *responseWriterDelegator) WriteHeader(code int) { 57 | r.status = code 58 | r.wroteHeader = true 59 | r.ResponseWriter.WriteHeader(code) 60 | if r.observeWriteHeader != nil { 61 | r.observeWriteHeader(code) 62 | } 63 | } 64 | 65 | func (r *responseWriterDelegator) Write(b []byte) (int, error) { 66 | if !r.wroteHeader { 67 | r.WriteHeader(http.StatusOK) 68 | } 69 | n, err := r.ResponseWriter.Write(b) 70 | r.written += int64(n) 71 | return n, err 72 | } 73 | 74 | type closeNotifierDelegator struct{ *responseWriterDelegator } 75 | type flusherDelegator struct{ *responseWriterDelegator } 76 | type hijackerDelegator struct{ *responseWriterDelegator } 77 | type readerFromDelegator struct{ *responseWriterDelegator } 78 | 79 | func (d closeNotifierDelegator) CloseNotify() <-chan bool { 80 | return d.ResponseWriter.(http.CloseNotifier).CloseNotify() 81 | } 82 | func (d flusherDelegator) Flush() { 83 | d.ResponseWriter.(http.Flusher).Flush() 84 | } 85 | func (d hijackerDelegator) Hijack() (net.Conn, *bufio.ReadWriter, error) { 86 | return d.ResponseWriter.(http.Hijacker).Hijack() 87 | } 88 | func (d readerFromDelegator) ReadFrom(re io.Reader) (int64, error) { 89 | if !d.wroteHeader { 90 | d.WriteHeader(http.StatusOK) 91 | } 92 | n, err := d.ResponseWriter.(io.ReaderFrom).ReadFrom(re) 93 | d.written += n 94 | return n, err 95 | } 96 | 97 | var pickDelegator = make([]func(*responseWriterDelegator) delegator, 32) 98 | 99 | func init() { 100 | // TODO(beorn7): Code generation would help here. 101 | pickDelegator[0] = func(d *responseWriterDelegator) delegator { // 0 102 | return d 103 | } 104 | pickDelegator[closeNotifier] = func(d *responseWriterDelegator) delegator { // 1 105 | return closeNotifierDelegator{d} 106 | } 107 | pickDelegator[flusher] = func(d *responseWriterDelegator) delegator { // 2 108 | return flusherDelegator{d} 109 | } 110 | pickDelegator[flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 3 111 | return struct { 112 | *responseWriterDelegator 113 | http.Flusher 114 | http.CloseNotifier 115 | }{d, flusherDelegator{d}, closeNotifierDelegator{d}} 116 | } 117 | pickDelegator[hijacker] = func(d *responseWriterDelegator) delegator { // 4 118 | return hijackerDelegator{d} 119 | } 120 | pickDelegator[hijacker+closeNotifier] = func(d *responseWriterDelegator) delegator { // 5 121 | return struct { 122 | *responseWriterDelegator 123 | http.Hijacker 124 | http.CloseNotifier 125 | }{d, hijackerDelegator{d}, closeNotifierDelegator{d}} 126 | } 127 | pickDelegator[hijacker+flusher] = func(d *responseWriterDelegator) delegator { // 6 128 | return struct { 129 | *responseWriterDelegator 130 | http.Hijacker 131 | http.Flusher 132 | }{d, hijackerDelegator{d}, flusherDelegator{d}} 133 | } 134 | pickDelegator[hijacker+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 7 135 | return struct { 136 | *responseWriterDelegator 137 | http.Hijacker 138 | http.Flusher 139 | http.CloseNotifier 140 | }{d, hijackerDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}} 141 | } 142 | pickDelegator[readerFrom] = func(d *responseWriterDelegator) delegator { // 8 143 | return readerFromDelegator{d} 144 | } 145 | pickDelegator[readerFrom+closeNotifier] = func(d *responseWriterDelegator) delegator { // 9 146 | return struct { 147 | *responseWriterDelegator 148 | io.ReaderFrom 149 | http.CloseNotifier 150 | }{d, readerFromDelegator{d}, closeNotifierDelegator{d}} 151 | } 152 | pickDelegator[readerFrom+flusher] = func(d *responseWriterDelegator) delegator { // 10 153 | return struct { 154 | *responseWriterDelegator 155 | io.ReaderFrom 156 | http.Flusher 157 | }{d, readerFromDelegator{d}, flusherDelegator{d}} 158 | } 159 | pickDelegator[readerFrom+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 11 160 | return struct { 161 | *responseWriterDelegator 162 | io.ReaderFrom 163 | http.Flusher 164 | http.CloseNotifier 165 | }{d, readerFromDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}} 166 | } 167 | pickDelegator[readerFrom+hijacker] = func(d *responseWriterDelegator) delegator { // 12 168 | return struct { 169 | *responseWriterDelegator 170 | io.ReaderFrom 171 | http.Hijacker 172 | }{d, readerFromDelegator{d}, hijackerDelegator{d}} 173 | } 174 | pickDelegator[readerFrom+hijacker+closeNotifier] = func(d *responseWriterDelegator) delegator { // 13 175 | return struct { 176 | *responseWriterDelegator 177 | io.ReaderFrom 178 | http.Hijacker 179 | http.CloseNotifier 180 | }{d, readerFromDelegator{d}, hijackerDelegator{d}, closeNotifierDelegator{d}} 181 | } 182 | pickDelegator[readerFrom+hijacker+flusher] = func(d *responseWriterDelegator) delegator { // 14 183 | return struct { 184 | *responseWriterDelegator 185 | io.ReaderFrom 186 | http.Hijacker 187 | http.Flusher 188 | }{d, readerFromDelegator{d}, hijackerDelegator{d}, flusherDelegator{d}} 189 | } 190 | pickDelegator[readerFrom+hijacker+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 15 191 | return struct { 192 | *responseWriterDelegator 193 | io.ReaderFrom 194 | http.Hijacker 195 | http.Flusher 196 | http.CloseNotifier 197 | }{d, readerFromDelegator{d}, hijackerDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}} 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /prometheus/counter_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package prometheus 15 | 16 | import ( 17 | "fmt" 18 | "math" 19 | "testing" 20 | 21 | dto "github.com/prometheus/client_model/go" 22 | ) 23 | 24 | func TestCounterAdd(t *testing.T) { 25 | counter := NewCounter(CounterOpts{ 26 | Name: "test", 27 | Help: "test help", 28 | ConstLabels: Labels{"a": "1", "b": "2"}, 29 | }).(*counter) 30 | counter.Inc() 31 | if expected, got := 0.0, math.Float64frombits(counter.valBits); expected != got { 32 | t.Errorf("Expected %f, got %f.", expected, got) 33 | } 34 | if expected, got := uint64(1), counter.valInt; expected != got { 35 | t.Errorf("Expected %d, got %d.", expected, got) 36 | } 37 | counter.Add(42) 38 | if expected, got := 0.0, math.Float64frombits(counter.valBits); expected != got { 39 | t.Errorf("Expected %f, got %f.", expected, got) 40 | } 41 | if expected, got := uint64(43), counter.valInt; expected != got { 42 | t.Errorf("Expected %d, got %d.", expected, got) 43 | } 44 | 45 | counter.Add(24.42) 46 | if expected, got := 24.42, math.Float64frombits(counter.valBits); expected != got { 47 | t.Errorf("Expected %f, got %f.", expected, got) 48 | } 49 | if expected, got := uint64(43), counter.valInt; expected != got { 50 | t.Errorf("Expected %d, got %d.", expected, got) 51 | } 52 | 53 | if expected, got := "counter cannot decrease in value", decreaseCounter(counter).Error(); expected != got { 54 | t.Errorf("Expected error %q, got %q.", expected, got) 55 | } 56 | 57 | m := &dto.Metric{} 58 | counter.Write(m) 59 | 60 | if expected, got := `label: label: counter: `, m.String(); expected != got { 61 | t.Errorf("expected %q, got %q", expected, got) 62 | } 63 | } 64 | 65 | func decreaseCounter(c *counter) (err error) { 66 | defer func() { 67 | if e := recover(); e != nil { 68 | err = e.(error) 69 | } 70 | }() 71 | c.Add(-1) 72 | return nil 73 | } 74 | 75 | func TestCounterVecGetMetricWithInvalidLabelValues(t *testing.T) { 76 | testCases := []struct { 77 | desc string 78 | labels Labels 79 | }{ 80 | { 81 | desc: "non utf8 label value", 82 | labels: Labels{"a": "\xFF"}, 83 | }, 84 | { 85 | desc: "not enough label values", 86 | labels: Labels{}, 87 | }, 88 | { 89 | desc: "too many label values", 90 | labels: Labels{"a": "1", "b": "2"}, 91 | }, 92 | } 93 | 94 | for _, test := range testCases { 95 | counterVec := NewCounterVec(CounterOpts{ 96 | Name: "test", 97 | }, []string{"a"}) 98 | 99 | labelValues := make([]string, len(test.labels)) 100 | for _, val := range test.labels { 101 | labelValues = append(labelValues, val) 102 | } 103 | 104 | expectPanic(t, func() { 105 | counterVec.WithLabelValues(labelValues...) 106 | }, fmt.Sprintf("WithLabelValues: expected panic because: %s", test.desc)) 107 | expectPanic(t, func() { 108 | counterVec.With(test.labels) 109 | }, fmt.Sprintf("WithLabelValues: expected panic because: %s", test.desc)) 110 | 111 | if _, err := counterVec.GetMetricWithLabelValues(labelValues...); err == nil { 112 | t.Errorf("GetMetricWithLabelValues: expected error because: %s", test.desc) 113 | } 114 | if _, err := counterVec.GetMetricWith(test.labels); err == nil { 115 | t.Errorf("GetMetricWith: expected error because: %s", test.desc) 116 | } 117 | } 118 | } 119 | 120 | func expectPanic(t *testing.T, op func(), errorMsg string) { 121 | defer func() { 122 | if err := recover(); err == nil { 123 | t.Error(errorMsg) 124 | } 125 | }() 126 | 127 | op() 128 | } 129 | 130 | func TestCounterAddInf(t *testing.T) { 131 | counter := NewCounter(CounterOpts{ 132 | Name: "test", 133 | Help: "test help", 134 | }).(*counter) 135 | 136 | counter.Inc() 137 | if expected, got := 0.0, math.Float64frombits(counter.valBits); expected != got { 138 | t.Errorf("Expected %f, got %f.", expected, got) 139 | } 140 | if expected, got := uint64(1), counter.valInt; expected != got { 141 | t.Errorf("Expected %d, got %d.", expected, got) 142 | } 143 | 144 | counter.Add(math.Inf(1)) 145 | if expected, got := math.Inf(1), math.Float64frombits(counter.valBits); expected != got { 146 | t.Errorf("valBits expected %f, got %f.", expected, got) 147 | } 148 | if expected, got := uint64(1), counter.valInt; expected != got { 149 | t.Errorf("valInts expected %d, got %d.", expected, got) 150 | } 151 | 152 | counter.Inc() 153 | if expected, got := math.Inf(1), math.Float64frombits(counter.valBits); expected != got { 154 | t.Errorf("Expected %f, got %f.", expected, got) 155 | } 156 | if expected, got := uint64(2), counter.valInt; expected != got { 157 | t.Errorf("Expected %d, got %d.", expected, got) 158 | } 159 | 160 | m := &dto.Metric{} 161 | counter.Write(m) 162 | 163 | if expected, got := `counter: `, m.String(); expected != got { 164 | t.Errorf("expected %q, got %q", expected, got) 165 | } 166 | } 167 | 168 | func TestCounterAddLarge(t *testing.T) { 169 | counter := NewCounter(CounterOpts{ 170 | Name: "test", 171 | Help: "test help", 172 | }).(*counter) 173 | 174 | // large overflows the underlying type and should therefore be stored in valBits. 175 | large := float64(math.MaxUint64 + 1) 176 | counter.Add(large) 177 | if expected, got := large, math.Float64frombits(counter.valBits); expected != got { 178 | t.Errorf("valBits expected %f, got %f.", expected, got) 179 | } 180 | if expected, got := uint64(0), counter.valInt; expected != got { 181 | t.Errorf("valInts expected %d, got %d.", expected, got) 182 | } 183 | 184 | m := &dto.Metric{} 185 | counter.Write(m) 186 | 187 | if expected, got := fmt.Sprintf("counter: ", large), m.String(); expected != got { 188 | t.Errorf("expected %q, got %q", expected, got) 189 | } 190 | } 191 | 192 | func TestCounterAddSmall(t *testing.T) { 193 | counter := NewCounter(CounterOpts{ 194 | Name: "test", 195 | Help: "test help", 196 | }).(*counter) 197 | small := 0.000000000001 198 | counter.Add(small) 199 | if expected, got := small, math.Float64frombits(counter.valBits); expected != got { 200 | t.Errorf("valBits expected %f, got %f.", expected, got) 201 | } 202 | if expected, got := uint64(0), counter.valInt; expected != got { 203 | t.Errorf("valInts expected %d, got %d.", expected, got) 204 | } 205 | 206 | m := &dto.Metric{} 207 | counter.Write(m) 208 | 209 | if expected, got := fmt.Sprintf("counter: ", small), m.String(); expected != got { 210 | t.Errorf("expected %q, got %q", expected, got) 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /prometheus/push/deprecated.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package push 15 | 16 | // This file contains only deprecated code. Remove after v0.9 is released. 17 | 18 | import ( 19 | "bytes" 20 | "fmt" 21 | "io/ioutil" 22 | "net/http" 23 | "net/url" 24 | "os" 25 | "strings" 26 | 27 | "github.com/prometheus/common/expfmt" 28 | "github.com/prometheus/common/model" 29 | 30 | "github.com/prometheus/client_golang/prometheus" 31 | ) 32 | 33 | // FromGatherer triggers a metric collection by the provided Gatherer (which is 34 | // usually implemented by a prometheus.Registry) and pushes all gathered metrics 35 | // to the Pushgateway specified by url, using the provided job name and the 36 | // (optional) further grouping labels (the grouping map may be nil). See the 37 | // Pushgateway documentation for detailed implications of the job and other 38 | // grouping labels. Neither the job name nor any grouping label value may 39 | // contain a "/". The metrics pushed must not contain a job label of their own 40 | // nor any of the grouping labels. 41 | // 42 | // You can use just host:port or ip:port as url, in which case 'http://' is 43 | // added automatically. You can also include the schema in the URL. However, do 44 | // not include the '/metrics/jobs/...' part. 45 | // 46 | // Note that all previously pushed metrics with the same job and other grouping 47 | // labels will be replaced with the metrics pushed by this call. (It uses HTTP 48 | // method 'PUT' to push to the Pushgateway.) 49 | // 50 | // Deprecated: Please use a Pusher created with New instead. 51 | func FromGatherer(job string, grouping map[string]string, url string, g prometheus.Gatherer) error { 52 | return push(job, grouping, url, g, "PUT") 53 | } 54 | 55 | // AddFromGatherer works like FromGatherer, but only previously pushed metrics 56 | // with the same name (and the same job and other grouping labels) will be 57 | // replaced. (It uses HTTP method 'POST' to push to the Pushgateway.) 58 | // 59 | // Deprecated: Please use a Pusher created with New instead. 60 | func AddFromGatherer(job string, grouping map[string]string, url string, g prometheus.Gatherer) error { 61 | return push(job, grouping, url, g, "POST") 62 | } 63 | 64 | func push(job string, grouping map[string]string, pushURL string, g prometheus.Gatherer, method string) error { 65 | if !strings.Contains(pushURL, "://") { 66 | pushURL = "http://" + pushURL 67 | } 68 | if strings.HasSuffix(pushURL, "/") { 69 | pushURL = pushURL[:len(pushURL)-1] 70 | } 71 | 72 | if strings.Contains(job, "/") { 73 | return fmt.Errorf("job contains '/': %s", job) 74 | } 75 | urlComponents := []string{url.QueryEscape(job)} 76 | for ln, lv := range grouping { 77 | if !model.LabelName(ln).IsValid() { 78 | return fmt.Errorf("grouping label has invalid name: %s", ln) 79 | } 80 | if strings.Contains(lv, "/") { 81 | return fmt.Errorf("value of grouping label %s contains '/': %s", ln, lv) 82 | } 83 | urlComponents = append(urlComponents, ln, lv) 84 | } 85 | pushURL = fmt.Sprintf("%s/metrics/job/%s", pushURL, strings.Join(urlComponents, "/")) 86 | 87 | mfs, err := g.Gather() 88 | if err != nil { 89 | return err 90 | } 91 | buf := &bytes.Buffer{} 92 | enc := expfmt.NewEncoder(buf, expfmt.FmtProtoDelim) 93 | // Check for pre-existing grouping labels: 94 | for _, mf := range mfs { 95 | for _, m := range mf.GetMetric() { 96 | for _, l := range m.GetLabel() { 97 | if l.GetName() == "job" { 98 | return fmt.Errorf("pushed metric %s (%s) already contains a job label", mf.GetName(), m) 99 | } 100 | if _, ok := grouping[l.GetName()]; ok { 101 | return fmt.Errorf( 102 | "pushed metric %s (%s) already contains grouping label %s", 103 | mf.GetName(), m, l.GetName(), 104 | ) 105 | } 106 | } 107 | } 108 | enc.Encode(mf) 109 | } 110 | req, err := http.NewRequest(method, pushURL, buf) 111 | if err != nil { 112 | return err 113 | } 114 | req.Header.Set(contentTypeHeader, string(expfmt.FmtProtoDelim)) 115 | resp, err := http.DefaultClient.Do(req) 116 | if err != nil { 117 | return err 118 | } 119 | defer resp.Body.Close() 120 | if resp.StatusCode != 202 { 121 | body, _ := ioutil.ReadAll(resp.Body) // Ignore any further error as this is for an error message only. 122 | return fmt.Errorf("unexpected status code %d while pushing to %s: %s", resp.StatusCode, pushURL, body) 123 | } 124 | return nil 125 | } 126 | 127 | // Collectors works like FromGatherer, but it does not use a Gatherer. Instead, 128 | // it collects from the provided collectors directly. It is a convenient way to 129 | // push only a few metrics. 130 | // 131 | // Deprecated: Please use a Pusher created with New instead. 132 | func Collectors(job string, grouping map[string]string, url string, collectors ...prometheus.Collector) error { 133 | return pushCollectors(job, grouping, url, "PUT", collectors...) 134 | } 135 | 136 | // AddCollectors works like AddFromGatherer, but it does not use a Gatherer. 137 | // Instead, it collects from the provided collectors directly. It is a 138 | // convenient way to push only a few metrics. 139 | // 140 | // Deprecated: Please use a Pusher created with New instead. 141 | func AddCollectors(job string, grouping map[string]string, url string, collectors ...prometheus.Collector) error { 142 | return pushCollectors(job, grouping, url, "POST", collectors...) 143 | } 144 | 145 | func pushCollectors(job string, grouping map[string]string, url, method string, collectors ...prometheus.Collector) error { 146 | r := prometheus.NewRegistry() 147 | for _, collector := range collectors { 148 | if err := r.Register(collector); err != nil { 149 | return err 150 | } 151 | } 152 | return push(job, grouping, url, r, method) 153 | } 154 | 155 | // HostnameGroupingKey returns a label map with the only entry 156 | // {instance=""}. This can be conveniently used as the grouping 157 | // parameter if metrics should be pushed with the hostname as label. The 158 | // returned map is created upon each call so that the caller is free to add more 159 | // labels to the map. 160 | // 161 | // Deprecated: Usually, metrics pushed to the Pushgateway should not be 162 | // host-centric. (You would use https://github.com/prometheus/node_exporter in 163 | // that case.) If you have the need to add the hostname to the grouping key, you 164 | // are probably doing something wrong. See 165 | // https://prometheus.io/docs/practices/pushing/ for details. 166 | func HostnameGroupingKey() map[string]string { 167 | hostname, err := os.Hostname() 168 | if err != nil { 169 | return map[string]string{"instance": "unknown"} 170 | } 171 | return map[string]string{"instance": hostname} 172 | } 173 | -------------------------------------------------------------------------------- /prometheus/desc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package prometheus 15 | 16 | import ( 17 | "errors" 18 | "fmt" 19 | "sort" 20 | "strings" 21 | 22 | "github.com/golang/protobuf/proto" 23 | "github.com/prometheus/common/model" 24 | 25 | dto "github.com/prometheus/client_model/go" 26 | ) 27 | 28 | // Desc is the descriptor used by every Prometheus Metric. It is essentially 29 | // the immutable meta-data of a Metric. The normal Metric implementations 30 | // included in this package manage their Desc under the hood. Users only have to 31 | // deal with Desc if they use advanced features like the ExpvarCollector or 32 | // custom Collectors and Metrics. 33 | // 34 | // Descriptors registered with the same registry have to fulfill certain 35 | // consistency and uniqueness criteria if they share the same fully-qualified 36 | // name: They must have the same help string and the same label names (aka label 37 | // dimensions) in each, constLabels and variableLabels, but they must differ in 38 | // the values of the constLabels. 39 | // 40 | // Descriptors that share the same fully-qualified names and the same label 41 | // values of their constLabels are considered equal. 42 | // 43 | // Use NewDesc to create new Desc instances. 44 | type Desc struct { 45 | // fqName has been built from Namespace, Subsystem, and Name. 46 | fqName string 47 | // help provides some helpful information about this metric. 48 | help string 49 | // constLabelPairs contains precalculated DTO label pairs based on 50 | // the constant labels. 51 | constLabelPairs []*dto.LabelPair 52 | // VariableLabels contains names of labels for which the metric 53 | // maintains variable values. 54 | variableLabels []string 55 | // id is a hash of the values of the ConstLabels and fqName. This 56 | // must be unique among all registered descriptors and can therefore be 57 | // used as an identifier of the descriptor. 58 | id uint64 59 | // dimHash is a hash of the label names (preset and variable) and the 60 | // Help string. Each Desc with the same fqName must have the same 61 | // dimHash. 62 | dimHash uint64 63 | // err is an error that occurred during construction. It is reported on 64 | // registration time. 65 | err error 66 | } 67 | 68 | // NewDesc allocates and initializes a new Desc. Errors are recorded in the Desc 69 | // and will be reported on registration time. variableLabels and constLabels can 70 | // be nil if no such labels should be set. fqName and help must not be empty. 71 | // 72 | // variableLabels only contain the label names. Their label values are variable 73 | // and therefore not part of the Desc. (They are managed within the Metric.) 74 | // 75 | // For constLabels, the label values are constant. Therefore, they are fully 76 | // specified in the Desc. See the Collector example for a usage pattern. 77 | func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) *Desc { 78 | d := &Desc{ 79 | fqName: fqName, 80 | help: help, 81 | variableLabels: variableLabels, 82 | } 83 | if help == "" { 84 | d.err = errors.New("empty help string") 85 | return d 86 | } 87 | if !model.IsValidMetricName(model.LabelValue(fqName)) { 88 | d.err = fmt.Errorf("%q is not a valid metric name", fqName) 89 | return d 90 | } 91 | // labelValues contains the label values of const labels (in order of 92 | // their sorted label names) plus the fqName (at position 0). 93 | labelValues := make([]string, 1, len(constLabels)+1) 94 | labelValues[0] = fqName 95 | labelNames := make([]string, 0, len(constLabels)+len(variableLabels)) 96 | labelNameSet := map[string]struct{}{} 97 | // First add only the const label names and sort them... 98 | for labelName := range constLabels { 99 | if !checkLabelName(labelName) { 100 | d.err = fmt.Errorf("%q is not a valid label name", labelName) 101 | return d 102 | } 103 | labelNames = append(labelNames, labelName) 104 | labelNameSet[labelName] = struct{}{} 105 | } 106 | sort.Strings(labelNames) 107 | // ... so that we can now add const label values in the order of their names. 108 | for _, labelName := range labelNames { 109 | labelValues = append(labelValues, constLabels[labelName]) 110 | } 111 | // Validate the const label values. They can't have a wrong cardinality, so 112 | // use in len(labelValues) as expectedNumberOfValues. 113 | if err := validateLabelValues(labelValues, len(labelValues)); err != nil { 114 | d.err = err 115 | return d 116 | } 117 | // Now add the variable label names, but prefix them with something that 118 | // cannot be in a regular label name. That prevents matching the label 119 | // dimension with a different mix between preset and variable labels. 120 | for _, labelName := range variableLabels { 121 | if !checkLabelName(labelName) { 122 | d.err = fmt.Errorf("%q is not a valid label name", labelName) 123 | return d 124 | } 125 | labelNames = append(labelNames, "$"+labelName) 126 | labelNameSet[labelName] = struct{}{} 127 | } 128 | if len(labelNames) != len(labelNameSet) { 129 | d.err = errors.New("duplicate label names") 130 | return d 131 | } 132 | 133 | vh := hashNew() 134 | for _, val := range labelValues { 135 | vh = hashAdd(vh, val) 136 | vh = hashAddByte(vh, separatorByte) 137 | } 138 | d.id = vh 139 | // Sort labelNames so that order doesn't matter for the hash. 140 | sort.Strings(labelNames) 141 | // Now hash together (in this order) the help string and the sorted 142 | // label names. 143 | lh := hashNew() 144 | lh = hashAdd(lh, help) 145 | lh = hashAddByte(lh, separatorByte) 146 | for _, labelName := range labelNames { 147 | lh = hashAdd(lh, labelName) 148 | lh = hashAddByte(lh, separatorByte) 149 | } 150 | d.dimHash = lh 151 | 152 | d.constLabelPairs = make([]*dto.LabelPair, 0, len(constLabels)) 153 | for n, v := range constLabels { 154 | d.constLabelPairs = append(d.constLabelPairs, &dto.LabelPair{ 155 | Name: proto.String(n), 156 | Value: proto.String(v), 157 | }) 158 | } 159 | sort.Sort(LabelPairSorter(d.constLabelPairs)) 160 | return d 161 | } 162 | 163 | // NewInvalidDesc returns an invalid descriptor, i.e. a descriptor with the 164 | // provided error set. If a collector returning such a descriptor is registered, 165 | // registration will fail with the provided error. NewInvalidDesc can be used by 166 | // a Collector to signal inability to describe itself. 167 | func NewInvalidDesc(err error) *Desc { 168 | return &Desc{ 169 | err: err, 170 | } 171 | } 172 | 173 | func (d *Desc) String() string { 174 | lpStrings := make([]string, 0, len(d.constLabelPairs)) 175 | for _, lp := range d.constLabelPairs { 176 | lpStrings = append( 177 | lpStrings, 178 | fmt.Sprintf("%s=%q", lp.GetName(), lp.GetValue()), 179 | ) 180 | } 181 | return fmt.Sprintf( 182 | "Desc{fqName: %q, help: %q, constLabels: {%s}, variableLabels: %v}", 183 | d.fqName, 184 | d.help, 185 | strings.Join(lpStrings, ","), 186 | d.variableLabels, 187 | ) 188 | } 189 | -------------------------------------------------------------------------------- /prometheus/graphite/bridge.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | // Package graphite provides a bridge to push Prometheus metrics to a Graphite 15 | // server. 16 | package graphite 17 | 18 | import ( 19 | "bufio" 20 | "errors" 21 | "fmt" 22 | "io" 23 | "net" 24 | "sort" 25 | "time" 26 | 27 | "github.com/prometheus/common/expfmt" 28 | "github.com/prometheus/common/model" 29 | "golang.org/x/net/context" 30 | 31 | dto "github.com/prometheus/client_model/go" 32 | 33 | "github.com/prometheus/client_golang/prometheus" 34 | ) 35 | 36 | const ( 37 | defaultInterval = 15 * time.Second 38 | millisecondsPerSecond = 1000 39 | ) 40 | 41 | // HandlerErrorHandling defines how a Handler serving metrics will handle 42 | // errors. 43 | type HandlerErrorHandling int 44 | 45 | // These constants cause handlers serving metrics to behave as described if 46 | // errors are encountered. 47 | const ( 48 | // Ignore errors and try to push as many metrics to Graphite as possible. 49 | ContinueOnError HandlerErrorHandling = iota 50 | 51 | // Abort the push to Graphite upon the first error encountered. 52 | AbortOnError 53 | ) 54 | 55 | // Config defines the Graphite bridge config. 56 | type Config struct { 57 | // The url to push data to. Required. 58 | URL string 59 | 60 | // The prefix for the pushed Graphite metrics. Defaults to empty string. 61 | Prefix string 62 | 63 | // The interval to use for pushing data to Graphite. Defaults to 15 seconds. 64 | Interval time.Duration 65 | 66 | // The timeout for pushing metrics to Graphite. Defaults to 15 seconds. 67 | Timeout time.Duration 68 | 69 | // The Gatherer to use for metrics. Defaults to prometheus.DefaultGatherer. 70 | Gatherer prometheus.Gatherer 71 | 72 | // The logger that messages are written to. Defaults to no logging. 73 | Logger Logger 74 | 75 | // ErrorHandling defines how errors are handled. Note that errors are 76 | // logged regardless of the configured ErrorHandling provided Logger 77 | // is not nil. 78 | ErrorHandling HandlerErrorHandling 79 | } 80 | 81 | // Bridge pushes metrics to the configured Graphite server. 82 | type Bridge struct { 83 | url string 84 | prefix string 85 | interval time.Duration 86 | timeout time.Duration 87 | 88 | errorHandling HandlerErrorHandling 89 | logger Logger 90 | 91 | g prometheus.Gatherer 92 | } 93 | 94 | // Logger is the minimal interface Bridge needs for logging. Note that 95 | // log.Logger from the standard library implements this interface, and it is 96 | // easy to implement by custom loggers, if they don't do so already anyway. 97 | type Logger interface { 98 | Println(v ...interface{}) 99 | } 100 | 101 | // NewBridge returns a pointer to a new Bridge struct. 102 | func NewBridge(c *Config) (*Bridge, error) { 103 | b := &Bridge{} 104 | 105 | if c.URL == "" { 106 | return nil, errors.New("missing URL") 107 | } 108 | b.url = c.URL 109 | 110 | if c.Gatherer == nil { 111 | b.g = prometheus.DefaultGatherer 112 | } else { 113 | b.g = c.Gatherer 114 | } 115 | 116 | if c.Logger != nil { 117 | b.logger = c.Logger 118 | } 119 | 120 | if c.Prefix != "" { 121 | b.prefix = c.Prefix 122 | } 123 | 124 | var z time.Duration 125 | if c.Interval == z { 126 | b.interval = defaultInterval 127 | } else { 128 | b.interval = c.Interval 129 | } 130 | 131 | if c.Timeout == z { 132 | b.timeout = defaultInterval 133 | } else { 134 | b.timeout = c.Timeout 135 | } 136 | 137 | b.errorHandling = c.ErrorHandling 138 | 139 | return b, nil 140 | } 141 | 142 | // Run starts the event loop that pushes Prometheus metrics to Graphite at the 143 | // configured interval. 144 | func (b *Bridge) Run(ctx context.Context) { 145 | ticker := time.NewTicker(b.interval) 146 | defer ticker.Stop() 147 | for { 148 | select { 149 | case <-ticker.C: 150 | if err := b.Push(); err != nil && b.logger != nil { 151 | b.logger.Println("error pushing to Graphite:", err) 152 | } 153 | case <-ctx.Done(): 154 | return 155 | } 156 | } 157 | } 158 | 159 | // Push pushes Prometheus metrics to the configured Graphite server. 160 | func (b *Bridge) Push() error { 161 | mfs, err := b.g.Gather() 162 | if err != nil || len(mfs) == 0 { 163 | switch b.errorHandling { 164 | case AbortOnError: 165 | return err 166 | case ContinueOnError: 167 | if b.logger != nil { 168 | b.logger.Println("continue on error:", err) 169 | } 170 | default: 171 | panic("unrecognized error handling value") 172 | } 173 | } 174 | 175 | conn, err := net.DialTimeout("tcp", b.url, b.timeout) 176 | if err != nil { 177 | return err 178 | } 179 | defer conn.Close() 180 | 181 | return writeMetrics(conn, mfs, b.prefix, model.Now()) 182 | } 183 | 184 | func writeMetrics(w io.Writer, mfs []*dto.MetricFamily, prefix string, now model.Time) error { 185 | vec, err := expfmt.ExtractSamples(&expfmt.DecodeOptions{ 186 | Timestamp: now, 187 | }, mfs...) 188 | if err != nil { 189 | return err 190 | } 191 | 192 | buf := bufio.NewWriter(w) 193 | for _, s := range vec { 194 | for _, c := range prefix { 195 | if _, err := buf.WriteRune(c); err != nil { 196 | return err 197 | } 198 | } 199 | if err := buf.WriteByte('.'); err != nil { 200 | return err 201 | } 202 | if err := writeMetric(buf, s.Metric); err != nil { 203 | return err 204 | } 205 | if _, err := fmt.Fprintf(buf, " %g %d\n", s.Value, int64(s.Timestamp)/millisecondsPerSecond); err != nil { 206 | return err 207 | } 208 | if err := buf.Flush(); err != nil { 209 | return err 210 | } 211 | } 212 | 213 | return nil 214 | } 215 | 216 | func writeMetric(buf *bufio.Writer, m model.Metric) error { 217 | metricName, hasName := m[model.MetricNameLabel] 218 | numLabels := len(m) - 1 219 | if !hasName { 220 | numLabels = len(m) 221 | } 222 | 223 | labelStrings := make([]string, 0, numLabels) 224 | for label, value := range m { 225 | if label != model.MetricNameLabel { 226 | labelStrings = append(labelStrings, fmt.Sprintf("%s %s", string(label), string(value))) 227 | } 228 | } 229 | 230 | var err error 231 | switch numLabels { 232 | case 0: 233 | if hasName { 234 | return writeSanitized(buf, string(metricName)) 235 | } 236 | default: 237 | sort.Strings(labelStrings) 238 | if err = writeSanitized(buf, string(metricName)); err != nil { 239 | return err 240 | } 241 | for _, s := range labelStrings { 242 | if err = buf.WriteByte('.'); err != nil { 243 | return err 244 | } 245 | if err = writeSanitized(buf, s); err != nil { 246 | return err 247 | } 248 | } 249 | } 250 | return nil 251 | } 252 | 253 | func writeSanitized(buf *bufio.Writer, s string) error { 254 | prevUnderscore := false 255 | 256 | for _, c := range s { 257 | c = replaceInvalidRune(c) 258 | if c == '_' { 259 | if prevUnderscore { 260 | continue 261 | } 262 | prevUnderscore = true 263 | } else { 264 | prevUnderscore = false 265 | } 266 | if _, err := buf.WriteRune(c); err != nil { 267 | return err 268 | } 269 | } 270 | 271 | return nil 272 | } 273 | 274 | func replaceInvalidRune(c rune) rune { 275 | if c == ' ' { 276 | return '.' 277 | } 278 | if !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || c == ':' || c == '-' || (c >= '0' && c <= '9')) { 279 | return '_' 280 | } 281 | return c 282 | } 283 | -------------------------------------------------------------------------------- /prometheus/push/push.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | // Package push provides functions to push metrics to a Pushgateway. It uses a 15 | // builder approach. Create a Pusher with New and then add the various options 16 | // by using its methods, finally calling Add or Push, like this: 17 | // 18 | // // Easy case: 19 | // push.New("http://example.org/metrics", "my_job").Gatherer(myRegistry).Push() 20 | // 21 | // // Complex case: 22 | // push.New("http://example.org/metrics", "my_job"). 23 | // Collector(myCollector1). 24 | // Collector(myCollector2). 25 | // Grouping("zone", "xy"). 26 | // Client(&myHTTPClient). 27 | // BasicAuth("top", "secret"). 28 | // Add() 29 | // 30 | // See the examples section for more detailed examples. 31 | // 32 | // See the documentation of the Pushgateway to understand the meaning of 33 | // the grouping key and the differences between Push and Add: 34 | // https://github.com/prometheus/pushgateway 35 | package push 36 | 37 | import ( 38 | "bytes" 39 | "fmt" 40 | "io/ioutil" 41 | "net/http" 42 | "net/url" 43 | "strings" 44 | 45 | "github.com/prometheus/common/expfmt" 46 | "github.com/prometheus/common/model" 47 | 48 | "github.com/prometheus/client_golang/prometheus" 49 | ) 50 | 51 | const contentTypeHeader = "Content-Type" 52 | 53 | // Pusher manages a push to the Pushgateway. Use New to create one, configure it 54 | // with its methods, and finally use the Add or Push method to push. 55 | type Pusher struct { 56 | error error 57 | 58 | url, job string 59 | grouping map[string]string 60 | 61 | gatherers prometheus.Gatherers 62 | registerer prometheus.Registerer 63 | 64 | client *http.Client 65 | useBasicAuth bool 66 | username, password string 67 | } 68 | 69 | // New creates a new Pusher to push to the provided URL with the provided job 70 | // name. You can use just host:port or ip:port as url, in which case “http://” 71 | // is added automatically. Alternatively, include the schema in the 72 | // URL. However, do not include the “/metrics/jobs/…” part. 73 | // 74 | // Note that until https://github.com/prometheus/pushgateway/issues/97 is 75 | // resolved, a “/” character in the job name is prohibited. 76 | func New(url, job string) *Pusher { 77 | var ( 78 | reg = prometheus.NewRegistry() 79 | err error 80 | ) 81 | if !strings.Contains(url, "://") { 82 | url = "http://" + url 83 | } 84 | if strings.HasSuffix(url, "/") { 85 | url = url[:len(url)-1] 86 | } 87 | if strings.Contains(job, "/") { 88 | err = fmt.Errorf("job contains '/': %s", job) 89 | } 90 | 91 | return &Pusher{ 92 | error: err, 93 | url: url, 94 | job: job, 95 | grouping: map[string]string{}, 96 | gatherers: prometheus.Gatherers{reg}, 97 | registerer: reg, 98 | client: &http.Client{}, 99 | } 100 | } 101 | 102 | // Push collects/gathers all metrics from all Collectors and Gatherers added to 103 | // this Pusher. Then, it pushes them to the Pushgateway configured while 104 | // creating this Pusher, using the configured job name and any added grouping 105 | // labels as grouping key. All previously pushed metrics with the same job and 106 | // other grouping labels will be replaced with the metrics pushed by this 107 | // call. (It uses HTTP method “PUT” to push to the Pushgateway.) 108 | // 109 | // Push returns the first error encountered by any method call (including this 110 | // one) in the lifetime of the Pusher. 111 | func (p *Pusher) Push() error { 112 | return p.push("PUT") 113 | } 114 | 115 | // Add works like push, but only previously pushed metrics with the same name 116 | // (and the same job and other grouping labels) will be replaced. (It uses HTTP 117 | // method “POST” to push to the Pushgateway.) 118 | func (p *Pusher) Add() error { 119 | return p.push("POST") 120 | } 121 | 122 | // Gatherer adds a Gatherer to the Pusher, from which metrics will be gathered 123 | // to push them to the Pushgateway. The gathered metrics must not contain a job 124 | // label of their own. 125 | // 126 | // For convenience, this method returns a pointer to the Pusher itself. 127 | func (p *Pusher) Gatherer(g prometheus.Gatherer) *Pusher { 128 | p.gatherers = append(p.gatherers, g) 129 | return p 130 | } 131 | 132 | // Collector adds a Collector to the Pusher, from which metrics will be 133 | // collected to push them to the Pushgateway. The collected metrics must not 134 | // contain a job label of their own. 135 | // 136 | // For convenience, this method returns a pointer to the Pusher itself. 137 | func (p *Pusher) Collector(c prometheus.Collector) *Pusher { 138 | if p.error == nil { 139 | p.error = p.registerer.Register(c) 140 | } 141 | return p 142 | } 143 | 144 | // Grouping adds a label pair to the grouping key of the Pusher, replacing any 145 | // previously added label pair with the same label name. Note that setting any 146 | // labels in the grouping key that are already contained in the metrics to push 147 | // will lead to an error. 148 | // 149 | // For convenience, this method returns a pointer to the Pusher itself. 150 | // 151 | // Note that until https://github.com/prometheus/pushgateway/issues/97 is 152 | // resolved, this method does not allow a “/” character in the label value. 153 | func (p *Pusher) Grouping(name, value string) *Pusher { 154 | if p.error == nil { 155 | if !model.LabelName(name).IsValid() { 156 | p.error = fmt.Errorf("grouping label has invalid name: %s", name) 157 | return p 158 | } 159 | if strings.Contains(value, "/") { 160 | p.error = fmt.Errorf("value of grouping label %s contains '/': %s", name, value) 161 | return p 162 | } 163 | p.grouping[name] = value 164 | } 165 | return p 166 | } 167 | 168 | // Client sets a custom HTTP client for the Pusher. For convenience, this method 169 | // returns a pointer to the Pusher itself. 170 | func (p *Pusher) Client(c *http.Client) *Pusher { 171 | p.client = c 172 | return p 173 | } 174 | 175 | // BasicAuth configures the Pusher to use HTTP Basic Authentication with the 176 | // provided username and password. For convenience, this method returns a 177 | // pointer to the Pusher itself. 178 | func (p *Pusher) BasicAuth(username, password string) *Pusher { 179 | p.useBasicAuth = true 180 | p.username = username 181 | p.password = password 182 | return p 183 | } 184 | 185 | func (p *Pusher) push(method string) error { 186 | if p.error != nil { 187 | return p.error 188 | } 189 | urlComponents := []string{url.QueryEscape(p.job)} 190 | for ln, lv := range p.grouping { 191 | urlComponents = append(urlComponents, ln, lv) 192 | } 193 | pushURL := fmt.Sprintf("%s/metrics/job/%s", p.url, strings.Join(urlComponents, "/")) 194 | 195 | mfs, err := p.gatherers.Gather() 196 | if err != nil { 197 | return err 198 | } 199 | buf := &bytes.Buffer{} 200 | enc := expfmt.NewEncoder(buf, expfmt.FmtProtoDelim) 201 | // Check for pre-existing grouping labels: 202 | for _, mf := range mfs { 203 | for _, m := range mf.GetMetric() { 204 | for _, l := range m.GetLabel() { 205 | if l.GetName() == "job" { 206 | return fmt.Errorf("pushed metric %s (%s) already contains a job label", mf.GetName(), m) 207 | } 208 | if _, ok := p.grouping[l.GetName()]; ok { 209 | return fmt.Errorf( 210 | "pushed metric %s (%s) already contains grouping label %s", 211 | mf.GetName(), m, l.GetName(), 212 | ) 213 | } 214 | } 215 | } 216 | enc.Encode(mf) 217 | } 218 | req, err := http.NewRequest(method, pushURL, buf) 219 | if err != nil { 220 | return err 221 | } 222 | if p.useBasicAuth { 223 | req.SetBasicAuth(p.username, p.password) 224 | } 225 | req.Header.Set(contentTypeHeader, string(expfmt.FmtProtoDelim)) 226 | resp, err := p.client.Do(req) 227 | if err != nil { 228 | return err 229 | } 230 | defer resp.Body.Close() 231 | if resp.StatusCode != 202 { 232 | body, _ := ioutil.ReadAll(resp.Body) // Ignore any further error as this is for an error message only. 233 | return fmt.Errorf("unexpected status code %d while pushing to %s: %s", resp.StatusCode, pushURL, body) 234 | } 235 | return nil 236 | } 237 | -------------------------------------------------------------------------------- /prometheus/promhttp/http_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package promhttp 15 | 16 | import ( 17 | "bytes" 18 | "errors" 19 | "log" 20 | "net/http" 21 | "net/http/httptest" 22 | "strings" 23 | "testing" 24 | "time" 25 | 26 | "github.com/prometheus/client_golang/prometheus" 27 | ) 28 | 29 | type errorCollector struct{} 30 | 31 | func (e errorCollector) Describe(ch chan<- *prometheus.Desc) { 32 | ch <- prometheus.NewDesc("invalid_metric", "not helpful", nil, nil) 33 | } 34 | 35 | func (e errorCollector) Collect(ch chan<- prometheus.Metric) { 36 | ch <- prometheus.NewInvalidMetric( 37 | prometheus.NewDesc("invalid_metric", "not helpful", nil, nil), 38 | errors.New("collect error"), 39 | ) 40 | } 41 | 42 | type blockingCollector struct { 43 | CollectStarted, Block chan struct{} 44 | } 45 | 46 | func (b blockingCollector) Describe(ch chan<- *prometheus.Desc) { 47 | ch <- prometheus.NewDesc("dummy_desc", "not helpful", nil, nil) 48 | } 49 | 50 | func (b blockingCollector) Collect(ch chan<- prometheus.Metric) { 51 | select { 52 | case b.CollectStarted <- struct{}{}: 53 | default: 54 | } 55 | // Collects nothing, just waits for a channel receive. 56 | <-b.Block 57 | } 58 | 59 | func TestHandlerErrorHandling(t *testing.T) { 60 | 61 | // Create a registry that collects a MetricFamily with two elements, 62 | // another with one, and reports an error. 63 | reg := prometheus.NewRegistry() 64 | 65 | cnt := prometheus.NewCounter(prometheus.CounterOpts{ 66 | Name: "the_count", 67 | Help: "Ah-ah-ah! Thunder and lightning!", 68 | }) 69 | reg.MustRegister(cnt) 70 | 71 | cntVec := prometheus.NewCounterVec( 72 | prometheus.CounterOpts{ 73 | Name: "name", 74 | Help: "docstring", 75 | ConstLabels: prometheus.Labels{"constname": "constvalue"}, 76 | }, 77 | []string{"labelname"}, 78 | ) 79 | cntVec.WithLabelValues("val1").Inc() 80 | cntVec.WithLabelValues("val2").Inc() 81 | reg.MustRegister(cntVec) 82 | 83 | reg.MustRegister(errorCollector{}) 84 | 85 | logBuf := &bytes.Buffer{} 86 | logger := log.New(logBuf, "", 0) 87 | 88 | writer := httptest.NewRecorder() 89 | request, _ := http.NewRequest("GET", "/", nil) 90 | request.Header.Add("Accept", "test/plain") 91 | 92 | errorHandler := HandlerFor(reg, HandlerOpts{ 93 | ErrorLog: logger, 94 | ErrorHandling: HTTPErrorOnError, 95 | }) 96 | continueHandler := HandlerFor(reg, HandlerOpts{ 97 | ErrorLog: logger, 98 | ErrorHandling: ContinueOnError, 99 | }) 100 | panicHandler := HandlerFor(reg, HandlerOpts{ 101 | ErrorLog: logger, 102 | ErrorHandling: PanicOnError, 103 | }) 104 | wantMsg := `error gathering metrics: error collecting metric Desc{fqName: "invalid_metric", help: "not helpful", constLabels: {}, variableLabels: []}: collect error 105 | ` 106 | wantErrorBody := `An error has occurred during metrics gathering: 107 | 108 | error collecting metric Desc{fqName: "invalid_metric", help: "not helpful", constLabels: {}, variableLabels: []}: collect error 109 | ` 110 | wantOKBody := `# HELP name docstring 111 | # TYPE name counter 112 | name{constname="constvalue",labelname="val1"} 1 113 | name{constname="constvalue",labelname="val2"} 1 114 | # HELP the_count Ah-ah-ah! Thunder and lightning! 115 | # TYPE the_count counter 116 | the_count 0 117 | ` 118 | 119 | errorHandler.ServeHTTP(writer, request) 120 | if got, want := writer.Code, http.StatusInternalServerError; got != want { 121 | t.Errorf("got HTTP status code %d, want %d", got, want) 122 | } 123 | if got := logBuf.String(); got != wantMsg { 124 | t.Errorf("got log message:\n%s\nwant log message:\n%s\n", got, wantMsg) 125 | } 126 | if got := writer.Body.String(); got != wantErrorBody { 127 | t.Errorf("got body:\n%s\nwant body:\n%s\n", got, wantErrorBody) 128 | } 129 | logBuf.Reset() 130 | writer.Body.Reset() 131 | writer.Code = http.StatusOK 132 | 133 | continueHandler.ServeHTTP(writer, request) 134 | if got, want := writer.Code, http.StatusOK; got != want { 135 | t.Errorf("got HTTP status code %d, want %d", got, want) 136 | } 137 | if got := logBuf.String(); got != wantMsg { 138 | t.Errorf("got log message %q, want %q", got, wantMsg) 139 | } 140 | if got := writer.Body.String(); got != wantOKBody { 141 | t.Errorf("got body %q, want %q", got, wantOKBody) 142 | } 143 | 144 | defer func() { 145 | if err := recover(); err == nil { 146 | t.Error("expected panic from panicHandler") 147 | } 148 | }() 149 | panicHandler.ServeHTTP(writer, request) 150 | } 151 | 152 | func TestInstrumentMetricHandler(t *testing.T) { 153 | reg := prometheus.NewRegistry() 154 | handler := InstrumentMetricHandler(reg, HandlerFor(reg, HandlerOpts{})) 155 | // Do it again to test idempotency. 156 | InstrumentMetricHandler(reg, HandlerFor(reg, HandlerOpts{})) 157 | writer := httptest.NewRecorder() 158 | request, _ := http.NewRequest("GET", "/", nil) 159 | request.Header.Add("Accept", "test/plain") 160 | 161 | handler.ServeHTTP(writer, request) 162 | if got, want := writer.Code, http.StatusOK; got != want { 163 | t.Errorf("got HTTP status code %d, want %d", got, want) 164 | } 165 | 166 | want := "promhttp_metric_handler_requests_in_flight 1\n" 167 | if got := writer.Body.String(); !strings.Contains(got, want) { 168 | t.Errorf("got body %q, does not contain %q", got, want) 169 | } 170 | want = "promhttp_metric_handler_requests_total{code=\"200\"} 0\n" 171 | if got := writer.Body.String(); !strings.Contains(got, want) { 172 | t.Errorf("got body %q, does not contain %q", got, want) 173 | } 174 | 175 | writer.Body.Reset() 176 | handler.ServeHTTP(writer, request) 177 | if got, want := writer.Code, http.StatusOK; got != want { 178 | t.Errorf("got HTTP status code %d, want %d", got, want) 179 | } 180 | 181 | want = "promhttp_metric_handler_requests_in_flight 1\n" 182 | if got := writer.Body.String(); !strings.Contains(got, want) { 183 | t.Errorf("got body %q, does not contain %q", got, want) 184 | } 185 | want = "promhttp_metric_handler_requests_total{code=\"200\"} 1\n" 186 | if got := writer.Body.String(); !strings.Contains(got, want) { 187 | t.Errorf("got body %q, does not contain %q", got, want) 188 | } 189 | } 190 | 191 | func TestHandlerMaxRequestsInFlight(t *testing.T) { 192 | reg := prometheus.NewRegistry() 193 | handler := HandlerFor(reg, HandlerOpts{MaxRequestsInFlight: 1}) 194 | w1 := httptest.NewRecorder() 195 | w2 := httptest.NewRecorder() 196 | w3 := httptest.NewRecorder() 197 | request, _ := http.NewRequest("GET", "/", nil) 198 | request.Header.Add("Accept", "test/plain") 199 | 200 | c := blockingCollector{Block: make(chan struct{}), CollectStarted: make(chan struct{}, 1)} 201 | reg.MustRegister(c) 202 | 203 | rq1Done := make(chan struct{}) 204 | go func() { 205 | handler.ServeHTTP(w1, request) 206 | close(rq1Done) 207 | }() 208 | <-c.CollectStarted 209 | 210 | handler.ServeHTTP(w2, request) 211 | 212 | if got, want := w2.Code, http.StatusServiceUnavailable; got != want { 213 | t.Errorf("got HTTP status code %d, want %d", got, want) 214 | } 215 | if got, want := w2.Body.String(), "Limit of concurrent requests reached (1), try again later.\n"; got != want { 216 | t.Errorf("got body %q, want %q", got, want) 217 | } 218 | 219 | close(c.Block) 220 | <-rq1Done 221 | 222 | handler.ServeHTTP(w3, request) 223 | 224 | if got, want := w3.Code, http.StatusOK; got != want { 225 | t.Errorf("got HTTP status code %d, want %d", got, want) 226 | } 227 | } 228 | 229 | func TestHandlerTimeout(t *testing.T) { 230 | reg := prometheus.NewRegistry() 231 | handler := HandlerFor(reg, HandlerOpts{Timeout: time.Millisecond}) 232 | w := httptest.NewRecorder() 233 | 234 | request, _ := http.NewRequest("GET", "/", nil) 235 | request.Header.Add("Accept", "test/plain") 236 | 237 | c := blockingCollector{Block: make(chan struct{}), CollectStarted: make(chan struct{}, 1)} 238 | reg.MustRegister(c) 239 | 240 | handler.ServeHTTP(w, request) 241 | 242 | if got, want := w.Code, http.StatusServiceUnavailable; got != want { 243 | t.Errorf("got HTTP status code %d, want %d", got, want) 244 | } 245 | if got, want := w.Body.String(), "Exceeded configured timeout of 1ms.\n"; got != want { 246 | t.Errorf("got body %q, want %q", got, want) 247 | } 248 | 249 | close(c.Block) // To not leak a goroutine. 250 | } 251 | -------------------------------------------------------------------------------- /prometheus/histogram_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package prometheus 15 | 16 | import ( 17 | "math" 18 | "math/rand" 19 | "reflect" 20 | "sort" 21 | "sync" 22 | "testing" 23 | "testing/quick" 24 | 25 | dto "github.com/prometheus/client_model/go" 26 | ) 27 | 28 | func benchmarkHistogramObserve(w int, b *testing.B) { 29 | b.StopTimer() 30 | 31 | wg := new(sync.WaitGroup) 32 | wg.Add(w) 33 | 34 | g := new(sync.WaitGroup) 35 | g.Add(1) 36 | 37 | s := NewHistogram(HistogramOpts{}) 38 | 39 | for i := 0; i < w; i++ { 40 | go func() { 41 | g.Wait() 42 | 43 | for i := 0; i < b.N; i++ { 44 | s.Observe(float64(i)) 45 | } 46 | 47 | wg.Done() 48 | }() 49 | } 50 | 51 | b.StartTimer() 52 | g.Done() 53 | wg.Wait() 54 | } 55 | 56 | func BenchmarkHistogramObserve1(b *testing.B) { 57 | benchmarkHistogramObserve(1, b) 58 | } 59 | 60 | func BenchmarkHistogramObserve2(b *testing.B) { 61 | benchmarkHistogramObserve(2, b) 62 | } 63 | 64 | func BenchmarkHistogramObserve4(b *testing.B) { 65 | benchmarkHistogramObserve(4, b) 66 | } 67 | 68 | func BenchmarkHistogramObserve8(b *testing.B) { 69 | benchmarkHistogramObserve(8, b) 70 | } 71 | 72 | func benchmarkHistogramWrite(w int, b *testing.B) { 73 | b.StopTimer() 74 | 75 | wg := new(sync.WaitGroup) 76 | wg.Add(w) 77 | 78 | g := new(sync.WaitGroup) 79 | g.Add(1) 80 | 81 | s := NewHistogram(HistogramOpts{}) 82 | 83 | for i := 0; i < 1000000; i++ { 84 | s.Observe(float64(i)) 85 | } 86 | 87 | for j := 0; j < w; j++ { 88 | outs := make([]dto.Metric, b.N) 89 | 90 | go func(o []dto.Metric) { 91 | g.Wait() 92 | 93 | for i := 0; i < b.N; i++ { 94 | s.Write(&o[i]) 95 | } 96 | 97 | wg.Done() 98 | }(outs) 99 | } 100 | 101 | b.StartTimer() 102 | g.Done() 103 | wg.Wait() 104 | } 105 | 106 | func BenchmarkHistogramWrite1(b *testing.B) { 107 | benchmarkHistogramWrite(1, b) 108 | } 109 | 110 | func BenchmarkHistogramWrite2(b *testing.B) { 111 | benchmarkHistogramWrite(2, b) 112 | } 113 | 114 | func BenchmarkHistogramWrite4(b *testing.B) { 115 | benchmarkHistogramWrite(4, b) 116 | } 117 | 118 | func BenchmarkHistogramWrite8(b *testing.B) { 119 | benchmarkHistogramWrite(8, b) 120 | } 121 | 122 | func TestHistogramNonMonotonicBuckets(t *testing.T) { 123 | testCases := map[string][]float64{ 124 | "not strictly monotonic": {1, 2, 2, 3}, 125 | "not monotonic at all": {1, 2, 4, 3, 5}, 126 | "have +Inf in the middle": {1, 2, math.Inf(+1), 3}, 127 | } 128 | for name, buckets := range testCases { 129 | func() { 130 | defer func() { 131 | if r := recover(); r == nil { 132 | t.Errorf("Buckets %v are %s but NewHistogram did not panic.", buckets, name) 133 | } 134 | }() 135 | _ = NewHistogram(HistogramOpts{ 136 | Name: "test_histogram", 137 | Help: "helpless", 138 | Buckets: buckets, 139 | }) 140 | }() 141 | } 142 | } 143 | 144 | // Intentionally adding +Inf here to test if that case is handled correctly. 145 | // Also, getCumulativeCounts depends on it. 146 | var testBuckets = []float64{-2, -1, -0.5, 0, 0.5, 1, 2, math.Inf(+1)} 147 | 148 | func TestHistogramConcurrency(t *testing.T) { 149 | if testing.Short() { 150 | t.Skip("Skipping test in short mode.") 151 | } 152 | 153 | rand.Seed(42) 154 | 155 | it := func(n uint32) bool { 156 | mutations := int(n%1e4 + 1e4) 157 | concLevel := int(n%5 + 1) 158 | total := mutations * concLevel 159 | 160 | var start, end sync.WaitGroup 161 | start.Add(1) 162 | end.Add(concLevel) 163 | 164 | sum := NewHistogram(HistogramOpts{ 165 | Name: "test_histogram", 166 | Help: "helpless", 167 | Buckets: testBuckets, 168 | }) 169 | 170 | allVars := make([]float64, total) 171 | var sampleSum float64 172 | for i := 0; i < concLevel; i++ { 173 | vals := make([]float64, mutations) 174 | for j := 0; j < mutations; j++ { 175 | v := rand.NormFloat64() 176 | vals[j] = v 177 | allVars[i*mutations+j] = v 178 | sampleSum += v 179 | } 180 | 181 | go func(vals []float64) { 182 | start.Wait() 183 | for _, v := range vals { 184 | sum.Observe(v) 185 | } 186 | end.Done() 187 | }(vals) 188 | } 189 | sort.Float64s(allVars) 190 | start.Done() 191 | end.Wait() 192 | 193 | m := &dto.Metric{} 194 | sum.Write(m) 195 | if got, want := int(*m.Histogram.SampleCount), total; got != want { 196 | t.Errorf("got sample count %d, want %d", got, want) 197 | } 198 | if got, want := *m.Histogram.SampleSum, sampleSum; math.Abs((got-want)/want) > 0.001 { 199 | t.Errorf("got sample sum %f, want %f", got, want) 200 | } 201 | 202 | wantCounts := getCumulativeCounts(allVars) 203 | 204 | if got, want := len(m.Histogram.Bucket), len(testBuckets)-1; got != want { 205 | t.Errorf("got %d buckets in protobuf, want %d", got, want) 206 | } 207 | for i, wantBound := range testBuckets { 208 | if i == len(testBuckets)-1 { 209 | break // No +Inf bucket in protobuf. 210 | } 211 | if gotBound := *m.Histogram.Bucket[i].UpperBound; gotBound != wantBound { 212 | t.Errorf("got bound %f, want %f", gotBound, wantBound) 213 | } 214 | if gotCount, wantCount := *m.Histogram.Bucket[i].CumulativeCount, wantCounts[i]; gotCount != wantCount { 215 | t.Errorf("got count %d, want %d", gotCount, wantCount) 216 | } 217 | } 218 | return true 219 | } 220 | 221 | if err := quick.Check(it, nil); err != nil { 222 | t.Error(err) 223 | } 224 | } 225 | 226 | func TestHistogramVecConcurrency(t *testing.T) { 227 | if testing.Short() { 228 | t.Skip("Skipping test in short mode.") 229 | } 230 | 231 | rand.Seed(42) 232 | 233 | objectives := make([]float64, 0, len(DefObjectives)) 234 | for qu := range DefObjectives { 235 | 236 | objectives = append(objectives, qu) 237 | } 238 | sort.Float64s(objectives) 239 | 240 | it := func(n uint32) bool { 241 | mutations := int(n%1e4 + 1e4) 242 | concLevel := int(n%7 + 1) 243 | vecLength := int(n%3 + 1) 244 | 245 | var start, end sync.WaitGroup 246 | start.Add(1) 247 | end.Add(concLevel) 248 | 249 | his := NewHistogramVec( 250 | HistogramOpts{ 251 | Name: "test_histogram", 252 | Help: "helpless", 253 | Buckets: []float64{-2, -1, -0.5, 0, 0.5, 1, 2, math.Inf(+1)}, 254 | }, 255 | []string{"label"}, 256 | ) 257 | 258 | allVars := make([][]float64, vecLength) 259 | sampleSums := make([]float64, vecLength) 260 | for i := 0; i < concLevel; i++ { 261 | vals := make([]float64, mutations) 262 | picks := make([]int, mutations) 263 | for j := 0; j < mutations; j++ { 264 | v := rand.NormFloat64() 265 | vals[j] = v 266 | pick := rand.Intn(vecLength) 267 | picks[j] = pick 268 | allVars[pick] = append(allVars[pick], v) 269 | sampleSums[pick] += v 270 | } 271 | 272 | go func(vals []float64) { 273 | start.Wait() 274 | for i, v := range vals { 275 | his.WithLabelValues(string('A' + picks[i])).Observe(v) 276 | } 277 | end.Done() 278 | }(vals) 279 | } 280 | for _, vars := range allVars { 281 | sort.Float64s(vars) 282 | } 283 | start.Done() 284 | end.Wait() 285 | 286 | for i := 0; i < vecLength; i++ { 287 | m := &dto.Metric{} 288 | s := his.WithLabelValues(string('A' + i)) 289 | s.(Histogram).Write(m) 290 | 291 | if got, want := len(m.Histogram.Bucket), len(testBuckets)-1; got != want { 292 | t.Errorf("got %d buckets in protobuf, want %d", got, want) 293 | } 294 | if got, want := int(*m.Histogram.SampleCount), len(allVars[i]); got != want { 295 | t.Errorf("got sample count %d, want %d", got, want) 296 | } 297 | if got, want := *m.Histogram.SampleSum, sampleSums[i]; math.Abs((got-want)/want) > 0.001 { 298 | t.Errorf("got sample sum %f, want %f", got, want) 299 | } 300 | 301 | wantCounts := getCumulativeCounts(allVars[i]) 302 | 303 | for j, wantBound := range testBuckets { 304 | if j == len(testBuckets)-1 { 305 | break // No +Inf bucket in protobuf. 306 | } 307 | if gotBound := *m.Histogram.Bucket[j].UpperBound; gotBound != wantBound { 308 | t.Errorf("got bound %f, want %f", gotBound, wantBound) 309 | } 310 | if gotCount, wantCount := *m.Histogram.Bucket[j].CumulativeCount, wantCounts[j]; gotCount != wantCount { 311 | t.Errorf("got count %d, want %d", gotCount, wantCount) 312 | } 313 | } 314 | } 315 | return true 316 | } 317 | 318 | if err := quick.Check(it, nil); err != nil { 319 | t.Error(err) 320 | } 321 | } 322 | 323 | func getCumulativeCounts(vars []float64) []uint64 { 324 | counts := make([]uint64, len(testBuckets)) 325 | for _, v := range vars { 326 | for i := len(testBuckets) - 1; i >= 0; i-- { 327 | if v > testBuckets[i] { 328 | break 329 | } 330 | counts[i]++ 331 | } 332 | } 333 | return counts 334 | } 335 | 336 | func TestBuckets(t *testing.T) { 337 | got := LinearBuckets(-15, 5, 6) 338 | want := []float64{-15, -10, -5, 0, 5, 10} 339 | if !reflect.DeepEqual(got, want) { 340 | t.Errorf("linear buckets: got %v, want %v", got, want) 341 | } 342 | 343 | got = ExponentialBuckets(100, 1.2, 3) 344 | want = []float64{100, 120, 144} 345 | if !reflect.DeepEqual(got, want) { 346 | t.Errorf("linear buckets: got %v, want %v", got, want) 347 | } 348 | } 349 | -------------------------------------------------------------------------------- /prometheus/graphite/bridge_test.go: -------------------------------------------------------------------------------- 1 | package graphite 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "io" 8 | "log" 9 | "net" 10 | "os" 11 | "regexp" 12 | "testing" 13 | "time" 14 | 15 | "github.com/prometheus/common/model" 16 | "golang.org/x/net/context" 17 | 18 | "github.com/prometheus/client_golang/prometheus" 19 | ) 20 | 21 | func TestSanitize(t *testing.T) { 22 | testCases := []struct { 23 | in, out string 24 | }{ 25 | {in: "hello", out: "hello"}, 26 | {in: "hE/l1o", out: "hE_l1o"}, 27 | {in: "he,*ll(.o", out: "he_ll_o"}, 28 | {in: "hello_there%^&", out: "hello_there_"}, 29 | {in: "hell-.o", out: "hell-_o"}, 30 | } 31 | 32 | var buf bytes.Buffer 33 | w := bufio.NewWriter(&buf) 34 | 35 | for i, tc := range testCases { 36 | if err := writeSanitized(w, tc.in); err != nil { 37 | t.Fatalf("write failed: %v", err) 38 | } 39 | if err := w.Flush(); err != nil { 40 | t.Fatalf("flush failed: %v", err) 41 | } 42 | 43 | if want, got := tc.out, buf.String(); want != got { 44 | t.Fatalf("test case index %d: got sanitized string %s, want %s", i, got, want) 45 | } 46 | 47 | buf.Reset() 48 | } 49 | } 50 | 51 | func TestWriteSummary(t *testing.T) { 52 | sumVec := prometheus.NewSummaryVec( 53 | prometheus.SummaryOpts{ 54 | Name: "name", 55 | Help: "docstring", 56 | ConstLabels: prometheus.Labels{"constname": "constvalue"}, 57 | Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, 58 | }, 59 | []string{"labelname"}, 60 | ) 61 | 62 | sumVec.WithLabelValues("val1").Observe(float64(10)) 63 | sumVec.WithLabelValues("val1").Observe(float64(20)) 64 | sumVec.WithLabelValues("val1").Observe(float64(30)) 65 | sumVec.WithLabelValues("val2").Observe(float64(20)) 66 | sumVec.WithLabelValues("val2").Observe(float64(30)) 67 | sumVec.WithLabelValues("val2").Observe(float64(40)) 68 | 69 | reg := prometheus.NewRegistry() 70 | reg.MustRegister(sumVec) 71 | 72 | mfs, err := reg.Gather() 73 | if err != nil { 74 | t.Fatalf("error: %v", err) 75 | } 76 | 77 | testCases := []struct { 78 | prefix string 79 | }{ 80 | {prefix: "prefix"}, 81 | {prefix: "pre/fix"}, 82 | {prefix: "pre.fix"}, 83 | } 84 | 85 | const want = `%s.name.constname.constvalue.labelname.val1.quantile.0_5 20 1477043 86 | %s.name.constname.constvalue.labelname.val1.quantile.0_9 30 1477043 87 | %s.name.constname.constvalue.labelname.val1.quantile.0_99 30 1477043 88 | %s.name_sum.constname.constvalue.labelname.val1 60 1477043 89 | %s.name_count.constname.constvalue.labelname.val1 3 1477043 90 | %s.name.constname.constvalue.labelname.val2.quantile.0_5 30 1477043 91 | %s.name.constname.constvalue.labelname.val2.quantile.0_9 40 1477043 92 | %s.name.constname.constvalue.labelname.val2.quantile.0_99 40 1477043 93 | %s.name_sum.constname.constvalue.labelname.val2 90 1477043 94 | %s.name_count.constname.constvalue.labelname.val2 3 1477043 95 | ` 96 | for i, tc := range testCases { 97 | 98 | now := model.Time(1477043083) 99 | var buf bytes.Buffer 100 | err = writeMetrics(&buf, mfs, tc.prefix, now) 101 | if err != nil { 102 | t.Fatalf("error: %v", err) 103 | } 104 | 105 | wantWithPrefix := fmt.Sprintf(want, 106 | tc.prefix, tc.prefix, tc.prefix, tc.prefix, tc.prefix, 107 | tc.prefix, tc.prefix, tc.prefix, tc.prefix, tc.prefix, 108 | ) 109 | if got := buf.String(); wantWithPrefix != got { 110 | t.Fatalf("test case index %d: wanted \n%s\n, got \n%s\n", i, wantWithPrefix, got) 111 | } 112 | } 113 | } 114 | 115 | func TestWriteHistogram(t *testing.T) { 116 | histVec := prometheus.NewHistogramVec( 117 | prometheus.HistogramOpts{ 118 | Name: "name", 119 | Help: "docstring", 120 | ConstLabels: prometheus.Labels{"constname": "constvalue"}, 121 | Buckets: []float64{0.01, 0.02, 0.05, 0.1}, 122 | }, 123 | []string{"labelname"}, 124 | ) 125 | 126 | histVec.WithLabelValues("val1").Observe(float64(10)) 127 | histVec.WithLabelValues("val1").Observe(float64(20)) 128 | histVec.WithLabelValues("val1").Observe(float64(30)) 129 | histVec.WithLabelValues("val2").Observe(float64(20)) 130 | histVec.WithLabelValues("val2").Observe(float64(30)) 131 | histVec.WithLabelValues("val2").Observe(float64(40)) 132 | 133 | reg := prometheus.NewRegistry() 134 | reg.MustRegister(histVec) 135 | 136 | mfs, err := reg.Gather() 137 | if err != nil { 138 | t.Fatalf("error: %v", err) 139 | } 140 | 141 | now := model.Time(1477043083) 142 | var buf bytes.Buffer 143 | err = writeMetrics(&buf, mfs, "prefix", now) 144 | if err != nil { 145 | t.Fatalf("error: %v", err) 146 | } 147 | 148 | want := `prefix.name_bucket.constname.constvalue.labelname.val1.le.0_01 0 1477043 149 | prefix.name_bucket.constname.constvalue.labelname.val1.le.0_02 0 1477043 150 | prefix.name_bucket.constname.constvalue.labelname.val1.le.0_05 0 1477043 151 | prefix.name_bucket.constname.constvalue.labelname.val1.le.0_1 0 1477043 152 | prefix.name_sum.constname.constvalue.labelname.val1 60 1477043 153 | prefix.name_count.constname.constvalue.labelname.val1 3 1477043 154 | prefix.name_bucket.constname.constvalue.labelname.val1.le._Inf 3 1477043 155 | prefix.name_bucket.constname.constvalue.labelname.val2.le.0_01 0 1477043 156 | prefix.name_bucket.constname.constvalue.labelname.val2.le.0_02 0 1477043 157 | prefix.name_bucket.constname.constvalue.labelname.val2.le.0_05 0 1477043 158 | prefix.name_bucket.constname.constvalue.labelname.val2.le.0_1 0 1477043 159 | prefix.name_sum.constname.constvalue.labelname.val2 90 1477043 160 | prefix.name_count.constname.constvalue.labelname.val2 3 1477043 161 | prefix.name_bucket.constname.constvalue.labelname.val2.le._Inf 3 1477043 162 | ` 163 | if got := buf.String(); want != got { 164 | t.Fatalf("wanted \n%s\n, got \n%s\n", want, got) 165 | } 166 | } 167 | 168 | func TestToReader(t *testing.T) { 169 | cntVec := prometheus.NewCounterVec( 170 | prometheus.CounterOpts{ 171 | Name: "name", 172 | Help: "docstring", 173 | ConstLabels: prometheus.Labels{"constname": "constvalue"}, 174 | }, 175 | []string{"labelname"}, 176 | ) 177 | cntVec.WithLabelValues("val1").Inc() 178 | cntVec.WithLabelValues("val2").Inc() 179 | 180 | reg := prometheus.NewRegistry() 181 | reg.MustRegister(cntVec) 182 | 183 | want := `prefix.name.constname.constvalue.labelname.val1 1 1477043 184 | prefix.name.constname.constvalue.labelname.val2 1 1477043 185 | ` 186 | mfs, err := reg.Gather() 187 | if err != nil { 188 | t.Fatalf("error: %v", err) 189 | } 190 | 191 | now := model.Time(1477043083) 192 | var buf bytes.Buffer 193 | err = writeMetrics(&buf, mfs, "prefix", now) 194 | if err != nil { 195 | t.Fatalf("error: %v", err) 196 | } 197 | 198 | if got := buf.String(); want != got { 199 | t.Fatalf("wanted \n%s\n, got \n%s\n", want, got) 200 | } 201 | } 202 | 203 | func TestPush(t *testing.T) { 204 | reg := prometheus.NewRegistry() 205 | cntVec := prometheus.NewCounterVec( 206 | prometheus.CounterOpts{ 207 | Name: "name", 208 | Help: "docstring", 209 | ConstLabels: prometheus.Labels{"constname": "constvalue"}, 210 | }, 211 | []string{"labelname"}, 212 | ) 213 | cntVec.WithLabelValues("val1").Inc() 214 | cntVec.WithLabelValues("val2").Inc() 215 | reg.MustRegister(cntVec) 216 | 217 | host := "localhost" 218 | port := ":56789" 219 | b, err := NewBridge(&Config{ 220 | URL: host + port, 221 | Gatherer: reg, 222 | Prefix: "prefix", 223 | }) 224 | if err != nil { 225 | t.Fatalf("error creating bridge: %v", err) 226 | } 227 | 228 | nmg, err := newMockGraphite(port) 229 | if err != nil { 230 | t.Fatalf("error creating mock graphite: %v", err) 231 | } 232 | defer nmg.Close() 233 | 234 | err = b.Push() 235 | if err != nil { 236 | t.Fatalf("error pushing: %v", err) 237 | } 238 | 239 | wants := []string{ 240 | "prefix.name.constname.constvalue.labelname.val1 1", 241 | "prefix.name.constname.constvalue.labelname.val2 1", 242 | } 243 | 244 | select { 245 | case got := <-nmg.readc: 246 | for _, want := range wants { 247 | matched, err := regexp.MatchString(want, got) 248 | if err != nil { 249 | t.Fatalf("error pushing: %v", err) 250 | } 251 | if !matched { 252 | t.Fatalf("missing metric:\nno match for %s received by server:\n%s", want, got) 253 | } 254 | } 255 | return 256 | case err := <-nmg.errc: 257 | t.Fatalf("error reading push: %v", err) 258 | case <-time.After(50 * time.Millisecond): 259 | t.Fatalf("no result from graphite server") 260 | } 261 | } 262 | 263 | func newMockGraphite(port string) (*mockGraphite, error) { 264 | readc := make(chan string) 265 | errc := make(chan error) 266 | ln, err := net.Listen("tcp", port) 267 | if err != nil { 268 | return nil, err 269 | } 270 | 271 | go func() { 272 | conn, err := ln.Accept() 273 | if err != nil { 274 | errc <- err 275 | } 276 | var b bytes.Buffer 277 | io.Copy(&b, conn) 278 | readc <- b.String() 279 | }() 280 | 281 | return &mockGraphite{ 282 | readc: readc, 283 | errc: errc, 284 | Listener: ln, 285 | }, nil 286 | } 287 | 288 | type mockGraphite struct { 289 | readc chan string 290 | errc chan error 291 | 292 | net.Listener 293 | } 294 | 295 | func ExampleBridge() { 296 | b, err := NewBridge(&Config{ 297 | URL: "graphite.example.org:3099", 298 | Gatherer: prometheus.DefaultGatherer, 299 | Prefix: "prefix", 300 | Interval: 15 * time.Second, 301 | Timeout: 10 * time.Second, 302 | ErrorHandling: AbortOnError, 303 | Logger: log.New(os.Stdout, "graphite bridge: ", log.Lshortfile), 304 | }) 305 | if err != nil { 306 | panic(err) 307 | } 308 | 309 | go func() { 310 | // Start something in a goroutine that uses metrics. 311 | }() 312 | 313 | // Push initial metrics to Graphite. Fail fast if the push fails. 314 | if err := b.Push(); err != nil { 315 | panic(err) 316 | } 317 | 318 | // Create a Context to control stopping the Run() loop that pushes 319 | // metrics to Graphite. 320 | ctx, cancel := context.WithCancel(context.Background()) 321 | defer cancel() 322 | 323 | // Start pushing metrics to Graphite in the Run() loop. 324 | b.Run(ctx) 325 | } 326 | -------------------------------------------------------------------------------- /prometheus/go_collector.go: -------------------------------------------------------------------------------- 1 | package prometheus 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "runtime/debug" 7 | "time" 8 | ) 9 | 10 | type goCollector struct { 11 | goroutinesDesc *Desc 12 | threadsDesc *Desc 13 | gcDesc *Desc 14 | goInfoDesc *Desc 15 | 16 | // metrics to describe and collect 17 | metrics memStatsMetrics 18 | } 19 | 20 | // NewGoCollector returns a collector which exports metrics about the current Go 21 | // process. This includes memory stats. To collect those, runtime.ReadMemStats 22 | // is called. This causes a stop-the-world, which is very short with Go1.9+ 23 | // (~25µs). However, with older Go versions, the stop-the-world duration depends 24 | // on the heap size and can be quite significant (~1.7 ms/GiB as per 25 | // https://go-review.googlesource.com/c/go/+/34937). 26 | func NewGoCollector() Collector { 27 | return &goCollector{ 28 | goroutinesDesc: NewDesc( 29 | "go_goroutines", 30 | "Number of goroutines that currently exist.", 31 | nil, nil), 32 | threadsDesc: NewDesc( 33 | "go_threads", 34 | "Number of OS threads created.", 35 | nil, nil), 36 | gcDesc: NewDesc( 37 | "go_gc_duration_seconds", 38 | "A summary of the GC invocation durations.", 39 | nil, nil), 40 | goInfoDesc: NewDesc( 41 | "go_info", 42 | "Information about the Go environment.", 43 | nil, Labels{"version": runtime.Version()}), 44 | metrics: memStatsMetrics{ 45 | { 46 | desc: NewDesc( 47 | memstatNamespace("alloc_bytes"), 48 | "Number of bytes allocated and still in use.", 49 | nil, nil, 50 | ), 51 | eval: func(ms *runtime.MemStats) float64 { return float64(ms.Alloc) }, 52 | valType: GaugeValue, 53 | }, { 54 | desc: NewDesc( 55 | memstatNamespace("alloc_bytes_total"), 56 | "Total number of bytes allocated, even if freed.", 57 | nil, nil, 58 | ), 59 | eval: func(ms *runtime.MemStats) float64 { return float64(ms.TotalAlloc) }, 60 | valType: CounterValue, 61 | }, { 62 | desc: NewDesc( 63 | memstatNamespace("sys_bytes"), 64 | "Number of bytes obtained from system.", 65 | nil, nil, 66 | ), 67 | eval: func(ms *runtime.MemStats) float64 { return float64(ms.Sys) }, 68 | valType: GaugeValue, 69 | }, { 70 | desc: NewDesc( 71 | memstatNamespace("lookups_total"), 72 | "Total number of pointer lookups.", 73 | nil, nil, 74 | ), 75 | eval: func(ms *runtime.MemStats) float64 { return float64(ms.Lookups) }, 76 | valType: CounterValue, 77 | }, { 78 | desc: NewDesc( 79 | memstatNamespace("mallocs_total"), 80 | "Total number of mallocs.", 81 | nil, nil, 82 | ), 83 | eval: func(ms *runtime.MemStats) float64 { return float64(ms.Mallocs) }, 84 | valType: CounterValue, 85 | }, { 86 | desc: NewDesc( 87 | memstatNamespace("frees_total"), 88 | "Total number of frees.", 89 | nil, nil, 90 | ), 91 | eval: func(ms *runtime.MemStats) float64 { return float64(ms.Frees) }, 92 | valType: CounterValue, 93 | }, { 94 | desc: NewDesc( 95 | memstatNamespace("heap_alloc_bytes"), 96 | "Number of heap bytes allocated and still in use.", 97 | nil, nil, 98 | ), 99 | eval: func(ms *runtime.MemStats) float64 { return float64(ms.HeapAlloc) }, 100 | valType: GaugeValue, 101 | }, { 102 | desc: NewDesc( 103 | memstatNamespace("heap_sys_bytes"), 104 | "Number of heap bytes obtained from system.", 105 | nil, nil, 106 | ), 107 | eval: func(ms *runtime.MemStats) float64 { return float64(ms.HeapSys) }, 108 | valType: GaugeValue, 109 | }, { 110 | desc: NewDesc( 111 | memstatNamespace("heap_idle_bytes"), 112 | "Number of heap bytes waiting to be used.", 113 | nil, nil, 114 | ), 115 | eval: func(ms *runtime.MemStats) float64 { return float64(ms.HeapIdle) }, 116 | valType: GaugeValue, 117 | }, { 118 | desc: NewDesc( 119 | memstatNamespace("heap_inuse_bytes"), 120 | "Number of heap bytes that are in use.", 121 | nil, nil, 122 | ), 123 | eval: func(ms *runtime.MemStats) float64 { return float64(ms.HeapInuse) }, 124 | valType: GaugeValue, 125 | }, { 126 | desc: NewDesc( 127 | memstatNamespace("heap_released_bytes"), 128 | "Number of heap bytes released to OS.", 129 | nil, nil, 130 | ), 131 | eval: func(ms *runtime.MemStats) float64 { return float64(ms.HeapReleased) }, 132 | valType: GaugeValue, 133 | }, { 134 | desc: NewDesc( 135 | memstatNamespace("heap_objects"), 136 | "Number of allocated objects.", 137 | nil, nil, 138 | ), 139 | eval: func(ms *runtime.MemStats) float64 { return float64(ms.HeapObjects) }, 140 | valType: GaugeValue, 141 | }, { 142 | desc: NewDesc( 143 | memstatNamespace("stack_inuse_bytes"), 144 | "Number of bytes in use by the stack allocator.", 145 | nil, nil, 146 | ), 147 | eval: func(ms *runtime.MemStats) float64 { return float64(ms.StackInuse) }, 148 | valType: GaugeValue, 149 | }, { 150 | desc: NewDesc( 151 | memstatNamespace("stack_sys_bytes"), 152 | "Number of bytes obtained from system for stack allocator.", 153 | nil, nil, 154 | ), 155 | eval: func(ms *runtime.MemStats) float64 { return float64(ms.StackSys) }, 156 | valType: GaugeValue, 157 | }, { 158 | desc: NewDesc( 159 | memstatNamespace("mspan_inuse_bytes"), 160 | "Number of bytes in use by mspan structures.", 161 | nil, nil, 162 | ), 163 | eval: func(ms *runtime.MemStats) float64 { return float64(ms.MSpanInuse) }, 164 | valType: GaugeValue, 165 | }, { 166 | desc: NewDesc( 167 | memstatNamespace("mspan_sys_bytes"), 168 | "Number of bytes used for mspan structures obtained from system.", 169 | nil, nil, 170 | ), 171 | eval: func(ms *runtime.MemStats) float64 { return float64(ms.MSpanSys) }, 172 | valType: GaugeValue, 173 | }, { 174 | desc: NewDesc( 175 | memstatNamespace("mcache_inuse_bytes"), 176 | "Number of bytes in use by mcache structures.", 177 | nil, nil, 178 | ), 179 | eval: func(ms *runtime.MemStats) float64 { return float64(ms.MCacheInuse) }, 180 | valType: GaugeValue, 181 | }, { 182 | desc: NewDesc( 183 | memstatNamespace("mcache_sys_bytes"), 184 | "Number of bytes used for mcache structures obtained from system.", 185 | nil, nil, 186 | ), 187 | eval: func(ms *runtime.MemStats) float64 { return float64(ms.MCacheSys) }, 188 | valType: GaugeValue, 189 | }, { 190 | desc: NewDesc( 191 | memstatNamespace("buck_hash_sys_bytes"), 192 | "Number of bytes used by the profiling bucket hash table.", 193 | nil, nil, 194 | ), 195 | eval: func(ms *runtime.MemStats) float64 { return float64(ms.BuckHashSys) }, 196 | valType: GaugeValue, 197 | }, { 198 | desc: NewDesc( 199 | memstatNamespace("gc_sys_bytes"), 200 | "Number of bytes used for garbage collection system metadata.", 201 | nil, nil, 202 | ), 203 | eval: func(ms *runtime.MemStats) float64 { return float64(ms.GCSys) }, 204 | valType: GaugeValue, 205 | }, { 206 | desc: NewDesc( 207 | memstatNamespace("other_sys_bytes"), 208 | "Number of bytes used for other system allocations.", 209 | nil, nil, 210 | ), 211 | eval: func(ms *runtime.MemStats) float64 { return float64(ms.OtherSys) }, 212 | valType: GaugeValue, 213 | }, { 214 | desc: NewDesc( 215 | memstatNamespace("next_gc_bytes"), 216 | "Number of heap bytes when next garbage collection will take place.", 217 | nil, nil, 218 | ), 219 | eval: func(ms *runtime.MemStats) float64 { return float64(ms.NextGC) }, 220 | valType: GaugeValue, 221 | }, { 222 | desc: NewDesc( 223 | memstatNamespace("last_gc_time_seconds"), 224 | "Number of seconds since 1970 of last garbage collection.", 225 | nil, nil, 226 | ), 227 | eval: func(ms *runtime.MemStats) float64 { return float64(ms.LastGC) / 1e9 }, 228 | valType: GaugeValue, 229 | }, { 230 | desc: NewDesc( 231 | memstatNamespace("gc_cpu_fraction"), 232 | "The fraction of this program's available CPU time used by the GC since the program started.", 233 | nil, nil, 234 | ), 235 | eval: func(ms *runtime.MemStats) float64 { return ms.GCCPUFraction }, 236 | valType: GaugeValue, 237 | }, 238 | }, 239 | } 240 | } 241 | 242 | func memstatNamespace(s string) string { 243 | return fmt.Sprintf("go_memstats_%s", s) 244 | } 245 | 246 | // Describe returns all descriptions of the collector. 247 | func (c *goCollector) Describe(ch chan<- *Desc) { 248 | ch <- c.goroutinesDesc 249 | ch <- c.threadsDesc 250 | ch <- c.gcDesc 251 | ch <- c.goInfoDesc 252 | for _, i := range c.metrics { 253 | ch <- i.desc 254 | } 255 | } 256 | 257 | // Collect returns the current state of all metrics of the collector. 258 | func (c *goCollector) Collect(ch chan<- Metric) { 259 | ch <- MustNewConstMetric(c.goroutinesDesc, GaugeValue, float64(runtime.NumGoroutine())) 260 | n, _ := runtime.ThreadCreateProfile(nil) 261 | ch <- MustNewConstMetric(c.threadsDesc, GaugeValue, float64(n)) 262 | 263 | var stats debug.GCStats 264 | stats.PauseQuantiles = make([]time.Duration, 5) 265 | debug.ReadGCStats(&stats) 266 | 267 | quantiles := make(map[float64]float64) 268 | for idx, pq := range stats.PauseQuantiles[1:] { 269 | quantiles[float64(idx+1)/float64(len(stats.PauseQuantiles)-1)] = pq.Seconds() 270 | } 271 | quantiles[0.0] = stats.PauseQuantiles[0].Seconds() 272 | ch <- MustNewConstSummary(c.gcDesc, uint64(stats.NumGC), stats.PauseTotal.Seconds(), quantiles) 273 | 274 | ch <- MustNewConstMetric(c.goInfoDesc, GaugeValue, 1) 275 | 276 | ms := &runtime.MemStats{} 277 | runtime.ReadMemStats(ms) 278 | for _, i := range c.metrics { 279 | ch <- MustNewConstMetric(i.desc, i.valType, i.eval(ms)) 280 | } 281 | } 282 | 283 | // memStatsMetrics provide description, value, and value type for memstat metrics. 284 | type memStatsMetrics []struct { 285 | desc *Desc 286 | eval func(*runtime.MemStats) float64 287 | valType ValueType 288 | } 289 | --------------------------------------------------------------------------------