pax_global_header00006660000000000000000000000064125657205250014523gustar00rootroot0000000000000052 comment=dd1a2bbd0a75747f8d256a42c63d87c4389109c7 golang-prometheus-client-0.7.0+ds/000077500000000000000000000000001256572052500170455ustar00rootroot00000000000000golang-prometheus-client-0.7.0+ds/.gitignore000066400000000000000000000004121256572052500210320ustar00rootroot00000000000000# Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *~ *# .build golang-prometheus-client-0.7.0+ds/.travis.yml000066400000000000000000000000661256572052500211600ustar00rootroot00000000000000language: go go: - 1.4 script: - make -f Makefile golang-prometheus-client-0.7.0+ds/AUTHORS.md000066400000000000000000000010431256572052500205120ustar00rootroot00000000000000The Prometheus project was started by Matt T. Proud (emeritus) and Julius Volz in 2012. Maintainers of this repository: * Björn Rabenstein The following individuals have contributed code to this repository (listed in alphabetical order): * Bernerd Schaefer * Björn Rabenstein * Daniel Bornkessel * Jeff Younker * Julius Volz * Matt T. Proud * Tobias Schmidt golang-prometheus-client-0.7.0+ds/CHANGELOG.md000066400000000000000000000103231256572052500206550ustar00rootroot00000000000000## 0.7.0 / 2015-07-27 * [CHANGE] Rename ExporterLabelPrefix to ExportedLabelPrefix. * [BUGFIX] Closed gaps in metric consistency check. * [BUGFIX] Validate LabelName/LabelSet on JSON unmarshaling. * [ENHANCEMENT] Document the possibility to create "empty" metrics in a metric vector. * [ENHANCEMENT] Fix and clarify various doc comments and the README.md. * [ENHANCEMENT] (Kind of) solve "The Proxy Problem" of http.InstrumentHandler. * [ENHANCEMENT] Change responseWriterDelegator.written to int64. ## 0.6.0 / 2015-06-01 * [CHANGE] Rename process_goroutines to go_goroutines. * [ENHANCEMENT] Validate label names during YAML decoding. * [ENHANCEMENT] Add LabelName regular expression. * [BUGFIX] Ensure alignment of struct members for 32-bit systems. ## 0.5.0 / 2015-05-06 * [BUGFIX] Removed a weakness in the fingerprinting aka signature code. This makes fingerprinting slower and more allocation-heavy, but the weakness was too severe to be tolerated. * [CHANGE] As a result of the above, Metric.Fingerprint is now returning a different fingerprint. To keep the same fingerprint, the new method Metric.FastFingerprint was introduced, which will be used by the Prometheus server for storage purposes (implying that a collision detection has to be added, too). * [ENHANCEMENT] The Metric.Equal and Metric.Before do not depend on fingerprinting anymore, removing the possibility of an undetected fingerprint collision. * [FEATURE] The Go collector in the exposition library includes garbage collection stats. * [FEATURE] The exposition library allows to create constant "throw-away" summaries and histograms. * [CHANGE] A number of new reserved labels and prefixes. ## 0.4.0 / 2015-04-08 * [CHANGE] Return NaN when Summaries have no observations yet. * [BUGFIX] Properly handle Summary decay upon Write(). * [BUGFIX] Fix the documentation link to the consumption library. * [FEATURE] Allow the metric family injection hook to merge with existing metric families. * [ENHANCEMENT] Removed cgo dependency and conditional compilation of procfs. * [MAINTENANCE] Adjusted to changes in matttproud/golang_protobuf_extensions. ## 0.3.2 / 2015-03-11 * [BUGFIX] Fixed the receiver type of COWMetric.Set(). This method is only used by the Prometheus server internally. * [CLEANUP] Added licenses of vendored code left out by godep. ## 0.3.1 / 2015-03-04 * [ENHANCEMENT] Switched fingerprinting functions from own free list to sync.Pool. * [CHANGE] Makefile uses Go 1.4.2 now (only relevant for examples and tests). ## 0.3.0 / 2015-03-03 * [CHANGE] Changed the fingerprinting for metrics. THIS WILL INVALIDATE ALL PERSISTED FINGERPRINTS. IF YOU COMPILE THE PROMETHEUS SERVER WITH THIS VERSION, YOU HAVE TO WIPE THE PREVIOUSLY CREATED STORAGE. * [CHANGE] LabelValuesToSignature removed. (Nobody had used it, and it was arguably broken.) * [CHANGE] Vendored dependencies. Those are only used by the Makefile. If client_golang is used as a library, the vendoring will stay out of your way. * [BUGFIX] Remove a weakness in the fingerprinting for metrics. (This made the fingerprinting change above necessary.) * [FEATURE] Added new fingerprinting functions SignatureForLabels and SignatureWithoutLabels to be used by the Prometheus server. These functions require fewer allocations than the ones currently used by the server. ## 0.2.0 / 2015-02-23 * [FEATURE] Introduce new Histagram metric type. * [CHANGE] Ignore process collector errors for now (better error handling pending). * [CHANGE] Use clear error interface for process pidFn. * [BUGFIX] Fix Go download links for several archs and OSes. * [ENHANCEMENT] Massively improve Gauge and Counter performance. * [ENHANCEMENT] Catch illegal label names for summaries in histograms. * [ENHANCEMENT] Reduce allocations during fingerprinting. * [ENHANCEMENT] Remove cgo dependency. procfs package will only be included if both cgo is available and the build is for an OS with procfs. * [CLEANUP] Clean up code style issues. * [CLEANUP] Mark slow test as such and exclude them from travis. * [CLEANUP] Update protobuf library package name. * [CLEANUP] Updated vendoring of beorn7/perks. ## 0.1.0 / 2015-02-02 * [CLEANUP] Introduced semantic versioning and changelog. From now on, changes will be reported in this file. golang-prometheus-client-0.7.0+ds/CONTRIBUTING.md000066400000000000000000000015331256572052500213000ustar00rootroot00000000000000# Contributing Prometheus uses GitHub to manage reviews of pull requests. * If you have a trivial fix or improvement, go ahead and create a pull request, addressing (with `@...`) one or more of the maintainers (see [AUTHORS.md](AUTHORS.md)) in the description of the pull request. * If you plan to do something more involved, first discuss your ideas on our [mailing list](https://groups.google.com/forum/?fromgroups#!forum/prometheus-developers). This will avoid unnecessary work and surely give you and us a good deal of inspiration. * Relevant coding style guidelines are the [Go Code Review Comments](https://code.google.com/p/go-wiki/wiki/CodeReviewComments) and the _Formatting and style_ section of Peter Bourgon's [Go: Best Practices for Production Environments](http://peter.bourgon.org/go-in-production/#formatting-and-style). golang-prometheus-client-0.7.0+ds/Godeps/000077500000000000000000000000001256572052500202665ustar00rootroot00000000000000golang-prometheus-client-0.7.0+ds/Godeps/Godeps.json000066400000000000000000000016021256572052500224010ustar00rootroot00000000000000{ "ImportPath": "github.com/prometheus/client_golang", "GoVersion": "go1.4.2", "Packages": [ "./..." ], "Deps": [ { "ImportPath": "bitbucket.org/ww/goautoneg", "Comment": "null-5", "Rev": "75cd24fc2f2c2a2088577d12123ddee5f54e0675" }, { "ImportPath": "github.com/beorn7/perks/quantile", "Rev": "b965b613227fddccbfffe13eae360ed3fa822f8d" }, { "ImportPath": "github.com/golang/protobuf/proto", "Rev": "c22ae3cf020a21ebb7ae566dccbe90fc8ea4f9ea" }, { "ImportPath": "github.com/matttproud/golang_protobuf_extensions/pbutil", "Rev": "fc2b8d3a73c4867e51861bbdd5ae3c1f0869dd6a" }, { "ImportPath": "github.com/prometheus/client_model/go", "Comment": "model-0.0.2-12-gfa8ad6f", "Rev": "fa8ad6fec33561be4280a8f0514318c79d7f6cb6" }, { "ImportPath": "github.com/prometheus/procfs", "Rev": "6c34ef819e19b4e16f410100ace4aa006f0e3bf8" } ] } golang-prometheus-client-0.7.0+ds/Godeps/Readme000066400000000000000000000002101256572052500213770ustar00rootroot00000000000000This directory tree is generated automatically by godep. Please do not edit. See https://github.com/tools/godep for more information. golang-prometheus-client-0.7.0+ds/Godeps/_workspace/000077500000000000000000000000001256572052500224235ustar00rootroot00000000000000golang-prometheus-client-0.7.0+ds/Godeps/_workspace/.gitignore000066400000000000000000000000121256572052500244040ustar00rootroot00000000000000/pkg /bin golang-prometheus-client-0.7.0+ds/Godeps/_workspace/src/000077500000000000000000000000001256572052500232125ustar00rootroot00000000000000golang-prometheus-client-0.7.0+ds/Godeps/_workspace/src/bitbucket.org/000077500000000000000000000000001256572052500257545ustar00rootroot00000000000000golang-prometheus-client-0.7.0+ds/Godeps/_workspace/src/bitbucket.org/ww/000077500000000000000000000000001256572052500264115ustar00rootroot00000000000000golang-prometheus-client-0.7.0+ds/Godeps/_workspace/src/bitbucket.org/ww/goautoneg/000077500000000000000000000000001256572052500304015ustar00rootroot00000000000000golang-prometheus-client-0.7.0+ds/Godeps/_workspace/src/bitbucket.org/ww/goautoneg/Makefile000066400000000000000000000002741256572052500320440ustar00rootroot00000000000000include $(GOROOT)/src/Make.inc TARG=bitbucket.org/ww/goautoneg GOFILES=autoneg.go include $(GOROOT)/src/Make.pkg format: gofmt -w *.go docs: gomake clean godoc ${TARG} > README.txt golang-prometheus-client-0.7.0+ds/Godeps/_workspace/src/bitbucket.org/ww/goautoneg/README.txt000066400000000000000000000043341256572052500321030ustar00rootroot00000000000000PACKAGE package goautoneg import "bitbucket.org/ww/goautoneg" HTTP Content-Type Autonegotiation. The functions in this package implement the behaviour specified in http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html Copyright (c) 2011, Open Knowledge Foundation Ltd. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of the Open Knowledge Foundation Ltd. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. FUNCTIONS func Negotiate(header string, alternatives []string) (content_type string) Negotiate the most appropriate content_type given the accept header and a list of alternatives. func ParseAccept(header string) (accept []Accept) Parse an Accept Header string returning a sorted list of clauses TYPES type Accept struct { Type, SubType string Q float32 Params map[string]string } Structure to represent a clause in an HTTP Accept Header SUBDIRECTORIES .hg golang-prometheus-client-0.7.0+ds/Godeps/_workspace/src/bitbucket.org/ww/goautoneg/autoneg.go000066400000000000000000000103151256572052500323720ustar00rootroot00000000000000/* HTTP Content-Type Autonegotiation. The functions in this package implement the behaviour specified in http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html Copyright (c) 2011, Open Knowledge Foundation Ltd. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of the Open Knowledge Foundation Ltd. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package goautoneg import ( "sort" "strconv" "strings" ) // Structure to represent a clause in an HTTP Accept Header type Accept struct { Type, SubType string Q float64 Params map[string]string } // For internal use, so that we can use the sort interface type accept_slice []Accept func (accept accept_slice) Len() int { slice := []Accept(accept) return len(slice) } func (accept accept_slice) Less(i, j int) bool { slice := []Accept(accept) ai, aj := slice[i], slice[j] if ai.Q > aj.Q { return true } if ai.Type != "*" && aj.Type == "*" { return true } if ai.SubType != "*" && aj.SubType == "*" { return true } return false } func (accept accept_slice) Swap(i, j int) { slice := []Accept(accept) slice[i], slice[j] = slice[j], slice[i] } // Parse an Accept Header string returning a sorted list // of clauses func ParseAccept(header string) (accept []Accept) { parts := strings.Split(header, ",") accept = make([]Accept, 0, len(parts)) for _, part := range parts { part := strings.Trim(part, " ") a := Accept{} a.Params = make(map[string]string) a.Q = 1.0 mrp := strings.Split(part, ";") media_range := mrp[0] sp := strings.Split(media_range, "/") a.Type = strings.Trim(sp[0], " ") switch { case len(sp) == 1 && a.Type == "*": a.SubType = "*" case len(sp) == 2: a.SubType = strings.Trim(sp[1], " ") default: continue } if len(mrp) == 1 { accept = append(accept, a) continue } for _, param := range mrp[1:] { sp := strings.SplitN(param, "=", 2) if len(sp) != 2 { continue } token := strings.Trim(sp[0], " ") if token == "q" { a.Q, _ = strconv.ParseFloat(sp[1], 32) } else { a.Params[token] = strings.Trim(sp[1], " ") } } accept = append(accept, a) } slice := accept_slice(accept) sort.Sort(slice) return } // Negotiate the most appropriate content_type given the accept header // and a list of alternatives. func Negotiate(header string, alternatives []string) (content_type string) { asp := make([][]string, 0, len(alternatives)) for _, ctype := range alternatives { asp = append(asp, strings.SplitN(ctype, "/", 2)) } for _, clause := range ParseAccept(header) { for i, ctsp := range asp { if clause.Type == ctsp[0] && clause.SubType == ctsp[1] { content_type = alternatives[i] return } if clause.Type == ctsp[0] && clause.SubType == "*" { content_type = alternatives[i] return } if clause.Type == "*" && clause.SubType == "*" { content_type = alternatives[i] return } } } return } golang-prometheus-client-0.7.0+ds/Godeps/_workspace/src/bitbucket.org/ww/goautoneg/autoneg_test.go000066400000000000000000000017121256572052500334320ustar00rootroot00000000000000package goautoneg import ( "testing" ) var chrome = "application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5" func TestParseAccept(t *testing.T) { alternatives := []string{"text/html", "image/png"} content_type := Negotiate(chrome, alternatives) if content_type != "image/png" { t.Errorf("got %s expected image/png", content_type) } alternatives = []string{"text/html", "text/plain", "text/n3"} content_type = Negotiate(chrome, alternatives) if content_type != "text/html" { t.Errorf("got %s expected text/html", content_type) } alternatives = []string{"text/n3", "text/plain"} content_type = Negotiate(chrome, alternatives) if content_type != "text/plain" { t.Errorf("got %s expected text/plain", content_type) } alternatives = []string{"text/n3", "application/rdf+xml"} content_type = Negotiate(chrome, alternatives) if content_type != "text/n3" { t.Errorf("got %s expected text/n3", content_type) } } golang-prometheus-client-0.7.0+ds/Godeps/_workspace/src/github.com/000077500000000000000000000000001256572052500252515ustar00rootroot00000000000000golang-prometheus-client-0.7.0+ds/Godeps/_workspace/src/github.com/beorn7/000077500000000000000000000000001256572052500264455ustar00rootroot00000000000000golang-prometheus-client-0.7.0+ds/Godeps/_workspace/src/github.com/beorn7/perks/000077500000000000000000000000001256572052500275715ustar00rootroot00000000000000golang-prometheus-client-0.7.0+ds/Godeps/_workspace/src/github.com/beorn7/perks/README.md000066400000000000000000000035361256572052500310570ustar00rootroot00000000000000# Perks for Go (golang.org) Perks contains the Go package quantile that computes approximate quantiles over an unbounded data stream within low memory and CPU bounds. For more information and examples, see: http://godoc.org/github.com/bmizerany/perks A very special thank you and shout out to Graham Cormode (Rutgers University), Flip Korn (AT&T Labs–Research), S. Muthukrishnan (Rutgers University), and Divesh Srivastava (AT&T Labs–Research) for their research and publication of [Effective Computation of Biased Quantiles over Data Streams](http://www.cs.rutgers.edu/~muthu/bquant.pdf) Thank you, also: * Armon Dadgar (@armon) * Andrew Gerrand (@nf) * Brad Fitzpatrick (@bradfitz) * Keith Rarick (@kr) FAQ: Q: Why not move the quantile package into the project root? A: I want to add more packages to perks later. Copyright (C) 2013 Blake Mizerany Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. golang-prometheus-client-0.7.0+ds/Godeps/_workspace/src/github.com/beorn7/perks/quantile/000077500000000000000000000000001256572052500314135ustar00rootroot00000000000000bench_test.go000066400000000000000000000021751256572052500340060ustar00rootroot00000000000000golang-prometheus-client-0.7.0+ds/Godeps/_workspace/src/github.com/beorn7/perks/quantilepackage quantile import ( "testing" ) func BenchmarkInsertTargeted(b *testing.B) { b.ReportAllocs() s := NewTargeted(Targets) b.ResetTimer() for i := float64(0); i < float64(b.N); i++ { s.Insert(i) } } func BenchmarkInsertTargetedSmallEpsilon(b *testing.B) { s := NewTargeted(TargetsSmallEpsilon) b.ResetTimer() for i := float64(0); i < float64(b.N); i++ { s.Insert(i) } } func BenchmarkInsertBiased(b *testing.B) { s := NewLowBiased(0.01) b.ResetTimer() for i := float64(0); i < float64(b.N); i++ { s.Insert(i) } } func BenchmarkInsertBiasedSmallEpsilon(b *testing.B) { s := NewLowBiased(0.0001) b.ResetTimer() for i := float64(0); i < float64(b.N); i++ { s.Insert(i) } } func BenchmarkQuery(b *testing.B) { s := NewTargeted(Targets) for i := float64(0); i < 1e6; i++ { s.Insert(i) } b.ResetTimer() n := float64(b.N) for i := float64(0); i < n; i++ { s.Query(i / n) } } func BenchmarkQuerySmallEpsilon(b *testing.B) { s := NewTargeted(TargetsSmallEpsilon) for i := float64(0); i < 1e6; i++ { s.Insert(i) } b.ResetTimer() n := float64(b.N) for i := float64(0); i < n; i++ { s.Query(i / n) } } example_test.go000066400000000000000000000052331256572052500343600ustar00rootroot00000000000000golang-prometheus-client-0.7.0+ds/Godeps/_workspace/src/github.com/beorn7/perks/quantile// +build go1.1 package quantile_test import ( "bufio" "fmt" "log" "os" "strconv" "time" "github.com/beorn7/perks/quantile" ) func Example_simple() { ch := make(chan float64) go sendFloats(ch) // Compute the 50th, 90th, and 99th percentile. q := quantile.NewTargeted(map[float64]float64{ 0.50: 0.005, 0.90: 0.001, 0.99: 0.0001, }) for v := range ch { q.Insert(v) } fmt.Println("perc50:", q.Query(0.50)) fmt.Println("perc90:", q.Query(0.90)) fmt.Println("perc99:", q.Query(0.99)) fmt.Println("count:", q.Count()) // Output: // perc50: 5 // perc90: 16 // perc99: 223 // count: 2388 } func Example_mergeMultipleStreams() { // Scenario: // We have multiple database shards. On each shard, there is a process // collecting query response times from the database logs and inserting // them into a Stream (created via NewTargeted(0.90)), much like the // Simple example. These processes expose a network interface for us to // ask them to serialize and send us the results of their // Stream.Samples so we may Merge and Query them. // // NOTES: // * These sample sets are small, allowing us to get them // across the network much faster than sending the entire list of data // points. // // * For this to work correctly, we must supply the same quantiles // a priori the process collecting the samples supplied to NewTargeted, // even if we do not plan to query them all here. ch := make(chan quantile.Samples) getDBQuerySamples(ch) q := quantile.NewTargeted(map[float64]float64{0.90: 0.001}) for samples := range ch { q.Merge(samples) } fmt.Println("perc90:", q.Query(0.90)) } func Example_window() { // Scenario: We want the 90th, 95th, and 99th percentiles for each // minute. ch := make(chan float64) go sendStreamValues(ch) tick := time.NewTicker(1 * time.Minute) q := quantile.NewTargeted(map[float64]float64{ 0.90: 0.001, 0.95: 0.0005, 0.99: 0.0001, }) for { select { case t := <-tick.C: flushToDB(t, q.Samples()) q.Reset() case v := <-ch: q.Insert(v) } } } func sendStreamValues(ch chan float64) { // Use your imagination } func flushToDB(t time.Time, samples quantile.Samples) { // Use your imagination } // This is a stub for the above example. In reality this would hit the remote // servers via http or something like it. func getDBQuerySamples(ch chan quantile.Samples) {} func sendFloats(ch chan<- float64) { f, err := os.Open("exampledata.txt") if err != nil { log.Fatal(err) } sc := bufio.NewScanner(f) for sc.Scan() { b := sc.Bytes() v, err := strconv.ParseFloat(string(b), 64) if err != nil { log.Fatal(err) } ch <- v } if sc.Err() != nil { log.Fatal(sc.Err()) } close(ch) } exampledata.txt000066400000000000000000000123331256572052500343640ustar00rootroot00000000000000golang-prometheus-client-0.7.0+ds/Godeps/_workspace/src/github.com/beorn7/perks/quantile8 5 26 12 5 235 13 6 28 30 3 3 3 3 5 2 33 7 2 4 7 12 14 5 8 3 10 4 5 3 6 6 209 20 3 10 14 3 4 6 8 5 11 7 3 2 3 3 212 5 222 4 10 10 5 6 3 8 3 10 254 220 2 3 5 24 5 4 222 7 3 3 223 8 15 12 14 14 3 2 2 3 13 3 11 4 4 6 5 7 13 5 3 5 2 5 3 5 2 7 15 17 14 3 6 6 3 17 5 4 7 6 4 4 8 6 8 3 9 3 6 3 4 5 3 3 660 4 6 10 3 6 3 2 5 13 2 4 4 10 4 8 4 3 7 9 9 3 10 37 3 13 4 12 3 6 10 8 5 21 2 3 8 3 2 3 3 4 12 2 4 8 8 4 3 2 20 1 6 32 2 11 6 18 3 8 11 3 212 3 4 2 6 7 12 11 3 2 16 10 6 4 6 3 2 7 3 2 2 2 2 5 6 4 3 10 3 4 6 5 3 4 4 5 6 4 3 4 4 5 7 5 5 3 2 7 2 4 12 4 5 6 2 4 4 8 4 15 13 7 16 5 3 23 5 5 7 3 2 9 8 7 5 8 11 4 10 76 4 47 4 3 2 7 4 2 3 37 10 4 2 20 5 4 4 10 10 4 3 7 23 240 7 13 5 5 3 3 2 5 4 2 8 7 19 2 23 8 7 2 5 3 8 3 8 13 5 5 5 2 3 23 4 9 8 4 3 3 5 220 2 3 4 6 14 3 53 6 2 5 18 6 3 219 6 5 2 5 3 6 5 15 4 3 17 3 2 4 7 2 3 3 4 4 3 2 664 6 3 23 5 5 16 5 8 2 4 2 24 12 3 2 3 5 8 3 5 4 3 14 3 5 8 2 3 7 9 4 2 3 6 8 4 3 4 6 5 3 3 6 3 19 4 4 6 3 6 3 5 22 5 4 4 3 8 11 4 9 7 6 13 4 4 4 6 17 9 3 3 3 4 3 221 5 11 3 4 2 12 6 3 5 7 5 7 4 9 7 14 37 19 217 16 3 5 2 2 7 19 7 6 7 4 24 5 11 4 7 7 9 13 3 4 3 6 28 4 4 5 5 2 5 6 4 4 6 10 5 4 3 2 3 3 6 5 5 4 3 2 3 7 4 6 18 16 8 16 4 5 8 6 9 13 1545 6 215 6 5 6 3 45 31 5 2 2 4 3 3 2 5 4 3 5 7 7 4 5 8 5 4 749 2 31 9 11 2 11 5 4 4 7 9 11 4 5 4 7 3 4 6 2 15 3 4 3 4 3 5 2 13 5 5 3 3 23 4 4 5 7 4 13 2 4 3 4 2 6 2 7 3 5 5 3 29 5 4 4 3 10 2 3 79 16 6 6 7 7 3 5 5 7 4 3 7 9 5 6 5 9 6 3 6 4 17 2 10 9 3 6 2 3 21 22 5 11 4 2 17 2 224 2 14 3 4 4 2 4 4 4 4 5 3 4 4 10 2 6 3 3 5 7 2 7 5 6 3 218 2 2 5 2 6 3 5 222 14 6 33 3 2 5 3 3 3 9 5 3 3 2 7 4 3 4 3 5 6 5 26 4 13 9 7 3 221 3 3 4 4 4 4 2 18 5 3 7 9 6 8 3 10 3 11 9 5 4 17 5 5 6 6 3 2 4 12 17 6 7 218 4 2 4 10 3 5 15 3 9 4 3 3 6 29 3 3 4 5 5 3 8 5 6 6 7 5 3 5 3 29 2 31 5 15 24 16 5 207 4 3 3 2 15 4 4 13 5 5 4 6 10 2 7 8 4 6 20 5 3 4 3 12 12 5 17 7 3 3 3 6 10 3 5 25 80 4 9 3 2 11 3 3 2 3 8 7 5 5 19 5 3 3 12 11 2 6 5 5 5 3 3 3 4 209 14 3 2 5 19 4 4 3 4 14 5 6 4 13 9 7 4 7 10 2 9 5 7 2 8 4 6 5 5 222 8 7 12 5 216 3 4 4 6 3 14 8 7 13 4 3 3 3 3 17 5 4 3 33 6 6 33 7 5 3 8 7 5 2 9 4 2 233 24 7 4 8 10 3 4 15 2 16 3 3 13 12 7 5 4 207 4 2 4 27 15 2 5 2 25 6 5 5 6 13 6 18 6 4 12 225 10 7 5 2 2 11 4 14 21 8 10 3 5 4 232 2 5 5 3 7 17 11 6 6 23 4 6 3 5 4 2 17 3 6 5 8 3 2 2 14 9 4 4 2 5 5 3 7 6 12 6 10 3 6 2 2 19 5 4 4 9 2 4 13 3 5 6 3 6 5 4 9 6 3 5 7 3 6 6 4 3 10 6 3 221 3 5 3 6 4 8 5 3 6 4 4 2 54 5 6 11 3 3 4 4 4 3 7 3 11 11 7 10 6 13 223 213 15 231 7 3 7 228 2 3 4 4 5 6 7 4 13 3 4 5 3 6 4 6 7 2 4 3 4 3 3 6 3 7 3 5 18 5 6 8 10 3 3 3 2 4 2 4 4 5 6 6 4 10 13 3 12 5 12 16 8 4 19 11 2 4 5 6 8 5 6 4 18 10 4 2 216 6 6 6 2 4 12 8 3 11 5 6 14 5 3 13 4 5 4 5 3 28 6 3 7 219 3 9 7 3 10 6 3 4 19 5 7 11 6 15 19 4 13 11 3 7 5 10 2 8 11 2 6 4 6 24 6 3 3 3 3 6 18 4 11 4 2 5 10 8 3 9 5 3 4 5 6 2 5 7 4 4 14 6 4 4 5 5 7 2 4 3 7 3 3 6 4 5 4 4 4 3 3 3 3 8 14 2 3 5 3 2 4 5 3 7 3 3 18 3 4 4 5 7 3 3 3 13 5 4 8 211 5 5 3 5 2 5 4 2 655 6 3 5 11 2 5 3 12 9 15 11 5 12 217 2 6 17 3 3 207 5 5 4 5 9 3 2 8 5 4 3 2 5 12 4 14 5 4 2 13 5 8 4 225 4 3 4 5 4 3 3 6 23 9 2 6 7 233 4 4 6 18 3 4 6 3 4 4 2 3 7 4 13 227 4 3 5 4 2 12 9 17 3 7 14 6 4 5 21 4 8 9 2 9 25 16 3 6 4 7 8 5 2 3 5 4 3 3 5 3 3 3 2 3 19 2 4 3 4 2 3 4 4 2 4 3 3 3 2 6 3 17 5 6 4 3 13 5 3 3 3 4 9 4 2 14 12 4 5 24 4 3 37 12 11 21 3 4 3 13 4 2 3 15 4 11 4 4 3 8 3 4 4 12 8 5 3 3 4 2 220 3 5 223 3 3 3 10 3 15 4 241 9 7 3 6 6 23 4 13 7 3 4 7 4 9 3 3 4 10 5 5 1 5 24 2 4 5 5 6 14 3 8 2 3 5 13 13 3 5 2 3 15 3 4 2 10 4 4 4 5 5 3 5 3 4 7 4 27 3 6 4 15 3 5 6 6 5 4 8 3 9 2 6 3 4 3 7 4 18 3 11 3 3 8 9 7 24 3 219 7 10 4 5 9 12 2 5 4 4 4 3 3 19 5 8 16 8 6 22 3 23 3 242 9 4 3 3 5 7 3 3 5 8 3 7 5 14 8 10 3 4 3 7 4 6 7 4 10 4 3 11 3 7 10 3 13 6 8 12 10 5 7 9 3 4 7 7 10 8 30 9 19 4 3 19 15 4 13 3 215 223 4 7 4 8 17 16 3 7 6 5 5 4 12 3 7 4 4 13 4 5 2 5 6 5 6 6 7 10 18 23 9 3 3 6 5 2 4 2 7 3 3 2 5 5 14 10 224 6 3 4 3 7 5 9 3 6 4 2 5 11 4 3 3 2 8 4 7 4 10 7 3 3 18 18 17 3 3 3 4 5 3 3 4 12 7 3 11 13 5 4 7 13 5 4 11 3 12 3 6 4 4 21 4 6 9 5 3 10 8 4 6 4 4 6 5 4 8 6 4 6 4 4 5 9 6 3 4 2 9 3 18 2 4 3 13 3 6 6 8 7 9 3 2 16 3 4 6 3 2 33 22 14 4 9 12 4 5 6 3 23 9 4 3 5 5 3 4 5 3 5 3 10 4 5 5 8 4 4 6 8 5 4 3 4 6 3 3 3 5 9 12 6 5 9 3 5 3 2 2 2 18 3 2 21 2 5 4 6 4 5 10 3 9 3 2 10 7 3 6 6 4 4 8 12 7 3 7 3 3 9 3 4 5 4 4 5 5 10 15 4 4 14 6 227 3 14 5 216 22 5 4 2 2 6 3 4 2 9 9 4 3 28 13 11 4 5 3 3 2 3 3 5 3 4 3 5 23 26 3 4 5 6 4 6 3 5 5 3 4 3 2 2 2 7 14 3 6 7 17 2 2 15 14 16 4 6 7 13 6 4 5 6 16 3 3 28 3 6 15 3 9 2 4 6 3 3 22 4 12 6 7 2 5 4 10 3 16 6 9 2 5 12 7 5 5 5 5 2 11 9 17 4 3 11 7 3 5 15 4 3 4 211 8 7 5 4 7 6 7 6 3 6 5 6 5 3 4 4 26 4 6 10 4 4 3 2 3 3 4 5 9 3 9 4 4 5 5 8 2 4 2 3 8 4 11 19 5 8 6 3 5 6 12 3 2 4 16 12 3 4 4 8 6 5 6 6 219 8 222 6 16 3 13 19 5 4 3 11 6 10 4 7 7 12 5 3 3 5 6 10 3 8 2 5 4 7 2 4 4 2 12 9 6 4 2 40 2 4 10 4 223 4 2 20 6 7 24 5 4 5 2 20 16 6 5 13 2 3 3 19 3 2 4 5 6 7 11 12 5 6 7 7 3 5 3 5 3 14 3 4 4 2 11 1 7 3 9 6 11 12 5 8 6 221 4 2 12 4 3 15 4 5 226 7 218 7 5 4 5 18 4 5 9 4 4 2 9 18 18 9 5 6 6 3 3 7 3 5 4 4 4 12 3 6 31 5 4 7 3 6 5 6 5 11 2 2 11 11 6 7 5 8 7 10 5 23 7 4 3 5 34 2 5 23 7 3 6 8 4 4 4 2 5 3 8 5 4 8 25 2 3 17 8 3 4 8 7 3 15 6 5 7 21 9 5 6 6 5 3 2 3 10 3 6 3 14 7 4 4 8 7 8 2 6 12 4 213 6 5 21 8 2 5 23 3 11 2 3 6 25 2 3 6 7 6 6 4 4 6 3 17 9 7 6 4 3 10 7 2 3 3 3 11 8 3 7 6 4 14 36 3 4 3 3 22 13 21 4 2 7 4 4 17 15 3 7 11 2 4 7 6 209 6 3 2 2 24 4 9 4 3 3 3 29 2 2 4 3 3 5 4 6 3 3 2 4 golang-prometheus-client-0.7.0+ds/Godeps/_workspace/src/github.com/beorn7/perks/quantile/stream.go000066400000000000000000000163631256572052500332460ustar00rootroot00000000000000// Package quantile computes approximate quantiles over an unbounded data // stream within low memory and CPU bounds. // // A small amount of accuracy is traded to achieve the above properties. // // Multiple streams can be merged before calling Query to generate a single set // of results. This is meaningful when the streams represent the same type of // data. See Merge and Samples. // // For more detailed information about the algorithm used, see: // // Effective Computation of Biased Quantiles over Data Streams // // http://www.cs.rutgers.edu/~muthu/bquant.pdf package quantile import ( "math" "sort" ) // Sample holds an observed value and meta information for compression. JSON // tags have been added for convenience. type Sample struct { Value float64 `json:",string"` Width float64 `json:",string"` Delta float64 `json:",string"` } // Samples represents a slice of samples. It implements sort.Interface. type Samples []Sample func (a Samples) Len() int { return len(a) } func (a Samples) Less(i, j int) bool { return a[i].Value < a[j].Value } func (a Samples) Swap(i, j int) { a[i], a[j] = a[j], a[i] } type invariant func(s *stream, r float64) float64 // NewLowBiased returns an initialized Stream for low-biased quantiles // (e.g. 0.01, 0.1, 0.5) where the needed quantiles are not known a priori, but // error guarantees can still be given even for the lower ranks of the data // distribution. // // The provided epsilon is a relative error, i.e. the true quantile of a value // returned by a query is guaranteed to be within (1±Epsilon)*Quantile. // // See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error // properties. func NewLowBiased(epsilon float64) *Stream { ƒ := func(s *stream, r float64) float64 { return 2 * epsilon * r } return newStream(ƒ) } // NewHighBiased returns an initialized Stream for high-biased quantiles // (e.g. 0.01, 0.1, 0.5) where the needed quantiles are not known a priori, but // error guarantees can still be given even for the higher ranks of the data // distribution. // // The provided epsilon is a relative error, i.e. the true quantile of a value // returned by a query is guaranteed to be within 1-(1±Epsilon)*(1-Quantile). // // See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error // properties. func NewHighBiased(epsilon float64) *Stream { ƒ := func(s *stream, r float64) float64 { return 2 * epsilon * (s.n - r) } return newStream(ƒ) } // NewTargeted returns an initialized Stream concerned with a particular set of // quantile values that are supplied a priori. Knowing these a priori reduces // space and computation time. The targets map maps the desired quantiles to // their absolute errors, i.e. the true quantile of a value returned by a query // is guaranteed to be within (Quantile±Epsilon). // // See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error properties. func NewTargeted(targets map[float64]float64) *Stream { ƒ := func(s *stream, r float64) float64 { var m = math.MaxFloat64 var f float64 for quantile, epsilon := range targets { if quantile*s.n <= r { f = (2 * epsilon * r) / quantile } else { f = (2 * epsilon * (s.n - r)) / (1 - quantile) } if f < m { m = f } } return m } return newStream(ƒ) } // Stream computes quantiles for a stream of float64s. It is not thread-safe by // design. Take care when using across multiple goroutines. type Stream struct { *stream b Samples sorted bool } func newStream(ƒ invariant) *Stream { x := &stream{ƒ: ƒ} return &Stream{x, make(Samples, 0, 500), true} } // Insert inserts v into the stream. func (s *Stream) Insert(v float64) { s.insert(Sample{Value: v, Width: 1}) } func (s *Stream) insert(sample Sample) { s.b = append(s.b, sample) s.sorted = false if len(s.b) == cap(s.b) { s.flush() } } // Query returns the computed qth percentiles value. If s was created with // NewTargeted, and q is not in the set of quantiles provided a priori, Query // will return an unspecified result. func (s *Stream) Query(q float64) float64 { if !s.flushed() { // Fast path when there hasn't been enough data for a flush; // this also yields better accuracy for small sets of data. l := len(s.b) if l == 0 { return 0 } i := int(float64(l) * q) if i > 0 { i -= 1 } s.maybeSort() return s.b[i].Value } s.flush() return s.stream.query(q) } // Merge merges samples into the underlying streams samples. This is handy when // merging multiple streams from separate threads, database shards, etc. // // ATTENTION: This method is broken and does not yield correct results. The // underlying algorithm is not capable of merging streams correctly. func (s *Stream) Merge(samples Samples) { sort.Sort(samples) s.stream.merge(samples) } // Reset reinitializes and clears the list reusing the samples buffer memory. func (s *Stream) Reset() { s.stream.reset() s.b = s.b[:0] } // Samples returns stream samples held by s. func (s *Stream) Samples() Samples { if !s.flushed() { return s.b } s.flush() return s.stream.samples() } // Count returns the total number of samples observed in the stream // since initialization. func (s *Stream) Count() int { return len(s.b) + s.stream.count() } func (s *Stream) flush() { s.maybeSort() s.stream.merge(s.b) s.b = s.b[:0] } func (s *Stream) maybeSort() { if !s.sorted { s.sorted = true sort.Sort(s.b) } } func (s *Stream) flushed() bool { return len(s.stream.l) > 0 } type stream struct { n float64 l []Sample ƒ invariant } func (s *stream) reset() { s.l = s.l[:0] s.n = 0 } func (s *stream) insert(v float64) { s.merge(Samples{{v, 1, 0}}) } func (s *stream) merge(samples Samples) { // TODO(beorn7): This tries to merge not only individual samples, but // whole summaries. The paper doesn't mention merging summaries at // all. Unittests show that the merging is inaccurate. Find out how to // do merges properly. var r float64 i := 0 for _, sample := range samples { for ; i < len(s.l); i++ { c := s.l[i] if c.Value > sample.Value { // Insert at position i. s.l = append(s.l, Sample{}) copy(s.l[i+1:], s.l[i:]) s.l[i] = Sample{ sample.Value, sample.Width, math.Max(sample.Delta, math.Floor(s.ƒ(s, r))-1), // TODO(beorn7): How to calculate delta correctly? } i++ goto inserted } r += c.Width } s.l = append(s.l, Sample{sample.Value, sample.Width, 0}) i++ inserted: s.n += sample.Width r += sample.Width } s.compress() } func (s *stream) count() int { return int(s.n) } func (s *stream) query(q float64) float64 { t := math.Ceil(q * s.n) t += math.Ceil(s.ƒ(s, t) / 2) p := s.l[0] var r float64 for _, c := range s.l[1:] { r += p.Width if r+c.Width+c.Delta > t { return p.Value } p = c } return p.Value } func (s *stream) compress() { if len(s.l) < 2 { return } x := s.l[len(s.l)-1] xi := len(s.l) - 1 r := s.n - 1 - x.Width for i := len(s.l) - 2; i >= 0; i-- { c := s.l[i] if c.Width+x.Width+x.Delta <= s.ƒ(s, r) { x.Width += c.Width s.l[xi] = x // Remove element at i. copy(s.l[i:], s.l[i+1:]) s.l = s.l[:len(s.l)-1] xi -= 1 } else { x = c xi = i } r -= c.Width } } func (s *stream) samples() Samples { samples := make(Samples, len(s.l)) copy(samples, s.l) return samples } stream_test.go000066400000000000000000000107201256572052500342150ustar00rootroot00000000000000golang-prometheus-client-0.7.0+ds/Godeps/_workspace/src/github.com/beorn7/perks/quantilepackage quantile import ( "math" "math/rand" "sort" "testing" ) var ( Targets = map[float64]float64{ 0.01: 0.001, 0.10: 0.01, 0.50: 0.05, 0.90: 0.01, 0.99: 0.001, } TargetsSmallEpsilon = map[float64]float64{ 0.01: 0.0001, 0.10: 0.001, 0.50: 0.005, 0.90: 0.001, 0.99: 0.0001, } LowQuantiles = []float64{0.01, 0.1, 0.5} HighQuantiles = []float64{0.99, 0.9, 0.5} ) const RelativeEpsilon = 0.01 func verifyPercsWithAbsoluteEpsilon(t *testing.T, a []float64, s *Stream) { sort.Float64s(a) for quantile, epsilon := range Targets { n := float64(len(a)) k := int(quantile * n) lower := int((quantile - epsilon) * n) if lower < 1 { lower = 1 } upper := int(math.Ceil((quantile + epsilon) * n)) if upper > len(a) { upper = len(a) } w, min, max := a[k-1], a[lower-1], a[upper-1] if g := s.Query(quantile); g < min || g > max { t.Errorf("q=%f: want %v [%f,%f], got %v", quantile, w, min, max, g) } } } func verifyLowPercsWithRelativeEpsilon(t *testing.T, a []float64, s *Stream) { sort.Float64s(a) for _, qu := range LowQuantiles { n := float64(len(a)) k := int(qu * n) lowerRank := int((1 - RelativeEpsilon) * qu * n) upperRank := int(math.Ceil((1 + RelativeEpsilon) * qu * n)) w, min, max := a[k-1], a[lowerRank-1], a[upperRank-1] if g := s.Query(qu); g < min || g > max { t.Errorf("q=%f: want %v [%f,%f], got %v", qu, w, min, max, g) } } } func verifyHighPercsWithRelativeEpsilon(t *testing.T, a []float64, s *Stream) { sort.Float64s(a) for _, qu := range HighQuantiles { n := float64(len(a)) k := int(qu * n) lowerRank := int((1 - (1+RelativeEpsilon)*(1-qu)) * n) upperRank := int(math.Ceil((1 - (1-RelativeEpsilon)*(1-qu)) * n)) w, min, max := a[k-1], a[lowerRank-1], a[upperRank-1] if g := s.Query(qu); g < min || g > max { t.Errorf("q=%f: want %v [%f,%f], got %v", qu, w, min, max, g) } } } func populateStream(s *Stream) []float64 { a := make([]float64, 0, 1e5+100) for i := 0; i < cap(a); i++ { v := rand.NormFloat64() // Add 5% asymmetric outliers. if i%20 == 0 { v = v*v + 1 } s.Insert(v) a = append(a, v) } return a } func TestTargetedQuery(t *testing.T) { rand.Seed(42) s := NewTargeted(Targets) a := populateStream(s) verifyPercsWithAbsoluteEpsilon(t, a, s) } func TestLowBiasedQuery(t *testing.T) { rand.Seed(42) s := NewLowBiased(RelativeEpsilon) a := populateStream(s) verifyLowPercsWithRelativeEpsilon(t, a, s) } func TestHighBiasedQuery(t *testing.T) { rand.Seed(42) s := NewHighBiased(RelativeEpsilon) a := populateStream(s) verifyHighPercsWithRelativeEpsilon(t, a, s) } // BrokenTestTargetedMerge is broken, see Merge doc comment. func BrokenTestTargetedMerge(t *testing.T) { rand.Seed(42) s1 := NewTargeted(Targets) s2 := NewTargeted(Targets) a := populateStream(s1) a = append(a, populateStream(s2)...) s1.Merge(s2.Samples()) verifyPercsWithAbsoluteEpsilon(t, a, s1) } // BrokenTestLowBiasedMerge is broken, see Merge doc comment. func BrokenTestLowBiasedMerge(t *testing.T) { rand.Seed(42) s1 := NewLowBiased(RelativeEpsilon) s2 := NewLowBiased(RelativeEpsilon) a := populateStream(s1) a = append(a, populateStream(s2)...) s1.Merge(s2.Samples()) verifyLowPercsWithRelativeEpsilon(t, a, s2) } // BrokenTestHighBiasedMerge is broken, see Merge doc comment. func BrokenTestHighBiasedMerge(t *testing.T) { rand.Seed(42) s1 := NewHighBiased(RelativeEpsilon) s2 := NewHighBiased(RelativeEpsilon) a := populateStream(s1) a = append(a, populateStream(s2)...) s1.Merge(s2.Samples()) verifyHighPercsWithRelativeEpsilon(t, a, s2) } func TestUncompressed(t *testing.T) { q := NewTargeted(Targets) for i := 100; i > 0; i-- { q.Insert(float64(i)) } if g := q.Count(); g != 100 { t.Errorf("want count 100, got %d", g) } // Before compression, Query should have 100% accuracy. for quantile := range Targets { w := quantile * 100 if g := q.Query(quantile); g != w { t.Errorf("want %f, got %f", w, g) } } } func TestUncompressedSamples(t *testing.T) { q := NewTargeted(map[float64]float64{0.99: 0.001}) for i := 1; i <= 100; i++ { q.Insert(float64(i)) } if g := q.Samples().Len(); g != 100 { t.Errorf("want count 100, got %d", g) } } func TestUncompressedOne(t *testing.T) { q := NewTargeted(map[float64]float64{0.99: 0.01}) q.Insert(3.14) if g := q.Query(0.90); g != 3.14 { t.Error("want PI, got", g) } } func TestDefaults(t *testing.T) { if g := NewTargeted(map[float64]float64{0.99: 0.001}).Query(0.99); g != 0 { t.Errorf("want 0, got %f", g) } } golang-prometheus-client-0.7.0+ds/LICENSE000066400000000000000000000261351256572052500200610ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. golang-prometheus-client-0.7.0+ds/Makefile000066400000000000000000000066111256572052500205110ustar00rootroot00000000000000# Copyright 2013 The Prometheus Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. VERSION=$(shell cat `git rev-parse --show-toplevel`/VERSION) OS = $(shell uname) ARCH = $(shell uname -m) MAC_OS_X_VERSION ?= 10.8 BUILD_PATH = $(PWD)/.build export GO_VERSION = 1.4.2 export GOOS = $(subst Darwin,darwin,$(subst Linux,linux,$(subst FreeBSD,freebsd,$(OS)))) ifeq ($(GOOS),darwin) RELEASE_SUFFIX ?= -osx$(MAC_OS_X_VERSION) else RELEASE_SUFFIX ?= endif # Never honor GOBIN, should it be set at all. unexport GOBIN export GOARCH = $(subst x86_64,amd64,$(patsubst i%86,386,$(ARCH))) export GOPKG = go$(GO_VERSION).$(GOOS)-$(GOARCH)$(RELEASE_SUFFIX).tar.gz export GOURL = https://golang.org/dl export GOROOT = $(BUILD_PATH)/root/go export GOPATH = $(BUILD_PATH)/root/gopath export GOCC = $(GOROOT)/bin/go export TMPDIR = /tmp export GOENV = TMPDIR=$(TMPDIR) GOROOT=$(GOROOT) GOPATH=$(GOPATH) export GO = $(GOENV) $(GOCC) export GOFMT = $(GOROOT)/bin/gofmt export GODOC = $(GOENV) $(GOROOT)/bin/godoc BENCHMARK_FILTER ?= . FULL_GOPATH = $(GOPATH)/src/github.com/prometheus/client_golang FULL_GOPATH_BASE = $(GOPATH)/src/github.com/prometheus MAKE_ARTIFACTS = search_index $(BUILD_PATH) example_random example_simple all: test $(BUILD_PATH): mkdir -vp $(BUILD_PATH) $(BUILD_PATH)/cache: $(BUILD_PATH) mkdir -vp $(BUILD_PATH)/cache $(BUILD_PATH)/root: $(BUILD_PATH) mkdir -vp $(BUILD_PATH)/root $(BUILD_PATH)/cache/$(GOPKG): $(BUILD_PATH)/cache curl -o $@ -L $(GOURL)/$(GOPKG) $(GOCC): $(BUILD_PATH)/root $(BUILD_PATH)/cache/$(GOPKG) tar -C $(BUILD_PATH)/root -xzf $(BUILD_PATH)/cache/$(GOPKG) touch $@ build: source_path dependencies $(GO) build ./... dependencies: source_path $(GOCC) cp -a $(CURDIR)/Godeps/_workspace/src/* $(GOPATH)/src example_random: source_path dependencies examples/random/main.go $(GO) build -o example_random examples/random/main.go example_simple: source_path dependencies examples/simple/main.go $(GO) build -o example_simple examples/simple/main.go test: build $(GO) test -short ./... benchmark: build $(GO) test -benchmem -test.bench="$(BENCHMARK_FILTER)" ./... advice: test $(GO) vet ./... format: find . -iname '*.go' | grep -vE '^./(.build|Godeps)/' | xargs -n1 -P1 $(GOFMT) -w -s=true tag: git tag $(VERSION) git push --tags search_index: $(GODOC) -index -write_index -index_files='search_index' # source_path is responsible for ensuring that the builder has not done anything # stupid like working on Prometheus outside of ${GOPATH}. source_path: -[ -d "$(FULL_GOPATH)" ] || { mkdir -vp $(FULL_GOPATH_BASE) ; ln -s "$(PWD)" "$(FULL_GOPATH)" ; } [ -d "$(FULL_GOPATH)" ] documentation: search_index $(GODOC) -http=:6060 -index -index_files='search_index' clean: rm -rf $(MAKE_ARTIFACTS) find . -iname '*~' -exec rm -f '{}' ';' find . -iname '*#' -exec rm -f '{}' ';' .PHONY: advice build clean documentation format source_path test golang-prometheus-client-0.7.0+ds/NOTICE000066400000000000000000000017121256572052500177520ustar00rootroot00000000000000Prometheus instrumentation library for Go applications Copyright 2012-2015 The Prometheus Authors This product includes software developed at SoundCloud Ltd. (http://soundcloud.com/). The following components are included in this product: goautoneg http://bitbucket.org/ww/goautoneg Copyright 2011, Open Knowledge Foundation Ltd. See README.txt for license details. perks - a fork of https://github.com/bmizerany/perks https://github.com/beorn7/perks Copyright 2013-2015 Blake Mizerany, Björn Rabenstein See https://github.com/beorn7/perks/blob/master/README.md for license details. Go support for Protocol Buffers - Google's data interchange format http://github.com/golang/protobuf/ Copyright 2010 The Go Authors See source code for license details. Support for streaming Protocol Buffer messages for the Go language (golang). https://github.com/matttproud/golang_protobuf_extensions Copyright 2013 Matt T. Proud Licensed under the Apache License, Version 2.0 golang-prometheus-client-0.7.0+ds/README.md000066400000000000000000000023201256572052500203210ustar00rootroot00000000000000# Prometheus Go client library [![Build Status](https://travis-ci.org/prometheus/client_golang.svg?branch=master)](https://travis-ci.org/prometheus/client_golang) [![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) This is the [Go](http://golang.org) client library for [Prometheus](http://prometheus.io). ## Instrumenting applications For instrumenting your Go application code with Prometheus metrics, see the [documentation of the exposition client](https://godoc.org/github.com/prometheus/client_golang/prometheus). ## Consuming exported metrics If you want to process metrics exported by a Prometheus client, see the [consumption library documentation](https://godoc.org/github.com/prometheus/client_golang/extraction). (The Prometheus server is using that library.) # Testing $ go test ./... ## Contributing and community See the [contributing guidelines](CONTRIBUTING.md) and the [Community section](http://prometheus.io/community/) of the homepage. golang-prometheus-client-0.7.0+ds/VERSION000066400000000000000000000000061256572052500201110ustar00rootroot000000000000000.7.0 golang-prometheus-client-0.7.0+ds/examples/000077500000000000000000000000001256572052500206635ustar00rootroot00000000000000golang-prometheus-client-0.7.0+ds/examples/random/000077500000000000000000000000001256572052500221435ustar00rootroot00000000000000golang-prometheus-client-0.7.0+ds/examples/random/main.go000066400000000000000000000066301256572052500234230ustar00rootroot00000000000000// Copyright 2015 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // A simple example exposing fictional RPC latencies with different types of // random distributions (uniform, normal, and exponential) as Prometheus // metrics. package main import ( "flag" "math" "math/rand" "net/http" "time" "github.com/prometheus/client_golang/prometheus" ) var ( addr = flag.String("listen-address", ":8080", "The address to listen on for HTTP requests.") uniformDomain = flag.Float64("uniform.domain", 200, "The domain for the uniform distribution.") normDomain = flag.Float64("normal.domain", 200, "The domain for the normal distribution.") normMean = flag.Float64("normal.mean", 10, "The mean for the normal distribution.") oscillationPeriod = flag.Duration("oscillation-period", 10*time.Minute, "The duration of the rate oscillation period.") ) var ( // Create a summary to track fictional interservice RPC latencies for three // distinct services with different latency distributions. These services are // differentiated via a "service" label. rpcDurations = prometheus.NewSummaryVec( prometheus.SummaryOpts{ Name: "rpc_durations_microseconds", Help: "RPC latency distributions.", }, []string{"service"}, ) // The same as above, but now as a histogram, and only for the normal // distribution. The buckets are targeted to the parameters of the // normal distribution, with 20 buckets centered on the mean, each // half-sigma wide. rpcDurationsHistogram = prometheus.NewHistogram(prometheus.HistogramOpts{ Name: "rpc_durations_histogram_microseconds", Help: "RPC latency distributions.", Buckets: prometheus.LinearBuckets(*normMean-5**normDomain, .5**normDomain, 20), }) ) func init() { // Register the summary and the histogram with Prometheus's default registry. prometheus.MustRegister(rpcDurations) prometheus.MustRegister(rpcDurationsHistogram) } func main() { flag.Parse() start := time.Now() oscillationFactor := func() float64 { return 2 + math.Sin(math.Sin(2*math.Pi*float64(time.Since(start))/float64(*oscillationPeriod))) } // Periodically record some sample latencies for the three services. go func() { for { v := rand.Float64() * *uniformDomain rpcDurations.WithLabelValues("uniform").Observe(v) time.Sleep(time.Duration(100*oscillationFactor()) * time.Millisecond) } }() go func() { for { v := (rand.NormFloat64() * *normDomain) + *normMean rpcDurations.WithLabelValues("normal").Observe(v) rpcDurationsHistogram.Observe(v) time.Sleep(time.Duration(75*oscillationFactor()) * time.Millisecond) } }() go func() { for { v := rand.ExpFloat64() rpcDurations.WithLabelValues("exponential").Observe(v) time.Sleep(time.Duration(50*oscillationFactor()) * time.Millisecond) } }() // Expose the registered metrics via HTTP. http.Handle("/metrics", prometheus.Handler()) http.ListenAndServe(*addr, nil) } golang-prometheus-client-0.7.0+ds/examples/simple/000077500000000000000000000000001256572052500221545ustar00rootroot00000000000000golang-prometheus-client-0.7.0+ds/examples/simple/main.go000066400000000000000000000017051256572052500234320ustar00rootroot00000000000000// Copyright 2015 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // A minimal example of how to include Prometheus instrumentation. package main import ( "flag" "net/http" "github.com/prometheus/client_golang/prometheus" ) var addr = flag.String("listen-address", ":8080", "The address to listen on for HTTP requests.") func main() { flag.Parse() http.Handle("/metrics", prometheus.Handler()) http.ListenAndServe(*addr, nil) } golang-prometheus-client-0.7.0+ds/extraction/000077500000000000000000000000001256572052500212255ustar00rootroot00000000000000golang-prometheus-client-0.7.0+ds/extraction/discriminator.go000066400000000000000000000045571256572052500244360ustar00rootroot00000000000000// Copyright 2013 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package extraction import ( "errors" "fmt" "mime" "net/http" ) // ProcessorForRequestHeader interprets a HTTP request header to determine // what Processor should be used for the given input. If no acceptable // Processor can be found, an error is returned. func ProcessorForRequestHeader(header http.Header) (Processor, error) { if header == nil { return nil, errors.New("received illegal and nil header") } mediatype, params, err := mime.ParseMediaType(header.Get("Content-Type")) if err != nil { return nil, fmt.Errorf("invalid Content-Type header %q: %s", header.Get("Content-Type"), err) } switch mediatype { case "application/vnd.google.protobuf": if params["proto"] != "io.prometheus.client.MetricFamily" { return nil, fmt.Errorf("unrecognized protocol message %s", params["proto"]) } if params["encoding"] != "delimited" { return nil, fmt.Errorf("unsupported encoding %s", params["encoding"]) } return MetricFamilyProcessor, nil case "text/plain": switch params["version"] { case "0.0.4": return Processor004, nil case "": // Fallback: most recent version. return Processor004, nil default: return nil, fmt.Errorf("unrecognized API version %s", params["version"]) } case "application/json": var prometheusAPIVersion string if params["schema"] == "prometheus/telemetry" && params["version"] != "" { prometheusAPIVersion = params["version"] } else { prometheusAPIVersion = header.Get("X-Prometheus-API-Version") } switch prometheusAPIVersion { case "0.0.2": return Processor002, nil case "0.0.1": return Processor001, nil default: return nil, fmt.Errorf("unrecognized API version %s", prometheusAPIVersion) } default: return nil, fmt.Errorf("unsupported media type %q, expected %q", mediatype, "application/json") } } golang-prometheus-client-0.7.0+ds/extraction/discriminator_test.go000066400000000000000000000070731256572052500254710ustar00rootroot00000000000000// Copyright 2013 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package extraction import ( "errors" "net/http" "testing" ) func testDiscriminatorHTTPHeader(t testing.TB) { var scenarios = []struct { input map[string]string output Processor err error }{ { output: nil, err: errors.New("received illegal and nil header"), }, { input: map[string]string{"Content-Type": "application/json", "X-Prometheus-API-Version": "0.0.0"}, output: nil, err: errors.New("unrecognized API version 0.0.0"), }, { input: map[string]string{"Content-Type": "application/json", "X-Prometheus-API-Version": "0.0.1"}, output: Processor001, err: nil, }, { input: map[string]string{"Content-Type": `application/json; schema="prometheus/telemetry"; version=0.0.0`}, output: nil, err: errors.New("unrecognized API version 0.0.0"), }, { input: map[string]string{"Content-Type": `application/json; schema="prometheus/telemetry"; version=0.0.1`}, output: Processor001, err: nil, }, { input: map[string]string{"Content-Type": `application/json; schema="prometheus/telemetry"; version=0.0.2`}, output: Processor002, err: nil, }, { input: map[string]string{"Content-Type": `application/vnd.google.protobuf; proto="io.prometheus.client.MetricFamily"; encoding="delimited"`}, output: MetricFamilyProcessor, err: nil, }, { input: map[string]string{"Content-Type": `application/vnd.google.protobuf; proto="illegal"; encoding="delimited"`}, output: nil, err: errors.New("unrecognized protocol message illegal"), }, { input: map[string]string{"Content-Type": `application/vnd.google.protobuf; proto="io.prometheus.client.MetricFamily"; encoding="illegal"`}, output: nil, err: errors.New("unsupported encoding illegal"), }, { input: map[string]string{"Content-Type": `text/plain; version=0.0.4`}, output: Processor004, err: nil, }, { input: map[string]string{"Content-Type": `text/plain`}, output: Processor004, err: nil, }, { input: map[string]string{"Content-Type": `text/plain; version=0.0.3`}, output: nil, err: errors.New("unrecognized API version 0.0.3"), }, } for i, scenario := range scenarios { var header http.Header if len(scenario.input) > 0 { header = http.Header{} } for key, value := range scenario.input { header.Add(key, value) } actual, err := ProcessorForRequestHeader(header) if scenario.err != err { if scenario.err != nil && err != nil { if scenario.err.Error() != err.Error() { t.Errorf("%d. expected %s, got %s", i, scenario.err, err) } } else if scenario.err != nil || err != nil { t.Errorf("%d. expected %s, got %s", i, scenario.err, err) } } if scenario.output != actual { t.Errorf("%d. expected %s, got %s", i, scenario.output, actual) } } } func TestDiscriminatorHTTPHeader(t *testing.T) { testDiscriminatorHTTPHeader(t) } func BenchmarkDiscriminatorHTTPHeader(b *testing.B) { for i := 0; i < b.N; i++ { testDiscriminatorHTTPHeader(b) } } golang-prometheus-client-0.7.0+ds/extraction/extraction.go000066400000000000000000000012621256572052500237350ustar00rootroot00000000000000// Copyright 2013 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package extraction decodes Prometheus clients' data streams for consumers. package extraction golang-prometheus-client-0.7.0+ds/extraction/fixtures/000077500000000000000000000000001256572052500230765ustar00rootroot00000000000000golang-prometheus-client-0.7.0+ds/extraction/fixtures/empty.json000066400000000000000000000000001256572052500251150ustar00rootroot00000000000000golang-prometheus-client-0.7.0+ds/extraction/fixtures/test0_0_1-0_0_2-large.json000066400000000000000000000565251256572052500273710ustar00rootroot00000000000000[ { "baseLabels": { "__name__": "rpc_calls_total_0", "job": "batch_job" }, "docstring": "Total count of RPC calls.", "metric": { "type": "counter", "value": [ { "labels": { "foo": "bar", "service": "zed" }, "value": 25 }, { "labels": { "foo": "baz", "service": "bar" }, "value": 25 }, { "labels": { "foo": "bar", "service": "foo" }, "value": 25 }, { "labels": { "foo": "baz", "service": "foo" }, "value": 25 } ] } }, { "baseLabels": { "__name__": "rpc_latency_microseconds_0" }, "docstring": "RPC latency summary.", "metric": { "type": "histogram", "value": [ { "labels": { "foo": "bar", "service": "foo" }, "value": { "0.010000": 15.890724674774395, "0.050000": 15.890724674774395, "0.500000": 84.63044031436561, "0.900000": 160.21100853053224, "0.990000": 172.49828748957728 } }, { "labels": { "foo": "bar", "service": "zed" }, "value": { "0.010000": 0.0459814091918713, "0.050000": 0.0459814091918713, "0.500000": 0.6120456642749681, "0.900000": 1.355915069887731, "0.990000": 1.772733213161236 } }, { "labels": { "foo": "bar", "service": "bar" }, "value": { "0.010000": 78.48563317257356, "0.050000": 78.48563317257356, "0.500000": 97.31798360385088, "0.900000": 109.89202084295582, "0.990000": 109.99626121011262 } }, { "labels": { "foo": "baz", "service": "bar" }, "value": { "0.010000": 78.48563317257356, "0.050000": 78.48563317257356, "0.500000": 97.31798360385088, "0.900000": 109.89202084295582, "0.990000": 109.99626121011262 } } ] } }, { "baseLabels": { "__name__": "rpc_calls_total_1", "job": "batch_job" }, "docstring": "Total count of RPC calls.", "metric": { "type": "counter", "value": [ { "labels": { "foo": "bar", "service": "zed" }, "value": 25 }, { "labels": { "foo": "baz", "service": "bar" }, "value": 25 }, { "labels": { "foo": "bar", "service": "foo" }, "value": 25 }, { "labels": { "foo": "baz", "service": "foo" }, "value": 25 } ] } }, { "baseLabels": { "__name__": "rpc_latency_microseconds_1" }, "docstring": "RPC latency summary.", "metric": { "type": "histogram", "value": [ { "labels": { "foo": "bar", "service": "foo" }, "value": { "0.010000": 15.890724674774395, "0.050000": 15.890724674774395, "0.500000": 84.63044031436561, "0.900000": 160.21100853053224, "0.990000": 172.49828748957728 } }, { "labels": { "foo": "bar", "service": "zed" }, "value": { "0.010000": 0.0459814091918713, "0.050000": 0.0459814091918713, "0.500000": 0.6120456642749681, "0.900000": 1.355915069887731, "0.990000": 1.772733213161236 } }, { "labels": { "foo": "bar", "service": "bar" }, "value": { "0.010000": 78.48563317257356, "0.050000": 78.48563317257356, "0.500000": 97.31798360385088, "0.900000": 109.89202084295582, "0.990000": 109.99626121011262 } }, { "labels": { "foo": "baz", "service": "bar" }, "value": { "0.010000": 78.48563317257356, "0.050000": 78.48563317257356, "0.500000": 97.31798360385088, "0.900000": 109.89202084295582, "0.990000": 109.99626121011262 } } ] } }, { "baseLabels": { "__name__": "rpc_calls_total_2", "job": "batch_job" }, "docstring": "Total count of RPC calls.", "metric": { "type": "counter", "value": [ { "labels": { "foo": "bar", "service": "zed" }, "value": 25 }, { "labels": { "foo": "baz", "service": "bar" }, "value": 25 }, { "labels": { "foo": "bar", "service": "foo" }, "value": 25 }, { "labels": { "foo": "baz", "service": "foo" }, "value": 25 } ] } }, { "baseLabels": { "__name__": "rpc_latency_microseconds_2" }, "docstring": "RPC latency summary.", "metric": { "type": "histogram", "value": [ { "labels": { "foo": "bar", "service": "foo" }, "value": { "0.010000": 15.890724674774395, "0.050000": 15.890724674774395, "0.500000": 84.63044031436561, "0.900000": 160.21100853053224, "0.990000": 172.49828748957728 } }, { "labels": { "foo": "bar", "service": "zed" }, "value": { "0.010000": 0.0459814091918713, "0.050000": 0.0459814091918713, "0.500000": 0.6120456642749681, "0.900000": 1.355915069887731, "0.990000": 1.772733213161236 } }, { "labels": { "foo": "bar", "service": "bar" }, "value": { "0.010000": 78.48563317257356, "0.050000": 78.48563317257356, "0.500000": 97.31798360385088, "0.900000": 109.89202084295582, "0.990000": 109.99626121011262 } }, { "labels": { "foo": "baz", "service": "bar" }, "value": { "0.010000": 78.48563317257356, "0.050000": 78.48563317257356, "0.500000": 97.31798360385088, "0.900000": 109.89202084295582, "0.990000": 109.99626121011262 } } ] } }, { "baseLabels": { "__name__": "rpc_calls_total_3", "job": "batch_job" }, "docstring": "Total count of RPC calls.", "metric": { "type": "counter", "value": [ { "labels": { "foo": "bar", "service": "zed" }, "value": 25 }, { "labels": { "foo": "baz", "service": "bar" }, "value": 25 }, { "labels": { "foo": "bar", "service": "foo" }, "value": 25 }, { "labels": { "foo": "baz", "service": "foo" }, "value": 25 } ] } }, { "baseLabels": { "__name__": "rpc_latency_microseconds_3" }, "docstring": "RPC latency summary.", "metric": { "type": "histogram", "value": [ { "labels": { "foo": "bar", "service": "foo" }, "value": { "0.010000": 15.890724674774395, "0.050000": 15.890724674774395, "0.500000": 84.63044031436561, "0.900000": 160.21100853053224, "0.990000": 172.49828748957728 } }, { "labels": { "foo": "bar", "service": "zed" }, "value": { "0.010000": 0.0459814091918713, "0.050000": 0.0459814091918713, "0.500000": 0.6120456642749681, "0.900000": 1.355915069887731, "0.990000": 1.772733213161236 } }, { "labels": { "foo": "bar", "service": "bar" }, "value": { "0.010000": 78.48563317257356, "0.050000": 78.48563317257356, "0.500000": 97.31798360385088, "0.900000": 109.89202084295582, "0.990000": 109.99626121011262 } }, { "labels": { "foo": "baz", "service": "bar" }, "value": { "0.010000": 78.48563317257356, "0.050000": 78.48563317257356, "0.500000": 97.31798360385088, "0.900000": 109.89202084295582, "0.990000": 109.99626121011262 } } ] } }, { "baseLabels": { "__name__": "rpc_calls_total_4", "job": "batch_job" }, "docstring": "Total count of RPC calls.", "metric": { "type": "counter", "value": [ { "labels": { "foo": "bar", "service": "zed" }, "value": 25 }, { "labels": { "foo": "baz", "service": "bar" }, "value": 25 }, { "labels": { "foo": "bar", "service": "foo" }, "value": 25 }, { "labels": { "foo": "baz", "service": "foo" }, "value": 25 } ] } }, { "baseLabels": { "__name__": "rpc_latency_microseconds_4" }, "docstring": "RPC latency summary.", "metric": { "type": "histogram", "value": [ { "labels": { "foo": "bar", "service": "foo" }, "value": { "0.010000": 15.890724674774395, "0.050000": 15.890724674774395, "0.500000": 84.63044031436561, "0.900000": 160.21100853053224, "0.990000": 172.49828748957728 } }, { "labels": { "foo": "bar", "service": "zed" }, "value": { "0.010000": 0.0459814091918713, "0.050000": 0.0459814091918713, "0.500000": 0.6120456642749681, "0.900000": 1.355915069887731, "0.990000": 1.772733213161236 } }, { "labels": { "foo": "bar", "service": "bar" }, "value": { "0.010000": 78.48563317257356, "0.050000": 78.48563317257356, "0.500000": 97.31798360385088, "0.900000": 109.89202084295582, "0.990000": 109.99626121011262 } }, { "labels": { "foo": "baz", "service": "bar" }, "value": { "0.010000": 78.48563317257356, "0.050000": 78.48563317257356, "0.500000": 97.31798360385088, "0.900000": 109.89202084295582, "0.990000": 109.99626121011262 } } ] } }, { "baseLabels": { "__name__": "rpc_calls_total_5", "job": "batch_job" }, "docstring": "Total count of RPC calls.", "metric": { "type": "counter", "value": [ { "labels": { "foo": "bar", "service": "zed" }, "value": 25 }, { "labels": { "foo": "baz", "service": "bar" }, "value": 25 }, { "labels": { "foo": "bar", "service": "foo" }, "value": 25 }, { "labels": { "foo": "baz", "service": "foo" }, "value": 25 } ] } }, { "baseLabels": { "__name__": "rpc_latency_microseconds_5" }, "docstring": "RPC latency summary.", "metric": { "type": "histogram", "value": [ { "labels": { "foo": "bar", "service": "foo" }, "value": { "0.010000": 15.890724674774395, "0.050000": 15.890724674774395, "0.500000": 84.63044031436561, "0.900000": 160.21100853053224, "0.990000": 172.49828748957728 } }, { "labels": { "foo": "bar", "service": "zed" }, "value": { "0.010000": 0.0459814091918713, "0.050000": 0.0459814091918713, "0.500000": 0.6120456642749681, "0.900000": 1.355915069887731, "0.990000": 1.772733213161236 } }, { "labels": { "foo": "bar", "service": "bar" }, "value": { "0.010000": 78.48563317257356, "0.050000": 78.48563317257356, "0.500000": 97.31798360385088, "0.900000": 109.89202084295582, "0.990000": 109.99626121011262 } }, { "labels": { "foo": "baz", "service": "bar" }, "value": { "0.010000": 78.48563317257356, "0.050000": 78.48563317257356, "0.500000": 97.31798360385088, "0.900000": 109.89202084295582, "0.990000": 109.99626121011262 } } ] } }, { "baseLabels": { "__name__": "rpc_calls_total_6", "job": "batch_job" }, "docstring": "Total count of RPC calls.", "metric": { "type": "counter", "value": [ { "labels": { "foo": "bar", "service": "zed" }, "value": 25 }, { "labels": { "foo": "baz", "service": "bar" }, "value": 25 }, { "labels": { "foo": "bar", "service": "foo" }, "value": 25 }, { "labels": { "foo": "baz", "service": "foo" }, "value": 25 } ] } }, { "baseLabels": { "__name__": "rpc_latency_microseconds_6" }, "docstring": "RPC latency summary.", "metric": { "type": "histogram", "value": [ { "labels": { "foo": "bar", "service": "foo" }, "value": { "0.010000": 15.890724674774395, "0.050000": 15.890724674774395, "0.500000": 84.63044031436561, "0.900000": 160.21100853053224, "0.990000": 172.49828748957728 } }, { "labels": { "foo": "bar", "service": "zed" }, "value": { "0.010000": 0.0459814091918713, "0.050000": 0.0459814091918713, "0.500000": 0.6120456642749681, "0.900000": 1.355915069887731, "0.990000": 1.772733213161236 } }, { "labels": { "foo": "bar", "service": "bar" }, "value": { "0.010000": 78.48563317257356, "0.050000": 78.48563317257356, "0.500000": 97.31798360385088, "0.900000": 109.89202084295582, "0.990000": 109.99626121011262 } }, { "labels": { "foo": "baz", "service": "bar" }, "value": { "0.010000": 78.48563317257356, "0.050000": 78.48563317257356, "0.500000": 97.31798360385088, "0.900000": 109.89202084295582, "0.990000": 109.99626121011262 } } ] } }, { "baseLabels": { "__name__": "rpc_calls_total_7", "job": "batch_job" }, "docstring": "Total count of RPC calls.", "metric": { "type": "counter", "value": [ { "labels": { "foo": "bar", "service": "zed" }, "value": 25 }, { "labels": { "foo": "baz", "service": "bar" }, "value": 25 }, { "labels": { "foo": "bar", "service": "foo" }, "value": 25 }, { "labels": { "foo": "baz", "service": "foo" }, "value": 25 } ] } }, { "baseLabels": { "__name__": "rpc_latency_microseconds_7" }, "docstring": "RPC latency summary.", "metric": { "type": "histogram", "value": [ { "labels": { "foo": "bar", "service": "foo" }, "value": { "0.010000": 15.890724674774395, "0.050000": 15.890724674774395, "0.500000": 84.63044031436561, "0.900000": 160.21100853053224, "0.990000": 172.49828748957728 } }, { "labels": { "foo": "bar", "service": "zed" }, "value": { "0.010000": 0.0459814091918713, "0.050000": 0.0459814091918713, "0.500000": 0.6120456642749681, "0.900000": 1.355915069887731, "0.990000": 1.772733213161236 } }, { "labels": { "foo": "bar", "service": "bar" }, "value": { "0.010000": 78.48563317257356, "0.050000": 78.48563317257356, "0.500000": 97.31798360385088, "0.900000": 109.89202084295582, "0.990000": 109.99626121011262 } }, { "labels": { "foo": "baz", "service": "bar" }, "value": { "0.010000": 78.48563317257356, "0.050000": 78.48563317257356, "0.500000": 97.31798360385088, "0.900000": 109.89202084295582, "0.990000": 109.99626121011262 } } ] } }, { "baseLabels": { "__name__": "rpc_calls_total_8", "job": "batch_job" }, "docstring": "Total count of RPC calls.", "metric": { "type": "counter", "value": [ { "labels": { "foo": "bar", "service": "zed" }, "value": 25 }, { "labels": { "foo": "baz", "service": "bar" }, "value": 25 }, { "labels": { "foo": "bar", "service": "foo" }, "value": 25 }, { "labels": { "foo": "baz", "service": "foo" }, "value": 25 } ] } }, { "baseLabels": { "__name__": "rpc_latency_microseconds_8" }, "docstring": "RPC latency summary.", "metric": { "type": "histogram", "value": [ { "labels": { "foo": "bar", "service": "foo" }, "value": { "0.010000": 15.890724674774395, "0.050000": 15.890724674774395, "0.500000": 84.63044031436561, "0.900000": 160.21100853053224, "0.990000": 172.49828748957728 } }, { "labels": { "foo": "bar", "service": "zed" }, "value": { "0.010000": 0.0459814091918713, "0.050000": 0.0459814091918713, "0.500000": 0.6120456642749681, "0.900000": 1.355915069887731, "0.990000": 1.772733213161236 } }, { "labels": { "foo": "bar", "service": "bar" }, "value": { "0.010000": 78.48563317257356, "0.050000": 78.48563317257356, "0.500000": 97.31798360385088, "0.900000": 109.89202084295582, "0.990000": 109.99626121011262 } }, { "labels": { "foo": "baz", "service": "bar" }, "value": { "0.010000": 78.48563317257356, "0.050000": 78.48563317257356, "0.500000": 97.31798360385088, "0.900000": 109.89202084295582, "0.990000": 109.99626121011262 } } ] } }, { "baseLabels": { "__name__": "rpc_calls_total_9", "job": "batch_job" }, "docstring": "Total count of RPC calls.", "metric": { "type": "counter", "value": [ { "labels": { "foo": "bar", "service": "zed" }, "value": 25 }, { "labels": { "foo": "baz", "service": "bar" }, "value": 25 }, { "labels": { "foo": "bar", "service": "foo" }, "value": 25 }, { "labels": { "foo": "baz", "service": "foo" }, "value": 25 } ] } }, { "baseLabels": { "__name__": "rpc_latency_microseconds_9" }, "docstring": "RPC latency summary.", "metric": { "type": "histogram", "value": [ { "labels": { "foo": "bar", "service": "foo" }, "value": { "0.010000": 15.890724674774395, "0.050000": 15.890724674774395, "0.500000": 84.63044031436561, "0.900000": 160.21100853053224, "0.990000": 172.49828748957728 } }, { "labels": { "foo": "bar", "service": "zed" }, "value": { "0.010000": 0.0459814091918713, "0.050000": 0.0459814091918713, "0.500000": 0.6120456642749681, "0.900000": 1.355915069887731, "0.990000": 1.772733213161236 } }, { "labels": { "foo": "bar", "service": "bar" }, "value": { "0.010000": 78.48563317257356, "0.050000": 78.48563317257356, "0.500000": 97.31798360385088, "0.900000": 109.89202084295582, "0.990000": 109.99626121011262 } }, { "labels": { "foo": "baz", "service": "bar" }, "value": { "0.010000": 78.48563317257356, "0.050000": 78.48563317257356, "0.500000": 97.31798360385088, "0.900000": 109.89202084295582, "0.990000": 109.99626121011262 } } ] } } ] golang-prometheus-client-0.7.0+ds/extraction/fixtures/test0_0_1-0_0_2.json000066400000000000000000000034101256572052500262620ustar00rootroot00000000000000[ { "baseLabels": { "__name__": "rpc_calls_total", "job": "batch_job" }, "docstring": "RPC calls.", "metric": { "type": "counter", "value": [ { "labels": { "service": "zed" }, "value": 25 }, { "labels": { "service": "bar" }, "value": 25 }, { "labels": { "service": "foo" }, "value": 25 } ] } }, { "baseLabels": { "__name__": "rpc_latency_microseconds" }, "docstring": "RPC latency.", "metric": { "type": "histogram", "value": [ { "labels": { "service": "foo" }, "value": { "0.010000": 15.890724674774395, "0.050000": 15.890724674774395, "0.500000": 84.63044031436561, "0.900000": 160.21100853053224, "0.990000": 172.49828748957728 } }, { "labels": { "service": "zed" }, "value": { "0.010000": 0.0459814091918713, "0.050000": 0.0459814091918713, "0.500000": 0.6120456642749681, "0.900000": 1.355915069887731, "0.990000": 1.772733213161236 } }, { "labels": { "service": "bar" }, "value": { "0.010000": 78.48563317257356, "0.050000": 78.48563317257356, "0.500000": 97.31798360385088, "0.900000": 109.89202084295582, "0.990000": 109.99626121011262 } } ] } } ] golang-prometheus-client-0.7.0+ds/extraction/metricfamilyprocessor.go000066400000000000000000000205021256572052500262000ustar00rootroot00000000000000// Copyright 2013 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package extraction import ( "fmt" "io" "math" dto "github.com/prometheus/client_model/go" "github.com/matttproud/golang_protobuf_extensions/pbutil" "github.com/prometheus/client_golang/model" ) type metricFamilyProcessor struct{} // MetricFamilyProcessor decodes varint encoded record length-delimited streams // of io.prometheus.client.MetricFamily. // // See http://godoc.org/github.com/matttproud/golang_protobuf_extensions/ext for // more details. var MetricFamilyProcessor = &metricFamilyProcessor{} func (m *metricFamilyProcessor) ProcessSingle(i io.Reader, out Ingester, o *ProcessOptions) error { family := &dto.MetricFamily{} for { family.Reset() if _, err := pbutil.ReadDelimited(i, family); err != nil { if err == io.EOF { return nil } return err } if err := extractMetricFamily(out, o, family); err != nil { return err } } } func extractMetricFamily(out Ingester, o *ProcessOptions, family *dto.MetricFamily) error { switch family.GetType() { case dto.MetricType_COUNTER: if err := extractCounter(out, o, family); err != nil { return err } case dto.MetricType_GAUGE: if err := extractGauge(out, o, family); err != nil { return err } case dto.MetricType_SUMMARY: if err := extractSummary(out, o, family); err != nil { return err } case dto.MetricType_UNTYPED: if err := extractUntyped(out, o, family); err != nil { return err } case dto.MetricType_HISTOGRAM: if err := extractHistogram(out, o, family); err != nil { return err } } return nil } func extractCounter(out Ingester, o *ProcessOptions, f *dto.MetricFamily) error { samples := make(model.Samples, 0, len(f.Metric)) for _, m := range f.Metric { if m.Counter == nil { continue } sample := &model.Sample{ Metric: model.Metric{}, Value: model.SampleValue(m.Counter.GetValue()), } samples = append(samples, sample) if m.TimestampMs != nil { sample.Timestamp = model.TimestampFromUnixNano(*m.TimestampMs * 1000000) } else { sample.Timestamp = o.Timestamp } metric := sample.Metric for _, p := range m.Label { metric[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue()) } metric[model.MetricNameLabel] = model.LabelValue(f.GetName()) } return out.Ingest(samples) } func extractGauge(out Ingester, o *ProcessOptions, f *dto.MetricFamily) error { samples := make(model.Samples, 0, len(f.Metric)) for _, m := range f.Metric { if m.Gauge == nil { continue } sample := &model.Sample{ Metric: model.Metric{}, Value: model.SampleValue(m.Gauge.GetValue()), } samples = append(samples, sample) if m.TimestampMs != nil { sample.Timestamp = model.TimestampFromUnixNano(*m.TimestampMs * 1000000) } else { sample.Timestamp = o.Timestamp } metric := sample.Metric for _, p := range m.Label { metric[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue()) } metric[model.MetricNameLabel] = model.LabelValue(f.GetName()) } return out.Ingest(samples) } func extractSummary(out Ingester, o *ProcessOptions, f *dto.MetricFamily) error { samples := make(model.Samples, 0, len(f.Metric)) for _, m := range f.Metric { if m.Summary == nil { continue } timestamp := o.Timestamp if m.TimestampMs != nil { timestamp = model.TimestampFromUnixNano(*m.TimestampMs * 1000000) } for _, q := range m.Summary.Quantile { sample := &model.Sample{ Metric: model.Metric{}, Value: model.SampleValue(q.GetValue()), Timestamp: timestamp, } samples = append(samples, sample) metric := sample.Metric for _, p := range m.Label { metric[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue()) } // BUG(matt): Update other names to "quantile". metric[model.LabelName(model.QuantileLabel)] = model.LabelValue(fmt.Sprint(q.GetQuantile())) metric[model.MetricNameLabel] = model.LabelValue(f.GetName()) } if m.Summary.SampleSum != nil { sum := &model.Sample{ Metric: model.Metric{}, Value: model.SampleValue(m.Summary.GetSampleSum()), Timestamp: timestamp, } samples = append(samples, sum) metric := sum.Metric for _, p := range m.Label { metric[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue()) } metric[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_sum") } if m.Summary.SampleCount != nil { count := &model.Sample{ Metric: model.Metric{}, Value: model.SampleValue(m.Summary.GetSampleCount()), Timestamp: timestamp, } samples = append(samples, count) metric := count.Metric for _, p := range m.Label { metric[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue()) } metric[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_count") } } return out.Ingest(samples) } func extractUntyped(out Ingester, o *ProcessOptions, f *dto.MetricFamily) error { samples := make(model.Samples, 0, len(f.Metric)) for _, m := range f.Metric { if m.Untyped == nil { continue } sample := &model.Sample{ Metric: model.Metric{}, Value: model.SampleValue(m.Untyped.GetValue()), } samples = append(samples, sample) if m.TimestampMs != nil { sample.Timestamp = model.TimestampFromUnixNano(*m.TimestampMs * 1000000) } else { sample.Timestamp = o.Timestamp } metric := sample.Metric for _, p := range m.Label { metric[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue()) } metric[model.MetricNameLabel] = model.LabelValue(f.GetName()) } return out.Ingest(samples) } func extractHistogram(out Ingester, o *ProcessOptions, f *dto.MetricFamily) error { samples := make(model.Samples, 0, len(f.Metric)) for _, m := range f.Metric { if m.Histogram == nil { continue } timestamp := o.Timestamp if m.TimestampMs != nil { timestamp = model.TimestampFromUnixNano(*m.TimestampMs * 1000000) } infSeen := false for _, q := range m.Histogram.Bucket { sample := &model.Sample{ Metric: model.Metric{}, Value: model.SampleValue(q.GetCumulativeCount()), Timestamp: timestamp, } samples = append(samples, sample) metric := sample.Metric for _, p := range m.Label { metric[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue()) } metric[model.LabelName(model.BucketLabel)] = model.LabelValue(fmt.Sprint(q.GetUpperBound())) metric[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_bucket") if math.IsInf(q.GetUpperBound(), +1) { infSeen = true } } if m.Histogram.SampleSum != nil { sum := &model.Sample{ Metric: model.Metric{}, Value: model.SampleValue(m.Histogram.GetSampleSum()), Timestamp: timestamp, } samples = append(samples, sum) metric := sum.Metric for _, p := range m.Label { metric[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue()) } metric[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_sum") } if m.Histogram.SampleCount != nil { count := &model.Sample{ Metric: model.Metric{}, Value: model.SampleValue(m.Histogram.GetSampleCount()), Timestamp: timestamp, } samples = append(samples, count) metric := count.Metric for _, p := range m.Label { metric[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue()) } metric[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_count") if !infSeen { infBucket := &model.Sample{ Metric: model.Metric{}, Value: count.Value, Timestamp: timestamp, } samples = append(samples, infBucket) metric := infBucket.Metric for _, p := range m.Label { metric[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue()) } metric[model.LabelName(model.BucketLabel)] = model.LabelValue("+Inf") metric[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_bucket") } } } return out.Ingest(samples) } golang-prometheus-client-0.7.0+ds/extraction/metricfamilyprocessor_test.go000066400000000000000000000121651256572052500272450ustar00rootroot00000000000000// Copyright 2013 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package extraction import ( "sort" "strings" "testing" "github.com/prometheus/client_golang/model" ) var testTime = model.Now() type metricFamilyProcessorScenario struct { in string expected, actual []model.Samples } func (s *metricFamilyProcessorScenario) Ingest(samples model.Samples) error { s.actual = append(s.actual, samples) return nil } func (s *metricFamilyProcessorScenario) test(t *testing.T, set int) { i := strings.NewReader(s.in) o := &ProcessOptions{ Timestamp: testTime, } err := MetricFamilyProcessor.ProcessSingle(i, s, o) if err != nil { t.Fatalf("%d. got error: %s", set, err) } if len(s.expected) != len(s.actual) { t.Fatalf("%d. expected length %d, got %d", set, len(s.expected), len(s.actual)) } for i, expected := range s.expected { sort.Sort(s.actual[i]) sort.Sort(expected) if !expected.Equal(s.actual[i]) { t.Errorf("%d.%d. expected %s, got %s", set, i, expected, s.actual[i]) } } } func TestMetricFamilyProcessor(t *testing.T) { scenarios := []metricFamilyProcessorScenario{ { in: "", }, { in: "\x8f\x01\n\rrequest_count\x12\x12Number of requests\x18\x00\"0\n#\n\x0fsome_label_name\x12\x10some_label_value\x1a\t\t\x00\x00\x00\x00\x00\x00E\xc0\"6\n)\n\x12another_label_name\x12\x13another_label_value\x1a\t\t\x00\x00\x00\x00\x00\x00U@", expected: []model.Samples{ model.Samples{ &model.Sample{ Metric: model.Metric{model.MetricNameLabel: "request_count", "some_label_name": "some_label_value"}, Value: -42, Timestamp: testTime, }, &model.Sample{ Metric: model.Metric{model.MetricNameLabel: "request_count", "another_label_name": "another_label_value"}, Value: 84, Timestamp: testTime, }, }, }, }, { in: "\xb9\x01\n\rrequest_count\x12\x12Number of requests\x18\x02\"O\n#\n\x0fsome_label_name\x12\x10some_label_value\"(\x1a\x12\t\xaeG\xe1z\x14\xae\xef?\x11\x00\x00\x00\x00\x00\x00E\xc0\x1a\x12\t+\x87\x16\xd9\xce\xf7\xef?\x11\x00\x00\x00\x00\x00\x00U\xc0\"A\n)\n\x12another_label_name\x12\x13another_label_value\"\x14\x1a\x12\t\x00\x00\x00\x00\x00\x00\xe0?\x11\x00\x00\x00\x00\x00\x00$@", expected: []model.Samples{ model.Samples{ &model.Sample{ Metric: model.Metric{model.MetricNameLabel: "request_count", "some_label_name": "some_label_value", "quantile": "0.99"}, Value: -42, Timestamp: testTime, }, &model.Sample{ Metric: model.Metric{model.MetricNameLabel: "request_count", "some_label_name": "some_label_value", "quantile": "0.999"}, Value: -84, Timestamp: testTime, }, &model.Sample{ Metric: model.Metric{model.MetricNameLabel: "request_count", "another_label_name": "another_label_value", "quantile": "0.5"}, Value: 10, Timestamp: testTime, }, }, }, }, { in: "\x8d\x01\n\x1drequest_duration_microseconds\x12\x15The response latency.\x18\x04\"S:Q\b\x85\x15\x11\xcd\xcc\xccL\x8f\xcb:A\x1a\v\b{\x11\x00\x00\x00\x00\x00\x00Y@\x1a\f\b\x9c\x03\x11\x00\x00\x00\x00\x00\x00^@\x1a\f\b\xd0\x04\x11\x00\x00\x00\x00\x00\x00b@\x1a\f\b\xf4\v\x11\x9a\x99\x99\x99\x99\x99e@\x1a\f\b\x85\x15\x11\x00\x00\x00\x00\x00\x00\xf0\u007f", expected: []model.Samples{ model.Samples{ &model.Sample{ Metric: model.Metric{model.MetricNameLabel: "request_duration_microseconds_bucket", "le": "100"}, Value: 123, Timestamp: testTime, }, &model.Sample{ Metric: model.Metric{model.MetricNameLabel: "request_duration_microseconds_bucket", "le": "120"}, Value: 412, Timestamp: testTime, }, &model.Sample{ Metric: model.Metric{model.MetricNameLabel: "request_duration_microseconds_bucket", "le": "144"}, Value: 592, Timestamp: testTime, }, &model.Sample{ Metric: model.Metric{model.MetricNameLabel: "request_duration_microseconds_bucket", "le": "172.8"}, Value: 1524, Timestamp: testTime, }, &model.Sample{ Metric: model.Metric{model.MetricNameLabel: "request_duration_microseconds_bucket", "le": "+Inf"}, Value: 2693, Timestamp: testTime, }, &model.Sample{ Metric: model.Metric{model.MetricNameLabel: "request_duration_microseconds_sum"}, Value: 1756047.3, Timestamp: testTime, }, &model.Sample{ Metric: model.Metric{model.MetricNameLabel: "request_duration_microseconds_count"}, Value: 2693, Timestamp: testTime, }, }, }, }, } for i, scenario := range scenarios { scenario.test(t, i) } } golang-prometheus-client-0.7.0+ds/extraction/processor.go000066400000000000000000000051521256572052500235760ustar00rootroot00000000000000// Copyright 2013 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package extraction import ( "io" "time" "github.com/prometheus/client_golang/model" ) // ProcessOptions dictates how the interpreted stream should be rendered for // consumption. type ProcessOptions struct { // Timestamp is added to each value from the stream that has no explicit // timestamp set. Timestamp model.Timestamp } // Ingester consumes result streams in whatever way is desired by the user. type Ingester interface { Ingest(model.Samples) error } // Processor is responsible for decoding the actual message responses from // stream into a format that can be consumed with the end result written // to the results channel. type Processor interface { // ProcessSingle treats the input as a single self-contained message body and // transforms it accordingly. It has no support for streaming. ProcessSingle(in io.Reader, out Ingester, o *ProcessOptions) error } // Helper function to convert map[string]string into LabelSet. // // NOTE: This should be deleted when support for go 1.0.3 is removed; 1.1 is // smart enough to unmarshal JSON objects into LabelSet directly. func labelSet(labels map[string]string) model.LabelSet { labelset := make(model.LabelSet, len(labels)) for k, v := range labels { labelset[model.LabelName(k)] = model.LabelValue(v) } return labelset } // A basic interface only useful in testing contexts for dispensing the time // in a controlled manner. type instantProvider interface { // The current instant. Now() time.Time } // Clock is a simple means for fluently wrapping around standard Go timekeeping // mechanisms to enhance testability without compromising code readability. // // It is sufficient for use on bare initialization. A provider should be // set only for test contexts. When not provided, it emits the current // system time. type clock struct { // The underlying means through which time is provided, if supplied. Provider instantProvider } // Emit the current instant. func (t *clock) Now() time.Time { if t.Provider == nil { return time.Now() } return t.Provider.Now() } golang-prometheus-client-0.7.0+ds/extraction/processor0_0_1.go000066400000000000000000000067371256572052500243270ustar00rootroot00000000000000// Copyright 2013 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package extraction import ( "encoding/json" "fmt" "io" "io/ioutil" "github.com/prometheus/client_golang/model" ) const ( baseLabels001 = "baseLabels" counter001 = "counter" docstring001 = "docstring" gauge001 = "gauge" histogram001 = "histogram" labels001 = "labels" metric001 = "metric" type001 = "type" value001 = "value" percentile001 = "percentile" ) // Processor001 is responsible for decoding payloads from protocol version // 0.0.1. var Processor001 = &processor001{} // processor001 is responsible for handling API version 0.0.1. type processor001 struct{} // entity001 represents a the JSON structure that 0.0.1 uses. type entity001 []struct { BaseLabels map[string]string `json:"baseLabels"` Docstring string `json:"docstring"` Metric struct { MetricType string `json:"type"` Value []struct { Labels map[string]string `json:"labels"` Value interface{} `json:"value"` } `json:"value"` } `json:"metric"` } func (p *processor001) ProcessSingle(in io.Reader, out Ingester, o *ProcessOptions) error { // TODO(matt): Replace with plain-jane JSON unmarshalling. buffer, err := ioutil.ReadAll(in) if err != nil { return err } entities := entity001{} if err = json.Unmarshal(buffer, &entities); err != nil { return err } // TODO(matt): This outer loop is a great basis for parallelization. pendingSamples := model.Samples{} for _, entity := range entities { for _, value := range entity.Metric.Value { labels := labelSet(entity.BaseLabels).Merge(labelSet(value.Labels)) switch entity.Metric.MetricType { case gauge001, counter001: sampleValue, ok := value.Value.(float64) if !ok { return fmt.Errorf("could not convert value from %s %s to float64", entity, value) } pendingSamples = append(pendingSamples, &model.Sample{ Metric: model.Metric(labels), Timestamp: o.Timestamp, Value: model.SampleValue(sampleValue), }) break case histogram001: sampleValue, ok := value.Value.(map[string]interface{}) if !ok { return fmt.Errorf("could not convert value from %q to a map[string]interface{}", value.Value) } for percentile, percentileValue := range sampleValue { individualValue, ok := percentileValue.(float64) if !ok { return fmt.Errorf("could not convert value from %q to a float64", percentileValue) } childMetric := make(map[model.LabelName]model.LabelValue, len(labels)+1) for k, v := range labels { childMetric[k] = v } childMetric[model.LabelName(percentile001)] = model.LabelValue(percentile) pendingSamples = append(pendingSamples, &model.Sample{ Metric: model.Metric(childMetric), Timestamp: o.Timestamp, Value: model.SampleValue(individualValue), }) } break } } } if len(pendingSamples) > 0 { return out.Ingest(pendingSamples) } return nil } golang-prometheus-client-0.7.0+ds/extraction/processor0_0_1_test.go000066400000000000000000000141541256572052500253560ustar00rootroot00000000000000// Copyright 2013 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package extraction import ( "errors" "os" "path" "sort" "testing" "github.com/prometheus/client_golang/model" ) var test001Time = model.Now() type testProcessor001ProcessScenario struct { in string expected, actual []model.Samples err error } func (s *testProcessor001ProcessScenario) Ingest(samples model.Samples) error { s.actual = append(s.actual, samples) return nil } func (s *testProcessor001ProcessScenario) test(t testing.TB, set int) { reader, err := os.Open(path.Join("fixtures", s.in)) if err != nil { t.Fatalf("%d. couldn't open scenario input file %s: %s", set, s.in, err) } options := &ProcessOptions{ Timestamp: test001Time, } err = Processor001.ProcessSingle(reader, s, options) if s.err != err && (s.err == nil || err == nil || err.Error() != s.err.Error()) { t.Fatalf("%d. expected err of %s, got %s", set, s.err, err) } if len(s.actual) != len(s.expected) { t.Fatalf("%d. expected output length of %d, got %d", set, len(s.expected), len(s.actual)) } for i, expected := range s.expected { sort.Sort(s.actual[i]) sort.Sort(expected) if !expected.Equal(s.actual[i]) { t.Errorf("%d.%d. expected %s, got %s", set, i, expected, s.actual[i]) } } } func testProcessor001Process(t testing.TB) { var scenarios = []testProcessor001ProcessScenario{ { in: "empty.json", err: errors.New("unexpected end of JSON input"), }, { in: "test0_0_1-0_0_2.json", expected: []model.Samples{ model.Samples{ &model.Sample{ Metric: model.Metric{"service": "zed", model.MetricNameLabel: "rpc_calls_total", "job": "batch_job"}, Value: 25, Timestamp: test001Time, }, &model.Sample{ Metric: model.Metric{"service": "bar", model.MetricNameLabel: "rpc_calls_total", "job": "batch_job"}, Value: 25, Timestamp: test001Time, }, &model.Sample{ Metric: model.Metric{"service": "foo", model.MetricNameLabel: "rpc_calls_total", "job": "batch_job"}, Value: 25, Timestamp: test001Time, }, &model.Sample{ Metric: model.Metric{"percentile": "0.010000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "zed"}, Value: 0.0459814091918713, Timestamp: test001Time, }, &model.Sample{ Metric: model.Metric{"percentile": "0.010000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "bar"}, Value: 78.48563317257356, Timestamp: test001Time, }, &model.Sample{ Metric: model.Metric{"percentile": "0.010000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "foo"}, Value: 15.890724674774395, Timestamp: test001Time, }, &model.Sample{ Metric: model.Metric{"percentile": "0.050000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "zed"}, Value: 0.0459814091918713, Timestamp: test001Time, }, &model.Sample{ Metric: model.Metric{"percentile": "0.050000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "bar"}, Value: 78.48563317257356, Timestamp: test001Time, }, &model.Sample{ Metric: model.Metric{"percentile": "0.050000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "foo"}, Value: 15.890724674774395, Timestamp: test001Time, }, &model.Sample{ Metric: model.Metric{"percentile": "0.500000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "zed"}, Value: 0.6120456642749681, Timestamp: test001Time, }, &model.Sample{ Metric: model.Metric{"percentile": "0.500000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "bar"}, Value: 97.31798360385088, Timestamp: test001Time, }, &model.Sample{ Metric: model.Metric{"percentile": "0.500000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "foo"}, Value: 84.63044031436561, Timestamp: test001Time, }, &model.Sample{ Metric: model.Metric{"percentile": "0.900000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "zed"}, Value: 1.355915069887731, Timestamp: test001Time, }, &model.Sample{ Metric: model.Metric{"percentile": "0.900000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "bar"}, Value: 109.89202084295582, Timestamp: test001Time, }, &model.Sample{ Metric: model.Metric{"percentile": "0.900000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "foo"}, Value: 160.21100853053224, Timestamp: test001Time, }, &model.Sample{ Metric: model.Metric{"percentile": "0.990000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "zed"}, Value: 1.772733213161236, Timestamp: test001Time, }, &model.Sample{ Metric: model.Metric{"percentile": "0.990000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "bar"}, Value: 109.99626121011262, Timestamp: test001Time, }, &model.Sample{ Metric: model.Metric{"percentile": "0.990000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "foo"}, Value: 172.49828748957728, Timestamp: test001Time, }, }, }, }, } for i, scenario := range scenarios { scenario.test(t, i) } } func TestProcessor001Process(t *testing.T) { testProcessor001Process(t) } func BenchmarkProcessor001Process(b *testing.B) { for i := 0; i < b.N; i++ { testProcessor001Process(b) } } golang-prometheus-client-0.7.0+ds/extraction/processor0_0_2.go000066400000000000000000000056651256572052500243270ustar00rootroot00000000000000// Copyright 2013 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package extraction import ( "encoding/json" "fmt" "io" "github.com/prometheus/client_golang/model" ) // Processor002 is responsible for decoding payloads from protocol version // 0.0.2. var Processor002 = &processor002{} type histogram002 struct { Labels map[string]string `json:"labels"` Values map[string]model.SampleValue `json:"value"` } type counter002 struct { Labels map[string]string `json:"labels"` Value model.SampleValue `json:"value"` } type processor002 struct{} func (p *processor002) ProcessSingle(in io.Reader, out Ingester, o *ProcessOptions) error { // Processor for telemetry schema version 0.0.2. // container for telemetry data var entities []struct { BaseLabels map[string]string `json:"baseLabels"` Docstring string `json:"docstring"` Metric struct { Type string `json:"type"` Values json.RawMessage `json:"value"` } `json:"metric"` } if err := json.NewDecoder(in).Decode(&entities); err != nil { return err } pendingSamples := model.Samples{} for _, entity := range entities { switch entity.Metric.Type { case "counter", "gauge": var values []counter002 if err := json.Unmarshal(entity.Metric.Values, &values); err != nil { return fmt.Errorf("could not extract %s value: %s", entity.Metric.Type, err) } for _, counter := range values { labels := labelSet(entity.BaseLabels).Merge(labelSet(counter.Labels)) pendingSamples = append(pendingSamples, &model.Sample{ Metric: model.Metric(labels), Timestamp: o.Timestamp, Value: counter.Value, }) } case "histogram": var values []histogram002 if err := json.Unmarshal(entity.Metric.Values, &values); err != nil { return fmt.Errorf("could not extract %s value: %s", entity.Metric.Type, err) } for _, histogram := range values { for percentile, value := range histogram.Values { labels := labelSet(entity.BaseLabels).Merge(labelSet(histogram.Labels)) labels[model.LabelName("percentile")] = model.LabelValue(percentile) pendingSamples = append(pendingSamples, &model.Sample{ Metric: model.Metric(labels), Timestamp: o.Timestamp, Value: value, }) } } default: return fmt.Errorf("unknown metric type %q", entity.Metric.Type) } } if len(pendingSamples) > 0 { return out.Ingest(pendingSamples) } return nil } golang-prometheus-client-0.7.0+ds/extraction/processor0_0_2_test.go000066400000000000000000000155561256572052500253660ustar00rootroot00000000000000// Copyright 2013 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package extraction import ( "bytes" "errors" "io/ioutil" "os" "path" "runtime" "sort" "testing" "github.com/prometheus/client_golang/model" ) var test002Time = model.Now() type testProcessor002ProcessScenario struct { in string expected, actual []model.Samples err error } func (s *testProcessor002ProcessScenario) Ingest(samples model.Samples) error { s.actual = append(s.actual, samples) return nil } func (s *testProcessor002ProcessScenario) test(t testing.TB, set int) { reader, err := os.Open(path.Join("fixtures", s.in)) if err != nil { t.Fatalf("%d. couldn't open scenario input file %s: %s", set, s.in, err) } options := &ProcessOptions{ Timestamp: test002Time, } err = Processor002.ProcessSingle(reader, s, options) if s.err != err && (s.err == nil || err == nil || err.Error() != s.err.Error()) { t.Fatalf("%d. expected err of %s, got %s", set, s.err, err) } if len(s.actual) != len(s.expected) { t.Fatalf("%d. expected output length of %d, got %d", set, len(s.expected), len(s.actual)) } for i, expected := range s.expected { sort.Sort(s.actual[i]) sort.Sort(expected) if !expected.Equal(s.actual[i]) { t.Fatalf("%d.%d. expected %s, got %s", set, i, expected, s.actual[i]) } } } func testProcessor002Process(t testing.TB) { var scenarios = []testProcessor002ProcessScenario{ { in: "empty.json", err: errors.New("EOF"), }, { in: "test0_0_1-0_0_2.json", expected: []model.Samples{ model.Samples{ &model.Sample{ Metric: model.Metric{"service": "zed", model.MetricNameLabel: "rpc_calls_total", "job": "batch_job"}, Value: 25, Timestamp: test002Time, }, &model.Sample{ Metric: model.Metric{"service": "bar", model.MetricNameLabel: "rpc_calls_total", "job": "batch_job"}, Value: 25, Timestamp: test002Time, }, &model.Sample{ Metric: model.Metric{"service": "foo", model.MetricNameLabel: "rpc_calls_total", "job": "batch_job"}, Value: 25, Timestamp: test002Time, }, &model.Sample{ Metric: model.Metric{"percentile": "0.010000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "zed"}, Value: 0.0459814091918713, Timestamp: test002Time, }, &model.Sample{ Metric: model.Metric{"percentile": "0.010000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "bar"}, Value: 78.48563317257356, Timestamp: test002Time, }, &model.Sample{ Metric: model.Metric{"percentile": "0.010000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "foo"}, Value: 15.890724674774395, Timestamp: test002Time, }, &model.Sample{ Metric: model.Metric{"percentile": "0.050000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "zed"}, Value: 0.0459814091918713, Timestamp: test002Time, }, &model.Sample{ Metric: model.Metric{"percentile": "0.050000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "bar"}, Value: 78.48563317257356, Timestamp: test002Time, }, &model.Sample{ Metric: model.Metric{"percentile": "0.050000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "foo"}, Value: 15.890724674774395, Timestamp: test002Time, }, &model.Sample{ Metric: model.Metric{"percentile": "0.500000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "zed"}, Value: 0.6120456642749681, Timestamp: test002Time, }, &model.Sample{ Metric: model.Metric{"percentile": "0.500000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "bar"}, Value: 97.31798360385088, Timestamp: test002Time, }, &model.Sample{ Metric: model.Metric{"percentile": "0.500000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "foo"}, Value: 84.63044031436561, Timestamp: test002Time, }, &model.Sample{ Metric: model.Metric{"percentile": "0.900000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "zed"}, Value: 1.355915069887731, Timestamp: test002Time, }, &model.Sample{ Metric: model.Metric{"percentile": "0.900000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "bar"}, Value: 109.89202084295582, Timestamp: test002Time, }, &model.Sample{ Metric: model.Metric{"percentile": "0.900000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "foo"}, Value: 160.21100853053224, Timestamp: test002Time, }, &model.Sample{ Metric: model.Metric{"percentile": "0.990000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "zed"}, Value: 1.772733213161236, Timestamp: test002Time, }, &model.Sample{ Metric: model.Metric{"percentile": "0.990000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "bar"}, Value: 109.99626121011262, Timestamp: test002Time, }, &model.Sample{ Metric: model.Metric{"percentile": "0.990000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "foo"}, Value: 172.49828748957728, Timestamp: test002Time, }, }, }, }, } for i, scenario := range scenarios { scenario.test(t, i) } } func TestProcessor002Process(t *testing.T) { testProcessor002Process(t) } func BenchmarkProcessor002Process(b *testing.B) { b.StopTimer() pre := runtime.MemStats{} runtime.ReadMemStats(&pre) b.StartTimer() for i := 0; i < b.N; i++ { testProcessor002Process(b) } post := runtime.MemStats{} runtime.ReadMemStats(&post) allocated := post.TotalAlloc - pre.TotalAlloc b.Logf("Allocated %d at %f per cycle with %d cycles.", allocated, float64(allocated)/float64(b.N), b.N) } func BenchmarkProcessor002ParseOnly(b *testing.B) { b.StopTimer() data, err := ioutil.ReadFile("fixtures/test0_0_1-0_0_2-large.json") if err != nil { b.Fatal(err) } ing := fakeIngester{} b.StartTimer() for i := 0; i < b.N; i++ { if err := Processor002.ProcessSingle(bytes.NewReader(data), ing, &ProcessOptions{}); err != nil { b.Fatal(err) } } } type fakeIngester struct{} func (i fakeIngester) Ingest(model.Samples) error { return nil } golang-prometheus-client-0.7.0+ds/extraction/textprocessor.go000066400000000000000000000022631256572052500245030ustar00rootroot00000000000000// Copyright 2014 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package extraction import ( "io" "github.com/prometheus/client_golang/text" ) type processor004 struct{} // Processor004 s responsible for decoding payloads from the text based variety // of protocol version 0.0.4. var Processor004 = &processor004{} func (t *processor004) ProcessSingle(i io.Reader, out Ingester, o *ProcessOptions) error { var parser text.Parser metricFamilies, err := parser.TextToMetricFamilies(i) if err != nil { return err } for _, metricFamily := range metricFamilies { if err := extractMetricFamily(out, o, metricFamily); err != nil { return err } } return nil } golang-prometheus-client-0.7.0+ds/extraction/textprocessor_test.go000066400000000000000000000045761256572052500255530ustar00rootroot00000000000000// Copyright 2014 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package extraction import ( "sort" "strings" "testing" "github.com/prometheus/client_golang/model" ) var ( ts = model.Now() in = ` # Only a quite simple scenario with two metric families. # More complicated tests of the parser itself can be found in the text package. # TYPE mf2 counter mf2 3 mf1{label="value1"} -3.14 123456 mf1{label="value2"} 42 mf2 4 ` out = map[model.LabelValue]model.Samples{ "mf1": model.Samples{ &model.Sample{ Metric: model.Metric{model.MetricNameLabel: "mf1", "label": "value1"}, Value: -3.14, Timestamp: 123456, }, &model.Sample{ Metric: model.Metric{model.MetricNameLabel: "mf1", "label": "value2"}, Value: 42, Timestamp: ts, }, }, "mf2": model.Samples{ &model.Sample{ Metric: model.Metric{model.MetricNameLabel: "mf2"}, Value: 3, Timestamp: ts, }, &model.Sample{ Metric: model.Metric{model.MetricNameLabel: "mf2"}, Value: 4, Timestamp: ts, }, }, } ) type testIngester struct { results []model.Samples } func (i *testIngester) Ingest(s model.Samples) error { i.results = append(i.results, s) return nil } func TestTextProcessor(t *testing.T) { var ingester testIngester i := strings.NewReader(in) o := &ProcessOptions{ Timestamp: ts, } err := Processor004.ProcessSingle(i, &ingester, o) if err != nil { t.Fatal(err) } if expected, got := len(out), len(ingester.results); expected != got { t.Fatalf("Expected length %d, got %d", expected, got) } for _, r := range ingester.results { expected, ok := out[r[0].Metric[model.MetricNameLabel]] if !ok { t.Fatalf( "Unexpected metric name %q", r[0].Metric[model.MetricNameLabel], ) } sort.Sort(expected) sort.Sort(r) if !expected.Equal(r) { t.Errorf("expected %s, got %s", expected, r) } } } golang-prometheus-client-0.7.0+ds/model/000077500000000000000000000000001256572052500201455ustar00rootroot00000000000000golang-prometheus-client-0.7.0+ds/model/fingerprinting.go000066400000000000000000000050061256572052500235220ustar00rootroot00000000000000// Copyright 2013 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package model import ( "fmt" "strconv" ) // Fingerprint provides a hash-capable representation of a Metric. // For our purposes, FNV-1A 64-bit is used. type Fingerprint uint64 func (f Fingerprint) String() string { return fmt.Sprintf("%016x", uint64(f)) } // Less implements sort.Interface. func (f Fingerprint) Less(o Fingerprint) bool { return f < o } // Equal implements sort.Interface. func (f Fingerprint) Equal(o Fingerprint) bool { return f == o } // LoadFromString transforms a string representation into a Fingerprint. func (f *Fingerprint) LoadFromString(s string) error { num, err := strconv.ParseUint(s, 16, 64) if err != nil { return err } *f = Fingerprint(num) return nil } // Fingerprints represents a collection of Fingerprint subject to a given // natural sorting scheme. It implements sort.Interface. type Fingerprints []Fingerprint // Len implements sort.Interface. func (f Fingerprints) Len() int { return len(f) } // Less implements sort.Interface. func (f Fingerprints) Less(i, j int) bool { return f[i] < f[j] } // Swap implements sort.Interface. func (f Fingerprints) Swap(i, j int) { f[i], f[j] = f[j], f[i] } // FingerprintSet is a set of Fingerprints. type FingerprintSet map[Fingerprint]struct{} // Equal returns true if both sets contain the same elements (and not more). func (s FingerprintSet) Equal(o FingerprintSet) bool { if len(s) != len(o) { return false } for k := range s { if _, ok := o[k]; !ok { return false } } return true } // Intersection returns the elements contained in both sets. func (s FingerprintSet) Intersection(o FingerprintSet) FingerprintSet { myLength, otherLength := len(s), len(o) if myLength == 0 || otherLength == 0 { return FingerprintSet{} } subSet := s superSet := o if otherLength < myLength { subSet = o superSet = s } out := FingerprintSet{} for k := range subSet { if _, ok := superSet[k]; ok { out[k] = struct{}{} } } return out } golang-prometheus-client-0.7.0+ds/model/labelname.go000066400000000000000000000066411256572052500224230ustar00rootroot00000000000000// Copyright 2013 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package model import ( "encoding/json" "fmt" "regexp" "strings" ) const ( // ExportedLabelPrefix is the prefix to prepend to the label names present in // exported metrics if a label of the same name is added by the server. ExportedLabelPrefix LabelName = "exported_" // MetricNameLabel is the label name indicating the metric name of a // timeseries. MetricNameLabel LabelName = "__name__" // AddressLabel is the name of the label that holds the address of // a scrape target. AddressLabel LabelName = "__address__" // MetricsPathLabel is the name of the label that holds the path on which to // scrape a target. MetricsPathLabel LabelName = "__metrics_path__" // ReservedLabelPrefix is a prefix which is not legal in user-supplied // label names. ReservedLabelPrefix = "__" // MetaLabelPrefix is a prefix for labels that provide meta information. // Labels with this prefix are used for intermediate label processing and // will not be attached to time series. MetaLabelPrefix = "__meta_" // JobLabel is the label name indicating the job from which a timeseries // was scraped. JobLabel LabelName = "job" // InstanceLabel is the label name used for the instance label. InstanceLabel LabelName = "instance" // BucketLabel is used for the label that defines the upper bound of a // bucket of a histogram ("le" -> "less or equal"). BucketLabel = "le" // QuantileLabel is used for the label that defines the quantile in a // summary. QuantileLabel = "quantile" ) // LabelNameRE is a regular expression matching valid label names. var LabelNameRE = regexp.MustCompile("^[a-zA-Z_][a-zA-Z0-9_]*$") // A LabelName is a key for a LabelSet or Metric. It has a value associated // therewith. type LabelName string // UnmarshalYAML implements the yaml.Unmarshaler interface. func (ln *LabelName) UnmarshalYAML(unmarshal func(interface{}) error) error { var s string if err := unmarshal(&s); err != nil { return err } if !LabelNameRE.MatchString(s) { return fmt.Errorf("%q is not a valid label name", s) } *ln = LabelName(s) return nil } // UnmarshalJSON implements the json.Unmarshaler interface. func (ln *LabelName) UnmarshalJSON(b []byte) error { var s string if err := json.Unmarshal(b, &s); err != nil { return err } if !LabelNameRE.MatchString(s) { return fmt.Errorf("%q is not a valid label name", s) } *ln = LabelName(s) return nil } // LabelNames is a sortable LabelName slice. In implements sort.Interface. type LabelNames []LabelName func (l LabelNames) Len() int { return len(l) } func (l LabelNames) Less(i, j int) bool { return l[i] < l[j] } func (l LabelNames) Swap(i, j int) { l[i], l[j] = l[j], l[i] } func (l LabelNames) String() string { labelStrings := make([]string, 0, len(l)) for _, label := range l { labelStrings = append(labelStrings, string(label)) } return strings.Join(labelStrings, ", ") } golang-prometheus-client-0.7.0+ds/model/labelname_test.go000066400000000000000000000024011256572052500234500ustar00rootroot00000000000000// Copyright 2013 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package model import ( "sort" "testing" ) func testLabelNames(t testing.TB) { var scenarios = []struct { in LabelNames out LabelNames }{ { in: LabelNames{"ZZZ", "zzz"}, out: LabelNames{"ZZZ", "zzz"}, }, { in: LabelNames{"aaa", "AAA"}, out: LabelNames{"AAA", "aaa"}, }, } for i, scenario := range scenarios { sort.Sort(scenario.in) for j, expected := range scenario.out { if expected != scenario.in[j] { t.Errorf("%d.%d expected %s, got %s", i, j, expected, scenario.in[j]) } } } } func TestLabelNames(t *testing.T) { testLabelNames(t) } func BenchmarkLabelNames(b *testing.B) { for i := 0; i < b.N; i++ { testLabelNames(b) } } golang-prometheus-client-0.7.0+ds/model/labelset.go000066400000000000000000000044461256572052500222770ustar00rootroot00000000000000// Copyright 2013 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package model import ( "encoding/json" "fmt" "sort" "strings" ) // A LabelSet is a collection of LabelName and LabelValue pairs. The LabelSet // may be fully-qualified down to the point where it may resolve to a single // Metric in the data store or not. All operations that occur within the realm // of a LabelSet can emit a vector of Metric entities to which the LabelSet may // match. type LabelSet map[LabelName]LabelValue // Merge is a helper function to non-destructively merge two label sets. func (l LabelSet) Merge(other LabelSet) LabelSet { result := make(LabelSet, len(l)) for k, v := range l { result[k] = v } for k, v := range other { result[k] = v } return result } func (l LabelSet) String() string { labelStrings := make([]string, 0, len(l)) for label, value := range l { labelStrings = append(labelStrings, fmt.Sprintf("%s=%q", label, value)) } switch len(labelStrings) { case 0: return "" default: sort.Strings(labelStrings) return fmt.Sprintf("{%s}", strings.Join(labelStrings, ", ")) } } // MergeFromMetric merges Metric into this LabelSet. func (l LabelSet) MergeFromMetric(m Metric) { for k, v := range m { l[k] = v } } // UnmarshalJSON implements the json.Unmarshaler interface. func (l *LabelSet) UnmarshalJSON(b []byte) error { var m map[LabelName]LabelValue if err := json.Unmarshal(b, &m); err != nil { return err } // encoding/json only unmarshals maps of the form map[string]T. It treats // LabelName as a string and does not call its UnmarshalJSON method. // Thus, we have to replicate the behavior here. for ln := range m { if !LabelNameRE.MatchString(string(ln)) { return fmt.Errorf("%q is not a valid label name", ln) } } *l = LabelSet(m) return nil } golang-prometheus-client-0.7.0+ds/model/labelvalue.go000066400000000000000000000020261256572052500226100ustar00rootroot00000000000000// Copyright 2013 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package model import ( "sort" ) // A LabelValue is an associated value for a LabelName. type LabelValue string // LabelValues is a sortable LabelValue slice. It implements sort.Interface. type LabelValues []LabelValue func (l LabelValues) Len() int { return len(l) } func (l LabelValues) Less(i, j int) bool { return sort.StringsAreSorted([]string{string(l[i]), string(l[j])}) } func (l LabelValues) Swap(i, j int) { l[i], l[j] = l[j], l[i] } golang-prometheus-client-0.7.0+ds/model/labelvalue_test.go000066400000000000000000000024141256572052500236500ustar00rootroot00000000000000// Copyright 2013 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package model import ( "sort" "testing" ) func testLabelValues(t testing.TB) { var scenarios = []struct { in LabelValues out LabelValues }{ { in: LabelValues{"ZZZ", "zzz"}, out: LabelValues{"ZZZ", "zzz"}, }, { in: LabelValues{"aaa", "AAA"}, out: LabelValues{"AAA", "aaa"}, }, } for i, scenario := range scenarios { sort.Sort(scenario.in) for j, expected := range scenario.out { if expected != scenario.in[j] { t.Errorf("%d.%d expected %s, got %s", i, j, expected, scenario.in[j]) } } } } func TestLabelValues(t *testing.T) { testLabelValues(t) } func BenchmarkLabelValues(b *testing.B) { for i := 0; i < b.N; i++ { testLabelValues(b) } } golang-prometheus-client-0.7.0+ds/model/metric.go000066400000000000000000000107761256572052500217720ustar00rootroot00000000000000// Copyright 2013 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package model import ( "encoding/json" "fmt" "sort" "strings" ) var separator = []byte{0} // A Metric is similar to a LabelSet, but the key difference is that a Metric is // a singleton and refers to one and only one stream of samples. type Metric map[LabelName]LabelValue // Equal compares the metrics. func (m Metric) Equal(o Metric) bool { if len(m) != len(o) { return false } for ln, lv := range m { olv, ok := o[ln] if !ok { return false } if olv != lv { return false } } return true } // Before compares the metrics, using the following criteria: // // If m has fewer labels than o, it is before o. If it has more, it is not. // // If the number of labels is the same, the superset of all label names is // sorted alphanumerically. The first differing label pair found in that order // determines the outcome: If the label does not exist at all in m, then m is // before o, and vice versa. Otherwise the label value is compared // alphanumerically. // // If m and o are equal, the method returns false. func (m Metric) Before(o Metric) bool { if len(m) < len(o) { return true } if len(m) > len(o) { return false } lns := make(LabelNames, 0, len(m)+len(o)) for ln := range m { lns = append(lns, ln) } for ln := range o { lns = append(lns, ln) } // It's probably not worth it to de-dup lns. sort.Sort(lns) for _, ln := range lns { mlv, ok := m[ln] if !ok { return true } olv, ok := o[ln] if !ok { return false } if mlv < olv { return true } if mlv > olv { return false } } return false } // String implements Stringer. func (m Metric) String() string { metricName, hasName := m[MetricNameLabel] numLabels := len(m) - 1 if !hasName { numLabels = len(m) } labelStrings := make([]string, 0, numLabels) for label, value := range m { if label != MetricNameLabel { labelStrings = append(labelStrings, fmt.Sprintf("%s=%q", label, value)) } } switch numLabels { case 0: if hasName { return string(metricName) } return "{}" default: sort.Strings(labelStrings) return fmt.Sprintf("%s{%s}", metricName, strings.Join(labelStrings, ", ")) } } // Fingerprint returns a Metric's Fingerprint. func (m Metric) Fingerprint() Fingerprint { return metricToFingerprint(m) } // FastFingerprint returns a Metric's Fingerprint calculated by a faster hashing // algorithm, which is, however, more susceptible to hash collisions. func (m Metric) FastFingerprint() Fingerprint { return metricToFastFingerprint(m) } // Clone returns a copy of the Metric. func (m Metric) Clone() Metric { clone := Metric{} for k, v := range m { clone[k] = v } return clone } // MergeFromLabelSet merges a label set into this Metric, prefixing a collision // prefix to the label names merged from the label set where required. func (m Metric) MergeFromLabelSet(labels LabelSet, collisionPrefix LabelName) { for k, v := range labels { if collisionPrefix != "" { for { if _, exists := m[k]; !exists { break } k = collisionPrefix + k } } m[k] = v } } // COWMetric wraps a Metric to enable copy-on-write access patterns. type COWMetric struct { Copied bool Metric Metric } // Set sets a label name in the wrapped Metric to a given value and copies the // Metric initially, if it is not already a copy. func (m *COWMetric) Set(ln LabelName, lv LabelValue) { m.doCOW() m.Metric[ln] = lv } // Delete deletes a given label name from the wrapped Metric and copies the // Metric initially, if it is not already a copy. func (m *COWMetric) Delete(ln LabelName) { m.doCOW() delete(m.Metric, ln) } // doCOW copies the underlying Metric if it is not already a copy. func (m *COWMetric) doCOW() { if !m.Copied { m.Metric = m.Metric.Clone() m.Copied = true } } // String implements fmt.Stringer. func (m COWMetric) String() string { return m.Metric.String() } // MarshalJSON implements json.Marshaler. func (m COWMetric) MarshalJSON() ([]byte, error) { return json.Marshal(m.Metric) } golang-prometheus-client-0.7.0+ds/model/metric_test.go000066400000000000000000000056541256572052500230300ustar00rootroot00000000000000// Copyright 2013 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package model import "testing" func testMetric(t testing.TB) { var scenarios = []struct { input Metric fingerprint Fingerprint fastFingerprint Fingerprint }{ { input: Metric{}, fingerprint: 14695981039346656037, fastFingerprint: 14695981039346656037, }, { input: Metric{ "first_name": "electro", "occupation": "robot", "manufacturer": "westinghouse", }, fingerprint: 5911716720268894962, fastFingerprint: 11310079640881077873, }, { input: Metric{ "x": "y", }, fingerprint: 8241431561484471700, fastFingerprint: 13948396922932177635, }, { input: Metric{ "a": "bb", "b": "c", }, fingerprint: 3016285359649981711, fastFingerprint: 3198632812309449502, }, { input: Metric{ "a": "b", "bb": "c", }, fingerprint: 7122421792099404749, fastFingerprint: 5774953389407657638, }, } for i, scenario := range scenarios { if scenario.fingerprint != scenario.input.Fingerprint() { t.Errorf("%d. expected %d, got %d", i, scenario.fingerprint, scenario.input.Fingerprint()) } if scenario.fastFingerprint != scenario.input.FastFingerprint() { t.Errorf("%d. expected %d, got %d", i, scenario.fastFingerprint, scenario.input.FastFingerprint()) } } } func TestMetric(t *testing.T) { testMetric(t) } func BenchmarkMetric(b *testing.B) { for i := 0; i < b.N; i++ { testMetric(b) } } func TestCOWMetric(t *testing.T) { testMetric := Metric{ "to_delete": "test1", "to_change": "test2", } scenarios := []struct { fn func(*COWMetric) out Metric }{ { fn: func(cm *COWMetric) { cm.Delete("to_delete") }, out: Metric{ "to_change": "test2", }, }, { fn: func(cm *COWMetric) { cm.Set("to_change", "changed") }, out: Metric{ "to_delete": "test1", "to_change": "changed", }, }, } for i, s := range scenarios { orig := testMetric.Clone() cm := &COWMetric{ Metric: orig, } s.fn(cm) // Test that the original metric was not modified. if !orig.Equal(testMetric) { t.Fatalf("%d. original metric changed; expected %v, got %v", i, testMetric, orig) } // Test that the new metric has the right changes. if !cm.Metric.Equal(s.out) { t.Fatalf("%d. copied metric doesn't contain expected changes; expected %v, got %v", i, s.out, cm.Metric) } } } golang-prometheus-client-0.7.0+ds/model/model.go000066400000000000000000000012561256572052500216000ustar00rootroot00000000000000// Copyright 2013 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package model contains core representation of Prometheus client primitives. package model golang-prometheus-client-0.7.0+ds/model/sample.go000066400000000000000000000034371256572052500217640ustar00rootroot00000000000000// Copyright 2013 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package model // Sample is a sample value with a timestamp and a metric. type Sample struct { Metric Metric Value SampleValue Timestamp Timestamp } // Equal compares first the metrics, then the timestamp, then the value. func (s *Sample) Equal(o *Sample) bool { if s == o { return true } if !s.Metric.Equal(o.Metric) { return false } if !s.Timestamp.Equal(o.Timestamp) { return false } if !s.Value.Equal(o.Value) { return false } return true } // Samples is a sortable Sample slice. It implements sort.Interface. type Samples []*Sample func (s Samples) Len() int { return len(s) } // Less compares first the metrics, then the timestamp. func (s Samples) Less(i, j int) bool { switch { case s[i].Metric.Before(s[j].Metric): return true case s[j].Metric.Before(s[i].Metric): return false case s[i].Timestamp.Before(s[j].Timestamp): return true default: return false } } func (s Samples) Swap(i, j int) { s[i], s[j] = s[j], s[i] } // Equal compares two sets of samples and returns true if they are equal. func (s Samples) Equal(o Samples) bool { if len(s) != len(o) { return false } for i, sample := range s { if !sample.Equal(o[i]) { return false } } return true } golang-prometheus-client-0.7.0+ds/model/sample_test.go000066400000000000000000000041771256572052500230250ustar00rootroot00000000000000// Copyright 2013 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package model import ( "sort" "testing" ) func TestSamplesSort(t *testing.T) { input := Samples{ &Sample{ Metric: Metric{ MetricNameLabel: "A", }, Timestamp: 1, }, &Sample{ Metric: Metric{ MetricNameLabel: "A", }, Timestamp: 2, }, &Sample{ Metric: Metric{ MetricNameLabel: "C", }, Timestamp: 1, }, &Sample{ Metric: Metric{ MetricNameLabel: "C", }, Timestamp: 2, }, &Sample{ Metric: Metric{ MetricNameLabel: "B", }, Timestamp: 1, }, &Sample{ Metric: Metric{ MetricNameLabel: "B", }, Timestamp: 2, }, } expected := Samples{ &Sample{ Metric: Metric{ MetricNameLabel: "A", }, Timestamp: 1, }, &Sample{ Metric: Metric{ MetricNameLabel: "A", }, Timestamp: 2, }, &Sample{ Metric: Metric{ MetricNameLabel: "B", }, Timestamp: 1, }, &Sample{ Metric: Metric{ MetricNameLabel: "B", }, Timestamp: 2, }, &Sample{ Metric: Metric{ MetricNameLabel: "C", }, Timestamp: 1, }, &Sample{ Metric: Metric{ MetricNameLabel: "C", }, Timestamp: 2, }, } sort.Sort(input) for i, actual := range input { actualFp := actual.Metric.Fingerprint() expectedFp := expected[i].Metric.Fingerprint() if !actualFp.Equal(expectedFp) { t.Fatalf("%d. Incorrect fingerprint. Got %s; want %s", i, actualFp.String(), expectedFp.String()) } if actual.Timestamp != expected[i].Timestamp { t.Fatalf("%d. Incorrect timestamp. Got %s; want %s", i, actual.Timestamp, expected[i].Timestamp) } } } golang-prometheus-client-0.7.0+ds/model/samplevalue.go000066400000000000000000000020751256572052500230160ustar00rootroot00000000000000// Copyright 2013 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package model import ( "fmt" "strconv" ) // A SampleValue is a representation of a value for a given sample at a given // time. type SampleValue float64 // Equal does a straight v==o. func (v SampleValue) Equal(o SampleValue) bool { return v == o } // MarshalJSON implements json.Marshaler. func (v SampleValue) MarshalJSON() ([]byte, error) { return []byte(fmt.Sprintf(`"%s"`, v)), nil } func (v SampleValue) String() string { return strconv.FormatFloat(float64(v), 'f', -1, 64) } golang-prometheus-client-0.7.0+ds/model/signature.go000066400000000000000000000120011256572052500224670ustar00rootroot00000000000000// Copyright 2014 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package model import ( "bytes" "hash" "hash/fnv" "sort" "sync" ) // SeparatorByte is a byte that cannot occur in valid UTF-8 sequences and is // used to separate label names, label values, and other strings from each other // when calculating their combined hash value (aka signature aka fingerprint). const SeparatorByte byte = 255 var ( // cache the signature of an empty label set. emptyLabelSignature = fnv.New64a().Sum64() hashAndBufPool sync.Pool ) type hashAndBuf struct { h hash.Hash64 b bytes.Buffer } func getHashAndBuf() *hashAndBuf { hb := hashAndBufPool.Get() if hb == nil { return &hashAndBuf{h: fnv.New64a()} } return hb.(*hashAndBuf) } func putHashAndBuf(hb *hashAndBuf) { hb.h.Reset() hb.b.Reset() hashAndBufPool.Put(hb) } // LabelsToSignature returns a quasi-unique signature (i.e., fingerprint) for a // given label set. (Collisions are possible but unlikely if the number of label // sets the function is applied to is small.) func LabelsToSignature(labels map[string]string) uint64 { if len(labels) == 0 { return emptyLabelSignature } labelNames := make([]string, 0, len(labels)) for labelName := range labels { labelNames = append(labelNames, labelName) } sort.Strings(labelNames) hb := getHashAndBuf() defer putHashAndBuf(hb) for _, labelName := range labelNames { hb.b.WriteString(labelName) hb.b.WriteByte(SeparatorByte) hb.b.WriteString(labels[labelName]) hb.b.WriteByte(SeparatorByte) hb.h.Write(hb.b.Bytes()) hb.b.Reset() } return hb.h.Sum64() } // metricToFingerprint works exactly as LabelsToSignature but takes a Metric as // parameter (rather than a label map) and returns a Fingerprint. func metricToFingerprint(m Metric) Fingerprint { if len(m) == 0 { return Fingerprint(emptyLabelSignature) } labelNames := make(LabelNames, 0, len(m)) for labelName := range m { labelNames = append(labelNames, labelName) } sort.Sort(labelNames) hb := getHashAndBuf() defer putHashAndBuf(hb) for _, labelName := range labelNames { hb.b.WriteString(string(labelName)) hb.b.WriteByte(SeparatorByte) hb.b.WriteString(string(m[labelName])) hb.b.WriteByte(SeparatorByte) hb.h.Write(hb.b.Bytes()) hb.b.Reset() } return Fingerprint(hb.h.Sum64()) } // metricToFastFingerprint works similar to metricToFingerprint but uses a // faster and less allocation-heavy hash function, which is more susceptible to // create hash collisions. Therefore, collision detection should be applied. func metricToFastFingerprint(m Metric) Fingerprint { if len(m) == 0 { return Fingerprint(emptyLabelSignature) } var result uint64 hb := getHashAndBuf() defer putHashAndBuf(hb) for labelName, labelValue := range m { hb.b.WriteString(string(labelName)) hb.b.WriteByte(SeparatorByte) hb.b.WriteString(string(labelValue)) hb.h.Write(hb.b.Bytes()) result ^= hb.h.Sum64() hb.h.Reset() hb.b.Reset() } return Fingerprint(result) } // SignatureForLabels works like LabelsToSignature but takes a Metric as // parameter (rather than a label map) and only includes the labels with the // specified LabelNames into the signature calculation. The labels passed in // will be sorted by this function. func SignatureForLabels(m Metric, labels LabelNames) uint64 { if len(m) == 0 || len(labels) == 0 { return emptyLabelSignature } sort.Sort(labels) hb := getHashAndBuf() defer putHashAndBuf(hb) for _, label := range labels { hb.b.WriteString(string(label)) hb.b.WriteByte(SeparatorByte) hb.b.WriteString(string(m[label])) hb.b.WriteByte(SeparatorByte) hb.h.Write(hb.b.Bytes()) hb.b.Reset() } return hb.h.Sum64() } // SignatureWithoutLabels works like LabelsToSignature but takes a Metric as // parameter (rather than a label map) and excludes the labels with any of the // specified LabelNames from the signature calculation. func SignatureWithoutLabels(m Metric, labels map[LabelName]struct{}) uint64 { if len(m) == 0 { return emptyLabelSignature } labelNames := make(LabelNames, 0, len(m)) for labelName := range m { if _, exclude := labels[labelName]; !exclude { labelNames = append(labelNames, labelName) } } if len(labelNames) == 0 { return emptyLabelSignature } sort.Sort(labelNames) hb := getHashAndBuf() defer putHashAndBuf(hb) for _, labelName := range labelNames { hb.b.WriteString(string(labelName)) hb.b.WriteByte(SeparatorByte) hb.b.WriteString(string(m[labelName])) hb.b.WriteByte(SeparatorByte) hb.h.Write(hb.b.Bytes()) hb.b.Reset() } return hb.h.Sum64() } golang-prometheus-client-0.7.0+ds/model/signature_test.go000066400000000000000000000212521256572052500235360ustar00rootroot00000000000000// Copyright 2014 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package model import ( "runtime" "sync" "testing" ) func TestLabelsToSignature(t *testing.T) { var scenarios = []struct { in map[string]string out uint64 }{ { in: map[string]string{}, out: 14695981039346656037, }, { in: map[string]string{"name": "garland, briggs", "fear": "love is not enough"}, out: 5799056148416392346, }, } for i, scenario := range scenarios { actual := LabelsToSignature(scenario.in) if actual != scenario.out { t.Errorf("%d. expected %d, got %d", i, scenario.out, actual) } } } func TestMetricToFingerprint(t *testing.T) { var scenarios = []struct { in Metric out Fingerprint }{ { in: Metric{}, out: 14695981039346656037, }, { in: Metric{"name": "garland, briggs", "fear": "love is not enough"}, out: 5799056148416392346, }, } for i, scenario := range scenarios { actual := metricToFingerprint(scenario.in) if actual != scenario.out { t.Errorf("%d. expected %d, got %d", i, scenario.out, actual) } } } func TestMetricToFastFingerprint(t *testing.T) { var scenarios = []struct { in Metric out Fingerprint }{ { in: Metric{}, out: 14695981039346656037, }, { in: Metric{"name": "garland, briggs", "fear": "love is not enough"}, out: 12952432476264840823, }, } for i, scenario := range scenarios { actual := metricToFastFingerprint(scenario.in) if actual != scenario.out { t.Errorf("%d. expected %d, got %d", i, scenario.out, actual) } } } func TestSignatureForLabels(t *testing.T) { var scenarios = []struct { in Metric labels LabelNames out uint64 }{ { in: Metric{}, labels: nil, out: 14695981039346656037, }, { in: Metric{"name": "garland, briggs", "fear": "love is not enough"}, labels: LabelNames{"fear", "name"}, out: 5799056148416392346, }, { in: Metric{"name": "garland, briggs", "fear": "love is not enough", "foo": "bar"}, labels: LabelNames{"fear", "name"}, out: 5799056148416392346, }, { in: Metric{"name": "garland, briggs", "fear": "love is not enough"}, labels: LabelNames{}, out: 14695981039346656037, }, { in: Metric{"name": "garland, briggs", "fear": "love is not enough"}, labels: nil, out: 14695981039346656037, }, } for i, scenario := range scenarios { actual := SignatureForLabels(scenario.in, scenario.labels) if actual != scenario.out { t.Errorf("%d. expected %d, got %d", i, scenario.out, actual) } } } func TestSignatureWithoutLabels(t *testing.T) { var scenarios = []struct { in Metric labels map[LabelName]struct{} out uint64 }{ { in: Metric{}, labels: nil, out: 14695981039346656037, }, { in: Metric{"name": "garland, briggs", "fear": "love is not enough"}, labels: map[LabelName]struct{}{"fear": struct{}{}, "name": struct{}{}}, out: 14695981039346656037, }, { in: Metric{"name": "garland, briggs", "fear": "love is not enough", "foo": "bar"}, labels: map[LabelName]struct{}{"foo": struct{}{}}, out: 5799056148416392346, }, { in: Metric{"name": "garland, briggs", "fear": "love is not enough"}, labels: map[LabelName]struct{}{}, out: 5799056148416392346, }, { in: Metric{"name": "garland, briggs", "fear": "love is not enough"}, labels: nil, out: 5799056148416392346, }, } for i, scenario := range scenarios { actual := SignatureWithoutLabels(scenario.in, scenario.labels) if actual != scenario.out { t.Errorf("%d. expected %d, got %d", i, scenario.out, actual) } } } func benchmarkLabelToSignature(b *testing.B, l map[string]string, e uint64) { for i := 0; i < b.N; i++ { if a := LabelsToSignature(l); a != e { b.Fatalf("expected signature of %d for %s, got %d", e, l, a) } } } func BenchmarkLabelToSignatureScalar(b *testing.B) { benchmarkLabelToSignature(b, nil, 14695981039346656037) } func BenchmarkLabelToSignatureSingle(b *testing.B) { benchmarkLabelToSignature(b, map[string]string{"first-label": "first-label-value"}, 5146282821936882169) } func BenchmarkLabelToSignatureDouble(b *testing.B) { benchmarkLabelToSignature(b, map[string]string{"first-label": "first-label-value", "second-label": "second-label-value"}, 3195800080984914717) } func BenchmarkLabelToSignatureTriple(b *testing.B) { benchmarkLabelToSignature(b, map[string]string{"first-label": "first-label-value", "second-label": "second-label-value", "third-label": "third-label-value"}, 13843036195897128121) } func benchmarkMetricToFingerprint(b *testing.B, m Metric, e Fingerprint) { for i := 0; i < b.N; i++ { if a := metricToFingerprint(m); a != e { b.Fatalf("expected signature of %d for %s, got %d", e, m, a) } } } func BenchmarkMetricToFingerprintScalar(b *testing.B) { benchmarkMetricToFingerprint(b, nil, 14695981039346656037) } func BenchmarkMetricToFingerprintSingle(b *testing.B) { benchmarkMetricToFingerprint(b, Metric{"first-label": "first-label-value"}, 5146282821936882169) } func BenchmarkMetricToFingerprintDouble(b *testing.B) { benchmarkMetricToFingerprint(b, Metric{"first-label": "first-label-value", "second-label": "second-label-value"}, 3195800080984914717) } func BenchmarkMetricToFingerprintTriple(b *testing.B) { benchmarkMetricToFingerprint(b, Metric{"first-label": "first-label-value", "second-label": "second-label-value", "third-label": "third-label-value"}, 13843036195897128121) } func benchmarkMetricToFastFingerprint(b *testing.B, m Metric, e Fingerprint) { for i := 0; i < b.N; i++ { if a := metricToFastFingerprint(m); a != e { b.Fatalf("expected signature of %d for %s, got %d", e, m, a) } } } func BenchmarkMetricToFastFingerprintScalar(b *testing.B) { benchmarkMetricToFastFingerprint(b, nil, 14695981039346656037) } func BenchmarkMetricToFastFingerprintSingle(b *testing.B) { benchmarkMetricToFastFingerprint(b, Metric{"first-label": "first-label-value"}, 5147259542624943964) } func BenchmarkMetricToFastFingerprintDouble(b *testing.B) { benchmarkMetricToFastFingerprint(b, Metric{"first-label": "first-label-value", "second-label": "second-label-value"}, 18269973311206963528) } func BenchmarkMetricToFastFingerprintTriple(b *testing.B) { benchmarkMetricToFastFingerprint(b, Metric{"first-label": "first-label-value", "second-label": "second-label-value", "third-label": "third-label-value"}, 15738406913934009676) } func TestEmptyLabelSignature(t *testing.T) { input := []map[string]string{nil, {}} var ms runtime.MemStats runtime.ReadMemStats(&ms) alloc := ms.Alloc for _, labels := range input { LabelsToSignature(labels) } runtime.ReadMemStats(&ms) if got := ms.Alloc; alloc != got { t.Fatal("expected LabelsToSignature with empty labels not to perform allocations") } } func benchmarkMetricToFastFingerprintConc(b *testing.B, m Metric, e Fingerprint, concLevel int) { var start, end sync.WaitGroup start.Add(1) end.Add(concLevel) for i := 0; i < concLevel; i++ { go func() { start.Wait() for j := b.N / concLevel; j >= 0; j-- { if a := metricToFastFingerprint(m); a != e { b.Fatalf("expected signature of %d for %s, got %d", e, m, a) } } end.Done() }() } b.ResetTimer() start.Done() end.Wait() } func BenchmarkMetricToFastFingerprintTripleConc1(b *testing.B) { benchmarkMetricToFastFingerprintConc(b, Metric{"first-label": "first-label-value", "second-label": "second-label-value", "third-label": "third-label-value"}, 15738406913934009676, 1) } func BenchmarkMetricToFastFingerprintTripleConc2(b *testing.B) { benchmarkMetricToFastFingerprintConc(b, Metric{"first-label": "first-label-value", "second-label": "second-label-value", "third-label": "third-label-value"}, 15738406913934009676, 2) } func BenchmarkMetricToFastFingerprintTripleConc4(b *testing.B) { benchmarkMetricToFastFingerprintConc(b, Metric{"first-label": "first-label-value", "second-label": "second-label-value", "third-label": "third-label-value"}, 15738406913934009676, 4) } func BenchmarkMetricToFastFingerprintTripleConc8(b *testing.B) { benchmarkMetricToFastFingerprintConc(b, Metric{"first-label": "first-label-value", "second-label": "second-label-value", "third-label": "third-label-value"}, 15738406913934009676, 8) } golang-prometheus-client-0.7.0+ds/model/timestamp.go000066400000000000000000000070201256572052500224760ustar00rootroot00000000000000// Copyright 2013 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package model import ( "math" "strconv" native_time "time" ) // Timestamp is the number of milliseconds since the epoch // (1970-01-01 00:00 UTC) excluding leap seconds. type Timestamp int64 const ( // MinimumTick is the minimum supported time resolution. This has to be // at least native_time.Second in order for the code below to work. MinimumTick = native_time.Millisecond // second is the timestamp duration equivalent to one second. second = int64(native_time.Second / MinimumTick) // The number of nanoseconds per minimum tick. nanosPerTick = int64(MinimumTick / native_time.Nanosecond) // Earliest is the earliest timestamp representable. Handy for // initializing a high watermark. Earliest = Timestamp(math.MinInt64) // Latest is the latest timestamp representable. Handy for initializing // a low watermark. Latest = Timestamp(math.MaxInt64) ) // Equal reports whether two timestamps represent the same instant. func (t Timestamp) Equal(o Timestamp) bool { return t == o } // Before reports whether the timestamp t is before o. func (t Timestamp) Before(o Timestamp) bool { return t < o } // After reports whether the timestamp t is after o. func (t Timestamp) After(o Timestamp) bool { return t > o } // Add returns the Timestamp t + d. func (t Timestamp) Add(d native_time.Duration) Timestamp { return t + Timestamp(d/MinimumTick) } // Sub returns the Duration t - o. func (t Timestamp) Sub(o Timestamp) native_time.Duration { return native_time.Duration(t-o) * MinimumTick } // Time returns the time.Time representation of t. func (t Timestamp) Time() native_time.Time { return native_time.Unix(int64(t)/second, (int64(t)%second)*nanosPerTick) } // Unix returns t as a Unix time, the number of seconds elapsed // since January 1, 1970 UTC. func (t Timestamp) Unix() int64 { return int64(t) / second } // UnixNano returns t as a Unix time, the number of nanoseconds elapsed // since January 1, 1970 UTC. func (t Timestamp) UnixNano() int64 { return int64(t) * nanosPerTick } // String returns a string representation of the timestamp. func (t Timestamp) String() string { return strconv.FormatFloat(float64(t)/float64(second), 'f', -1, 64) } // MarshalJSON implements the json.Marshaler interface. func (t Timestamp) MarshalJSON() ([]byte, error) { return []byte(t.String()), nil } // Now returns the current time as a Timestamp. func Now() Timestamp { return TimestampFromTime(native_time.Now()) } // TimestampFromTime returns the Timestamp equivalent to the time.Time t. func TimestampFromTime(t native_time.Time) Timestamp { return TimestampFromUnixNano(t.UnixNano()) } // TimestampFromUnix returns the Timestamp equivalent to the Unix timestamp t // provided in seconds. func TimestampFromUnix(t int64) Timestamp { return Timestamp(t * second) } // TimestampFromUnixNano returns the Timestamp equivalent to the Unix timestamp // t provided in nanoseconds. func TimestampFromUnixNano(t int64) Timestamp { return Timestamp(t / nanosPerTick) } golang-prometheus-client-0.7.0+ds/model/timestamp_test.go000066400000000000000000000045141256572052500235420ustar00rootroot00000000000000// Copyright 2013 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package model import ( "testing" native_time "time" ) func TestComparators(t *testing.T) { t1a := TimestampFromUnix(0) t1b := TimestampFromUnix(0) t2 := TimestampFromUnix(2*second - 1) if !t1a.Equal(t1b) { t.Fatalf("Expected %s to be equal to %s", t1a, t1b) } if t1a.Equal(t2) { t.Fatalf("Expected %s to not be equal to %s", t1a, t2) } if !t1a.Before(t2) { t.Fatalf("Expected %s to be before %s", t1a, t2) } if t1a.Before(t1b) { t.Fatalf("Expected %s to not be before %s", t1a, t1b) } if !t2.After(t1a) { t.Fatalf("Expected %s to be after %s", t2, t1a) } if t1b.After(t1a) { t.Fatalf("Expected %s to not be after %s", t1b, t1a) } } func TestTimestampConversions(t *testing.T) { unixSecs := int64(1136239445) unixNsecs := int64(123456789) unixNano := unixSecs*1000000000 + unixNsecs t1 := native_time.Unix(unixSecs, unixNsecs-unixNsecs%nanosPerTick) t2 := native_time.Unix(unixSecs, unixNsecs) ts := TimestampFromUnixNano(unixNano) if !ts.Time().Equal(t1) { t.Fatalf("Expected %s, got %s", t1, ts.Time()) } // Test available precision. ts = TimestampFromTime(t2) if !ts.Time().Equal(t1) { t.Fatalf("Expected %s, got %s", t1, ts.Time()) } if ts.UnixNano() != unixNano-unixNano%nanosPerTick { t.Fatalf("Expected %d, got %d", unixNano, ts.UnixNano()) } } func TestDuration(t *testing.T) { duration := native_time.Second + native_time.Minute + native_time.Hour goTime := native_time.Unix(1136239445, 0) ts := TimestampFromTime(goTime) if !goTime.Add(duration).Equal(ts.Add(duration).Time()) { t.Fatalf("Expected %s to be equal to %s", goTime.Add(duration), ts.Add(duration)) } earlier := ts.Add(-duration) delta := ts.Sub(earlier) if delta != duration { t.Fatalf("Expected %s to be equal to %s", delta, duration) } } golang-prometheus-client-0.7.0+ds/prometheus/000077500000000000000000000000001256572052500212405ustar00rootroot00000000000000golang-prometheus-client-0.7.0+ds/prometheus/.gitignore000066400000000000000000000000341256572052500232250ustar00rootroot00000000000000command-line-arguments.test golang-prometheus-client-0.7.0+ds/prometheus/README.md000066400000000000000000000024201256572052500225150ustar00rootroot00000000000000# Overview This is the [Prometheus](http://www.prometheus.io) telemetric instrumentation client [Go](http://golang.org) client library. It enable authors to define process-space metrics for their servers and expose them through a web service interface for extraction, aggregation, and a whole slew of other post processing techniques. # Installing $ go get github.com/prometheus/client_golang/prometheus # Example ```go package main import ( "net/http" "github.com/prometheus/client_golang/prometheus" ) var ( indexed = prometheus.NewCounter(prometheus.CounterOpts{ Namespace: "my_company", Subsystem: "indexer", Name: "documents_indexed", Help: "The number of documents indexed.", }) size = prometheus.NewGauge(prometheus.GaugeOpts{ Namespace: "my_company", Subsystem: "storage", Name: "documents_total_size_bytes", Help: "The total size of all documents in the storage.", }) ) func main() { http.Handle("/metrics", prometheus.Handler()) indexed.Inc() size.Set(5) http.ListenAndServe(":8080", nil) } func init() { prometheus.MustRegister(indexed) prometheus.MustRegister(size) } ``` # Documentation [![GoDoc](https://godoc.org/github.com/prometheus/client_golang?status.png)](https://godoc.org/github.com/prometheus/client_golang) golang-prometheus-client-0.7.0+ds/prometheus/benchmark_test.go000066400000000000000000000067671256572052500246000ustar00rootroot00000000000000// Copyright 2014 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package prometheus import ( "testing" ) func BenchmarkCounterWithLabelValues(b *testing.B) { m := NewCounterVec( CounterOpts{ Name: "benchmark_counter", Help: "A counter to benchmark it.", }, []string{"one", "two", "three"}, ) b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { m.WithLabelValues("eins", "zwei", "drei").Inc() } } func BenchmarkCounterWithMappedLabels(b *testing.B) { m := NewCounterVec( CounterOpts{ Name: "benchmark_counter", Help: "A counter to benchmark it.", }, []string{"one", "two", "three"}, ) b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { m.With(Labels{"two": "zwei", "one": "eins", "three": "drei"}).Inc() } } func BenchmarkCounterWithPreparedMappedLabels(b *testing.B) { m := NewCounterVec( CounterOpts{ Name: "benchmark_counter", Help: "A counter to benchmark it.", }, []string{"one", "two", "three"}, ) b.ReportAllocs() b.ResetTimer() labels := Labels{"two": "zwei", "one": "eins", "three": "drei"} for i := 0; i < b.N; i++ { m.With(labels).Inc() } } func BenchmarkCounterNoLabels(b *testing.B) { m := NewCounter(CounterOpts{ Name: "benchmark_counter", Help: "A counter to benchmark it.", }) b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { m.Inc() } } func BenchmarkGaugeWithLabelValues(b *testing.B) { m := NewGaugeVec( GaugeOpts{ Name: "benchmark_gauge", Help: "A gauge to benchmark it.", }, []string{"one", "two", "three"}, ) b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { m.WithLabelValues("eins", "zwei", "drei").Set(3.1415) } } func BenchmarkGaugeNoLabels(b *testing.B) { m := NewGauge(GaugeOpts{ Name: "benchmark_gauge", Help: "A gauge to benchmark it.", }) b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { m.Set(3.1415) } } func BenchmarkSummaryWithLabelValues(b *testing.B) { m := NewSummaryVec( SummaryOpts{ Name: "benchmark_summary", Help: "A summary to benchmark it.", }, []string{"one", "two", "three"}, ) b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { m.WithLabelValues("eins", "zwei", "drei").Observe(3.1415) } } func BenchmarkSummaryNoLabels(b *testing.B) { m := NewSummary(SummaryOpts{ Name: "benchmark_summary", Help: "A summary to benchmark it.", }, ) b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { m.Observe(3.1415) } } func BenchmarkHistogramWithLabelValues(b *testing.B) { m := NewHistogramVec( HistogramOpts{ Name: "benchmark_histogram", Help: "A histogram to benchmark it.", }, []string{"one", "two", "three"}, ) b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { m.WithLabelValues("eins", "zwei", "drei").Observe(3.1415) } } func BenchmarkHistogramNoLabels(b *testing.B) { m := NewHistogram(HistogramOpts{ Name: "benchmark_histogram", Help: "A histogram to benchmark it.", }, ) b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { m.Observe(3.1415) } } golang-prometheus-client-0.7.0+ds/prometheus/collector.go000066400000000000000000000067541256572052500235710ustar00rootroot00000000000000// Copyright 2014 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package prometheus // Collector is the interface implemented by anything that can be used by // Prometheus to collect metrics. A Collector has to be registered for // collection. See Register, MustRegister, RegisterOrGet, and MustRegisterOrGet. // // The stock metrics provided by this package (like Gauge, Counter, Summary) are // also Collectors (which only ever collect one metric, namely itself). An // implementer of Collector may, however, collect multiple metrics in a // coordinated fashion and/or create metrics on the fly. Examples for collectors // already implemented in this library are the metric vectors (i.e. collection // of multiple instances of the same Metric but with different label values) // like GaugeVec or SummaryVec, and the ExpvarCollector. type Collector interface { // Describe sends the super-set of all possible descriptors of metrics // collected by this Collector to the provided channel and returns once // the last descriptor has been sent. The sent descriptors fulfill the // consistency and uniqueness requirements described in the Desc // documentation. (It is valid if one and the same Collector sends // duplicate descriptors. Those duplicates are simply ignored. However, // two different Collectors must not send duplicate descriptors.) This // method idempotently sends the same descriptors throughout the // lifetime of the Collector. If a Collector encounters an error while // executing this method, it must send an invalid descriptor (created // with NewInvalidDesc) to signal the error to the registry. Describe(chan<- *Desc) // Collect is called by Prometheus when collecting metrics. The // implementation sends each collected metric via the provided channel // and returns once the last metric has been sent. The descriptor of // each sent metric is one of those returned by Describe. Returned // metrics that share the same descriptor must differ in their variable // label values. This method may be called concurrently and must // therefore be implemented in a concurrency safe way. Blocking occurs // at the expense of total performance of rendering all registered // metrics. Ideally, Collector implementations support concurrent // readers. Collect(chan<- Metric) } // SelfCollector implements Collector for a single Metric so that that the // Metric collects itself. Add it as an anonymous field to a struct that // implements Metric, and call Init with the Metric itself as an argument. type SelfCollector struct { self Metric } // Init provides the SelfCollector with a reference to the metric it is supposed // to collect. It is usually called within the factory function to create a // metric. See example. func (c *SelfCollector) Init(self Metric) { c.self = self } // Describe implements Collector. func (c *SelfCollector) Describe(ch chan<- *Desc) { ch <- c.self.Desc() } // Collect implements Collector. func (c *SelfCollector) Collect(ch chan<- Metric) { ch <- c.self } golang-prometheus-client-0.7.0+ds/prometheus/counter.go000066400000000000000000000135061256572052500232530ustar00rootroot00000000000000// Copyright 2014 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package prometheus import ( "errors" "hash/fnv" ) // Counter is a Metric that represents a single numerical value that only ever // goes up. That implies that it cannot be used to count items whose number can // also go down, e.g. the number of currently running goroutines. Those // "counters" are represented by Gauges. // // A Counter is typically used to count requests served, tasks completed, errors // occurred, etc. // // To create Counter instances, use NewCounter. type Counter interface { Metric Collector // Set is used to set the Counter to an arbitrary value. It is only used // if you have to transfer a value from an external counter into this // Prometheus metric. Do not use it for regular handling of a // Prometheus counter (as it can be used to break the contract of // monotonically increasing values). Set(float64) // Inc increments the counter by 1. Inc() // Add adds the given value to the counter. It panics if the value is < // 0. Add(float64) } // CounterOpts is an alias for Opts. See there for doc comments. type CounterOpts Opts // NewCounter creates a new Counter based on the provided CounterOpts. func NewCounter(opts CounterOpts) Counter { desc := NewDesc( BuildFQName(opts.Namespace, opts.Subsystem, opts.Name), opts.Help, nil, opts.ConstLabels, ) result := &counter{value: value{desc: desc, valType: CounterValue, labelPairs: desc.constLabelPairs}} result.Init(result) // Init self-collection. return result } type counter struct { value } func (c *counter) Add(v float64) { if v < 0 { panic(errors.New("counter cannot decrease in value")) } c.value.Add(v) } // CounterVec is a Collector that bundles a set of Counters that all share the // same Desc, but have different values for their variable labels. This is used // if you want to count the same thing partitioned by various dimensions // (e.g. number of HTTP requests, partitioned by response code and // method). Create instances with NewCounterVec. // // CounterVec embeds MetricVec. See there for a full list of methods with // detailed documentation. type CounterVec struct { MetricVec } // NewCounterVec creates a new CounterVec based on the provided CounterOpts and // partitioned by the given label names. At least one label name must be // provided. func NewCounterVec(opts CounterOpts, labelNames []string) *CounterVec { desc := NewDesc( BuildFQName(opts.Namespace, opts.Subsystem, opts.Name), opts.Help, labelNames, opts.ConstLabels, ) return &CounterVec{ MetricVec: MetricVec{ children: map[uint64]Metric{}, desc: desc, hash: fnv.New64a(), newMetric: func(lvs ...string) Metric { result := &counter{value: value{ desc: desc, valType: CounterValue, labelPairs: makeLabelPairs(desc, lvs), }} result.Init(result) // Init self-collection. return result }, }, } } // GetMetricWithLabelValues replaces the method of the same name in // MetricVec. The difference is that this method returns a Counter and not a // Metric so that no type conversion is required. func (m *CounterVec) GetMetricWithLabelValues(lvs ...string) (Counter, error) { metric, err := m.MetricVec.GetMetricWithLabelValues(lvs...) if metric != nil { return metric.(Counter), err } return nil, err } // GetMetricWith replaces the method of the same name in MetricVec. The // difference is that this method returns a Counter and not a Metric so that no // type conversion is required. func (m *CounterVec) GetMetricWith(labels Labels) (Counter, error) { metric, err := m.MetricVec.GetMetricWith(labels) if metric != nil { return metric.(Counter), err } return nil, err } // WithLabelValues works as GetMetricWithLabelValues, but panics where // GetMetricWithLabelValues would have returned an error. By not returning an // error, WithLabelValues allows shortcuts like // myVec.WithLabelValues("404", "GET").Add(42) func (m *CounterVec) WithLabelValues(lvs ...string) Counter { return m.MetricVec.WithLabelValues(lvs...).(Counter) } // With works as GetMetricWith, but panics where GetMetricWithLabels would have // returned an error. By not returning an error, With allows shortcuts like // myVec.With(Labels{"code": "404", "method": "GET"}).Add(42) func (m *CounterVec) With(labels Labels) Counter { return m.MetricVec.With(labels).(Counter) } // CounterFunc is a Counter whose value is determined at collect time by calling a // provided function. // // To create CounterFunc instances, use NewCounterFunc. type CounterFunc interface { Metric Collector } // NewCounterFunc creates a new CounterFunc based on the provided // CounterOpts. The value reported is determined by calling the given function // from within the Write method. Take into account that metric collection may // happen concurrently. If that results in concurrent calls to Write, like in // the case where a CounterFunc is directly registered with Prometheus, the // provided function must be concurrency-safe. The function should also honor // the contract for a Counter (values only go up, not down), but compliance will // not be checked. func NewCounterFunc(opts CounterOpts, function func() float64) CounterFunc { return newValueFunc(NewDesc( BuildFQName(opts.Namespace, opts.Subsystem, opts.Name), opts.Help, nil, opts.ConstLabels, ), CounterValue, function) } golang-prometheus-client-0.7.0+ds/prometheus/counter_test.go000066400000000000000000000032651256572052500243130ustar00rootroot00000000000000// Copyright 2014 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package prometheus import ( "math" "testing" dto "github.com/prometheus/client_model/go" ) func TestCounterAdd(t *testing.T) { counter := NewCounter(CounterOpts{ Name: "test", Help: "test help", ConstLabels: Labels{"a": "1", "b": "2"}, }).(*counter) counter.Inc() if expected, got := 1., math.Float64frombits(counter.valBits); expected != got { t.Errorf("Expected %f, got %f.", expected, got) } counter.Add(42) if expected, got := 43., math.Float64frombits(counter.valBits); expected != got { t.Errorf("Expected %f, got %f.", expected, got) } if expected, got := "counter cannot decrease in value", decreaseCounter(counter).Error(); expected != got { t.Errorf("Expected error %q, got %q.", expected, got) } m := &dto.Metric{} counter.Write(m) if expected, got := `label: label: counter: `, m.String(); expected != got { t.Errorf("expected %q, got %q", expected, got) } } func decreaseCounter(c *counter) (err error) { defer func() { if e := recover(); e != nil { err = e.(error) } }() c.Add(-1) return nil } golang-prometheus-client-0.7.0+ds/prometheus/desc.go000066400000000000000000000150131256572052500225050ustar00rootroot00000000000000package prometheus import ( "bytes" "errors" "fmt" "hash/fnv" "regexp" "sort" "strings" "github.com/golang/protobuf/proto" dto "github.com/prometheus/client_model/go" "github.com/prometheus/client_golang/model" ) var ( metricNameRE = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_:]*$`) ) // Labels represents a collection of label name -> value mappings. This type is // commonly used with the With(Labels) and GetMetricWith(Labels) methods of // metric vector Collectors, e.g.: // myVec.With(Labels{"code": "404", "method": "GET"}).Add(42) // // The other use-case is the specification of constant label pairs in Opts or to // create a Desc. type Labels map[string]string // Desc is the descriptor used by every Prometheus Metric. It is essentially // the immutable meta-data of a Metric. The normal Metric implementations // included in this package manage their Desc under the hood. Users only have to // deal with Desc if they use advanced features like the ExpvarCollector or // custom Collectors and Metrics. // // Descriptors registered with the same registry have to fulfill certain // consistency and uniqueness criteria if they share the same fully-qualified // name: They must have the same help string and the same label names (aka label // dimensions) in each, constLabels and variableLabels, but they must differ in // the values of the constLabels. // // Descriptors that share the same fully-qualified names and the same label // values of their constLabels are considered equal. // // Use NewDesc to create new Desc instances. type Desc struct { // fqName has been built from Namespace, Subsystem, and Name. fqName string // help provides some helpful information about this metric. help string // constLabelPairs contains precalculated DTO label pairs based on // the constant labels. constLabelPairs []*dto.LabelPair // VariableLabels contains names of labels for which the metric // maintains variable values. variableLabels []string // id is a hash of the values of the ConstLabels and fqName. This // must be unique among all registered descriptors and can therefore be // used as an identifier of the descriptor. id uint64 // dimHash is a hash of the label names (preset and variable) and the // Help string. Each Desc with the same fqName must have the same // dimHash. dimHash uint64 // err is an error that occured during construction. It is reported on // registration time. err error } // NewDesc allocates and initializes a new Desc. Errors are recorded in the Desc // and will be reported on registration time. variableLabels and constLabels can // be nil if no such labels should be set. fqName and help must not be empty. // // variableLabels only contain the label names. Their label values are variable // and therefore not part of the Desc. (They are managed within the Metric.) // // For constLabels, the label values are constant. Therefore, they are fully // specified in the Desc. See the Opts documentation for the implications of // constant labels. func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) *Desc { d := &Desc{ fqName: fqName, help: help, variableLabels: variableLabels, } if help == "" { d.err = errors.New("empty help string") return d } if !metricNameRE.MatchString(fqName) { d.err = fmt.Errorf("%q is not a valid metric name", fqName) return d } // labelValues contains the label values of const labels (in order of // their sorted label names) plus the fqName (at position 0). labelValues := make([]string, 1, len(constLabels)+1) labelValues[0] = fqName labelNames := make([]string, 0, len(constLabels)+len(variableLabels)) labelNameSet := map[string]struct{}{} // First add only the const label names and sort them... for labelName := range constLabels { if !checkLabelName(labelName) { d.err = fmt.Errorf("%q is not a valid label name", labelName) return d } labelNames = append(labelNames, labelName) labelNameSet[labelName] = struct{}{} } sort.Strings(labelNames) // ... so that we can now add const label values in the order of their names. for _, labelName := range labelNames { labelValues = append(labelValues, constLabels[labelName]) } // Now add the variable label names, but prefix them with something that // cannot be in a regular label name. That prevents matching the label // dimension with a different mix between preset and variable labels. for _, labelName := range variableLabels { if !checkLabelName(labelName) { d.err = fmt.Errorf("%q is not a valid label name", labelName) return d } labelNames = append(labelNames, "$"+labelName) labelNameSet[labelName] = struct{}{} } if len(labelNames) != len(labelNameSet) { d.err = errors.New("duplicate label names") return d } h := fnv.New64a() var b bytes.Buffer // To copy string contents into, avoiding []byte allocations. for _, val := range labelValues { b.Reset() b.WriteString(val) b.WriteByte(model.SeparatorByte) h.Write(b.Bytes()) } d.id = h.Sum64() // Sort labelNames so that order doesn't matter for the hash. sort.Strings(labelNames) // Now hash together (in this order) the help string and the sorted // label names. h.Reset() b.Reset() b.WriteString(help) b.WriteByte(model.SeparatorByte) h.Write(b.Bytes()) for _, labelName := range labelNames { b.Reset() b.WriteString(labelName) b.WriteByte(model.SeparatorByte) h.Write(b.Bytes()) } d.dimHash = h.Sum64() d.constLabelPairs = make([]*dto.LabelPair, 0, len(constLabels)) for n, v := range constLabels { d.constLabelPairs = append(d.constLabelPairs, &dto.LabelPair{ Name: proto.String(n), Value: proto.String(v), }) } sort.Sort(LabelPairSorter(d.constLabelPairs)) return d } // NewInvalidDesc returns an invalid descriptor, i.e. a descriptor with the // provided error set. If a collector returning such a descriptor is registered, // registration will fail with the provided error. NewInvalidDesc can be used by // a Collector to signal inability to describe itself. func NewInvalidDesc(err error) *Desc { return &Desc{ err: err, } } func (d *Desc) String() string { lpStrings := make([]string, 0, len(d.constLabelPairs)) for _, lp := range d.constLabelPairs { lpStrings = append( lpStrings, fmt.Sprintf("%s=%q", lp.GetName(), lp.GetValue()), ) } return fmt.Sprintf( "Desc{fqName: %q, help: %q, constLabels: {%s}, variableLabels: %v}", d.fqName, d.help, strings.Join(lpStrings, ","), d.variableLabels, ) } func checkLabelName(l string) bool { return model.LabelNameRE.MatchString(l) && !strings.HasPrefix(l, model.ReservedLabelPrefix) } golang-prometheus-client-0.7.0+ds/prometheus/doc.go000066400000000000000000000111171256572052500223350ustar00rootroot00000000000000// Copyright 2014 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package prometheus provides embeddable metric primitives for servers and // standardized exposition of telemetry through a web services interface. // // All exported functions and methods are safe to be used concurrently unless // specified otherwise. // // To expose metrics registered with the Prometheus registry, an HTTP server // needs to know about the Prometheus handler. The usual endpoint is "/metrics". // // http.Handle("/metrics", prometheus.Handler()) // // As a starting point a very basic usage example: // // package main // // import ( // "net/http" // // "github.com/prometheus/client_golang/prometheus" // ) // // var ( // cpuTemp = prometheus.NewGauge(prometheus.GaugeOpts{ // Name: "cpu_temperature_celsius", // Help: "Current temperature of the CPU.", // }) // hdFailures = prometheus.NewCounter(prometheus.CounterOpts{ // Name: "hd_errors_total", // Help: "Number of hard-disk errors.", // }) // ) // // func init() { // prometheus.MustRegister(cpuTemp) // prometheus.MustRegister(hdFailures) // } // // func main() { // cpuTemp.Set(65.3) // hdFailures.Inc() // // http.Handle("/metrics", prometheus.Handler()) // http.ListenAndServe(":8080", nil) // } // // // This is a complete program that exports two metrics, a Gauge and a Counter. // It also exports some stats about the HTTP usage of the /metrics // endpoint. (See the Handler function for more detail.) // // Two more advanced metric types are the Summary and Histogram. // // In addition to the fundamental metric types Gauge, Counter, Summary, and // Histogram, a very important part of the Prometheus data model is the // partitioning of samples along dimensions called labels, which results in // metric vectors. The fundamental types are GaugeVec, CounterVec, SummaryVec, // and HistogramVec. // // Those are all the parts needed for basic usage. Detailed documentation and // examples are provided below. // // Everything else this package offers is essentially for "power users" only. A // few pointers to "power user features": // // All the various ...Opts structs have a ConstLabels field for labels that // never change their value (which is only useful under special circumstances, // see documentation of the Opts type). // // The Untyped metric behaves like a Gauge, but signals the Prometheus server // not to assume anything about its type. // // Functions to fine-tune how the metric registry works: EnableCollectChecks, // PanicOnCollectError, Register, Unregister, SetMetricFamilyInjectionHook. // // For custom metric collection, there are two entry points: Custom Metric // implementations and custom Collector implementations. A Metric is the // fundamental unit in the Prometheus data model: a sample at a point in time // together with its meta-data (like its fully-qualified name and any number of // pairs of label name and label value) that knows how to marshal itself into a // data transfer object (aka DTO, implemented as a protocol buffer). A Collector // gets registered with the Prometheus registry and manages the collection of // one or more Metrics. Many parts of this package are building blocks for // Metrics and Collectors. Desc is the metric descriptor, actually used by all // metrics under the hood, and by Collectors to describe the Metrics to be // collected, but only to be dealt with by users if they implement their own // Metrics or Collectors. To create a Desc, the BuildFQName function will come // in handy. Other useful components for Metric and Collector implementation // include: LabelPairSorter to sort the DTO version of label pairs, // NewConstMetric and MustNewConstMetric to create "throw away" Metrics at // collection time, MetricVec to bundle custom Metrics into a metric vector // Collector, SelfCollector to make a custom Metric collect itself. // // A good example for a custom Collector is the ExpVarCollector included in this // package, which exports variables exported via the "expvar" package as // Prometheus metrics. package prometheus golang-prometheus-client-0.7.0+ds/prometheus/example_clustermanager_test.go000066400000000000000000000115221256572052500273560ustar00rootroot00000000000000// Copyright 2014 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package prometheus_test import ( "sync" "github.com/prometheus/client_golang/prometheus" ) // ClusterManager is an example for a system that might have been built without // Prometheus in mind. It models a central manager of jobs running in a // cluster. To turn it into something that collects Prometheus metrics, we // simply add the two methods required for the Collector interface. // // An additional challenge is that multiple instances of the ClusterManager are // run within the same binary, each in charge of a different zone. We need to // make use of ConstLabels to be able to register each ClusterManager instance // with Prometheus. type ClusterManager struct { Zone string OOMCount *prometheus.CounterVec RAMUsage *prometheus.GaugeVec mtx sync.Mutex // Protects OOMCount and RAMUsage. // ... many more fields } // ReallyExpensiveAssessmentOfTheSystemState is a mock for the data gathering a // real cluster manager would have to do. Since it may actually be really // expensive, it must only be called once per collection. This implementation, // obviously, only returns some made-up data. func (c *ClusterManager) ReallyExpensiveAssessmentOfTheSystemState() ( oomCountByHost map[string]int, ramUsageByHost map[string]float64, ) { // Just example fake data. oomCountByHost = map[string]int{ "foo.example.org": 42, "bar.example.org": 2001, } ramUsageByHost = map[string]float64{ "foo.example.org": 6.023e23, "bar.example.org": 3.14, } return } // Describe faces the interesting challenge that the two metric vectors that are // used in this example are already Collectors themselves. However, thanks to // the use of channels, it is really easy to "chain" Collectors. Here we simply // call the Describe methods of the two metric vectors. func (c *ClusterManager) Describe(ch chan<- *prometheus.Desc) { c.OOMCount.Describe(ch) c.RAMUsage.Describe(ch) } // Collect first triggers the ReallyExpensiveAssessmentOfTheSystemState. Then it // sets the retrieved values in the two metric vectors and then sends all their // metrics to the channel (again using a chaining technique as in the Describe // method). Since Collect could be called multiple times concurrently, that part // is protected by a mutex. func (c *ClusterManager) Collect(ch chan<- prometheus.Metric) { oomCountByHost, ramUsageByHost := c.ReallyExpensiveAssessmentOfTheSystemState() c.mtx.Lock() defer c.mtx.Unlock() for host, oomCount := range oomCountByHost { c.OOMCount.WithLabelValues(host).Set(float64(oomCount)) } for host, ramUsage := range ramUsageByHost { c.RAMUsage.WithLabelValues(host).Set(ramUsage) } c.OOMCount.Collect(ch) c.RAMUsage.Collect(ch) // All metrics in OOMCount and RAMUsage are sent to the channel now. We // can safely reset the two metric vectors now, so that we can start // fresh in the next Collect cycle. (Imagine a host disappears from the // cluster. If we did not reset here, its Metric would stay in the // metric vectors forever.) c.OOMCount.Reset() c.RAMUsage.Reset() } // NewClusterManager creates the two metric vectors OOMCount and RAMUsage. Note // that the zone is set as a ConstLabel. (It's different in each instance of the // ClusterManager, but constant over the lifetime of an instance.) The reported // values are partitioned by host, which is therefore a variable label. func NewClusterManager(zone string) *ClusterManager { return &ClusterManager{ Zone: zone, OOMCount: prometheus.NewCounterVec( prometheus.CounterOpts{ Subsystem: "clustermanager", Name: "oom_count", Help: "number of OOM crashes", ConstLabels: prometheus.Labels{"zone": zone}, }, []string{"host"}, ), RAMUsage: prometheus.NewGaugeVec( prometheus.GaugeOpts{ Subsystem: "clustermanager", Name: "ram_usage_bytes", Help: "RAM usage as reported to the cluster manager", ConstLabels: prometheus.Labels{"zone": zone}, }, []string{"host"}, ), } } func ExampleCollector_clustermanager() { workerDB := NewClusterManager("db") workerCA := NewClusterManager("ca") prometheus.MustRegister(workerDB) prometheus.MustRegister(workerCA) // Since we are dealing with custom Collector implementations, it might // be a good idea to enable the collect checks in the registry. prometheus.EnableCollectChecks(true) } golang-prometheus-client-0.7.0+ds/prometheus/example_memstats_test.go000066400000000000000000000054351256572052500262050ustar00rootroot00000000000000// Copyright 2014 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package prometheus_test import ( "runtime" "github.com/prometheus/client_golang/prometheus" ) var ( allocDesc = prometheus.NewDesc( prometheus.BuildFQName("", "memstats", "alloc_bytes"), "bytes allocated and still in use", nil, nil, ) totalAllocDesc = prometheus.NewDesc( prometheus.BuildFQName("", "memstats", "total_alloc_bytes"), "bytes allocated (even if freed)", nil, nil, ) numGCDesc = prometheus.NewDesc( prometheus.BuildFQName("", "memstats", "num_gc_total"), "number of GCs run", nil, nil, ) ) // MemStatsCollector is an example for a custom Collector that solves the // problem of feeding into multiple metrics at the same time. The // runtime.ReadMemStats should happen only once, and then the results need to be // fed into a number of separate Metrics. In this example, only a few of the // values reported by ReadMemStats are used. For each, there is a Desc provided // as a var, so the MemStatsCollector itself needs nothing else in the // struct. Only the methods need to be implemented. type MemStatsCollector struct{} // Describe just sends the three Desc objects for the Metrics we intend to // collect. func (_ MemStatsCollector) Describe(ch chan<- *prometheus.Desc) { ch <- allocDesc ch <- totalAllocDesc ch <- numGCDesc } // Collect does the trick by calling ReadMemStats once and then constructing // three different Metrics on the fly. func (_ MemStatsCollector) Collect(ch chan<- prometheus.Metric) { var ms runtime.MemStats runtime.ReadMemStats(&ms) ch <- prometheus.MustNewConstMetric( allocDesc, prometheus.GaugeValue, float64(ms.Alloc), ) ch <- prometheus.MustNewConstMetric( totalAllocDesc, prometheus.GaugeValue, float64(ms.TotalAlloc), ) ch <- prometheus.MustNewConstMetric( numGCDesc, prometheus.CounterValue, float64(ms.NumGC), ) // To avoid new allocations on each collection, you could also keep // metric objects around and return the same objects each time, just // with new values set. } func ExampleCollector_memstats() { prometheus.MustRegister(&MemStatsCollector{}) // Since we are dealing with custom Collector implementations, it might // be a good idea to enable the collect checks in the registry. prometheus.EnableCollectChecks(true) } golang-prometheus-client-0.7.0+ds/prometheus/example_selfcollector_test.go000066400000000000000000000040001256572052500271730ustar00rootroot00000000000000// Copyright 2014 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package prometheus_test import ( "runtime" "github.com/golang/protobuf/proto" dto "github.com/prometheus/client_model/go" "github.com/prometheus/client_golang/prometheus" ) func NewCallbackMetric(desc *prometheus.Desc, callback func() float64) *CallbackMetric { result := &CallbackMetric{desc: desc, callback: callback} result.Init(result) // Initialize the SelfCollector. return result } // TODO: Come up with a better example. // CallbackMetric is an example for a user-defined Metric that exports the // result of a function call as a metric of type "untyped" without any // labels. It uses SelfCollector to turn the Metric into a Collector so that it // can be registered with Prometheus. // // Note that this example is pretty much academic as the prometheus package // already provides an UntypedFunc type. type CallbackMetric struct { prometheus.SelfCollector desc *prometheus.Desc callback func() float64 } func (cm *CallbackMetric) Desc() *prometheus.Desc { return cm.desc } func (cm *CallbackMetric) Write(m *dto.Metric) error { m.Untyped = &dto.Untyped{Value: proto.Float64(cm.callback())} return nil } func ExampleSelfCollector() { m := NewCallbackMetric( prometheus.NewDesc( "runtime_goroutines_count", "Total number of goroutines that currently exist.", nil, nil, // No labels, these must be nil. ), func() float64 { return float64(runtime.NumGoroutine()) }, ) prometheus.MustRegister(m) } golang-prometheus-client-0.7.0+ds/prometheus/examples_test.go000066400000000000000000000477741256572052500244670ustar00rootroot00000000000000// Copyright 2014 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package prometheus_test import ( "flag" "fmt" "math" "net/http" "os" "runtime" "sort" "time" dto "github.com/prometheus/client_model/go" "github.com/golang/protobuf/proto" "github.com/prometheus/client_golang/prometheus" ) func ExampleGauge() { opsQueued := prometheus.NewGauge(prometheus.GaugeOpts{ Namespace: "our_company", Subsystem: "blob_storage", Name: "ops_queued", Help: "Number of blob storage operations waiting to be processed.", }) prometheus.MustRegister(opsQueued) // 10 operations queued by the goroutine managing incoming requests. opsQueued.Add(10) // A worker goroutine has picked up a waiting operation. opsQueued.Dec() // And once more... opsQueued.Dec() } func ExampleGaugeVec() { binaryVersion := flag.String("binary_version", "debug", "Version of the binary: debug, canary, production.") flag.Parse() opsQueued := prometheus.NewGaugeVec( prometheus.GaugeOpts{ Namespace: "our_company", Subsystem: "blob_storage", Name: "ops_queued", Help: "Number of blob storage operations waiting to be processed, partitioned by user and type.", ConstLabels: prometheus.Labels{"binary_version": *binaryVersion}, }, []string{ // Which user has requested the operation? "user", // Of what type is the operation? "type", }, ) prometheus.MustRegister(opsQueued) // Increase a value using compact (but order-sensitive!) WithLabelValues(). opsQueued.WithLabelValues("bob", "put").Add(4) // Increase a value with a map using WithLabels. More verbose, but order // doesn't matter anymore. opsQueued.With(prometheus.Labels{"type": "delete", "user": "alice"}).Inc() } func ExampleGaugeFunc() { if err := prometheus.Register(prometheus.NewGaugeFunc( prometheus.GaugeOpts{ Subsystem: "runtime", Name: "goroutines_count", Help: "Number of goroutines that currently exist.", }, func() float64 { return float64(runtime.NumGoroutine()) }, )); err == nil { fmt.Println("GaugeFunc 'goroutines_count' registered.") } // Note that the count of goroutines is a gauge (and not a counter) as // it can go up and down. // Output: // GaugeFunc 'goroutines_count' registered. } func ExampleCounter() { pushCounter := prometheus.NewCounter(prometheus.CounterOpts{ Name: "repository_pushes", // Note: No help string... }) err := prometheus.Register(pushCounter) // ... so this will return an error. if err != nil { fmt.Println("Push counter couldn't be registered, no counting will happen:", err) return } // Try it once more, this time with a help string. pushCounter = prometheus.NewCounter(prometheus.CounterOpts{ Name: "repository_pushes", Help: "Number of pushes to external repository.", }) err = prometheus.Register(pushCounter) if err != nil { fmt.Println("Push counter couldn't be registered AGAIN, no counting will happen:", err) return } pushComplete := make(chan struct{}) // TODO: Start a goroutine that performs repository pushes and reports // each completion via the channel. for _ = range pushComplete { pushCounter.Inc() } // Output: // Push counter couldn't be registered, no counting will happen: descriptor Desc{fqName: "repository_pushes", help: "", constLabels: {}, variableLabels: []} is invalid: empty help string } func ExampleCounterVec() { binaryVersion := flag.String("environment", "test", "Execution environment: test, staging, production.") flag.Parse() httpReqs := prometheus.NewCounterVec( prometheus.CounterOpts{ Name: "http_requests_total", Help: "How many HTTP requests processed, partitioned by status code and HTTP method.", ConstLabels: prometheus.Labels{"env": *binaryVersion}, }, []string{"code", "method"}, ) prometheus.MustRegister(httpReqs) httpReqs.WithLabelValues("404", "POST").Add(42) // If you have to access the same set of labels very frequently, it // might be good to retrieve the metric only once and keep a handle to // it. But beware of deletion of that metric, see below! m := httpReqs.WithLabelValues("200", "GET") for i := 0; i < 1000000; i++ { m.Inc() } // Delete a metric from the vector. If you have previously kept a handle // to that metric (as above), future updates via that handle will go // unseen (even if you re-create a metric with the same label set // later). httpReqs.DeleteLabelValues("200", "GET") // Same thing with the more verbose Labels syntax. httpReqs.Delete(prometheus.Labels{"method": "GET", "code": "200"}) } func ExampleInstrumentHandler() { // Handle the "/doc" endpoint with the standard http.FileServer handler. // By wrapping the handler with InstrumentHandler, request count, // request and response sizes, and request latency are automatically // exported to Prometheus, partitioned by HTTP status code and method // and by the handler name (here "fileserver"). http.Handle("/doc", prometheus.InstrumentHandler( "fileserver", http.FileServer(http.Dir("/usr/share/doc")), )) // The Prometheus handler still has to be registered to handle the // "/metrics" endpoint. The handler returned by prometheus.Handler() is // already instrumented - with "prometheus" as the handler name. In this // example, we want the handler name to be "metrics", so we instrument // the uninstrumented Prometheus handler ourselves. http.Handle("/metrics", prometheus.InstrumentHandler( "metrics", prometheus.UninstrumentedHandler(), )) } func ExampleLabelPairSorter() { labelPairs := []*dto.LabelPair{ &dto.LabelPair{Name: proto.String("status"), Value: proto.String("404")}, &dto.LabelPair{Name: proto.String("method"), Value: proto.String("get")}, } sort.Sort(prometheus.LabelPairSorter(labelPairs)) fmt.Println(labelPairs) // Output: // [name:"method" value:"get" name:"status" value:"404" ] } func ExampleRegister() { // Imagine you have a worker pool and want to count the tasks completed. taskCounter := prometheus.NewCounter(prometheus.CounterOpts{ Subsystem: "worker_pool", Name: "completed_tasks_total", Help: "Total number of tasks completed.", }) // This will register fine. if err := prometheus.Register(taskCounter); err != nil { fmt.Println(err) } else { fmt.Println("taskCounter registered.") } // Don't forget to tell the HTTP server about the Prometheus handler. // (In a real program, you still need to start the HTTP server...) http.Handle("/metrics", prometheus.Handler()) // Now you can start workers and give every one of them a pointer to // taskCounter and let it increment it whenever it completes a task. taskCounter.Inc() // This has to happen somewhere in the worker code. // But wait, you want to see how individual workers perform. So you need // a vector of counters, with one element for each worker. taskCounterVec := prometheus.NewCounterVec( prometheus.CounterOpts{ Subsystem: "worker_pool", Name: "completed_tasks_total", Help: "Total number of tasks completed.", }, []string{"worker_id"}, ) // Registering will fail because we already have a metric of that name. if err := prometheus.Register(taskCounterVec); err != nil { fmt.Println("taskCounterVec not registered:", err) } else { fmt.Println("taskCounterVec registered.") } // To fix, first unregister the old taskCounter. if prometheus.Unregister(taskCounter) { fmt.Println("taskCounter unregistered.") } // Try registering taskCounterVec again. if err := prometheus.Register(taskCounterVec); err != nil { fmt.Println("taskCounterVec not registered:", err) } else { fmt.Println("taskCounterVec registered.") } // Bummer! Still doesn't work. // Prometheus will not allow you to ever export metrics with // inconsistent help strings or label names. After unregistering, the // unregistered metrics will cease to show up in the /metrics HTTP // response, but the registry still remembers that those metrics had // been exported before. For this example, we will now choose a // different name. (In a real program, you would obviously not export // the obsolete metric in the first place.) taskCounterVec = prometheus.NewCounterVec( prometheus.CounterOpts{ Subsystem: "worker_pool", Name: "completed_tasks_by_id", Help: "Total number of tasks completed.", }, []string{"worker_id"}, ) if err := prometheus.Register(taskCounterVec); err != nil { fmt.Println("taskCounterVec not registered:", err) } else { fmt.Println("taskCounterVec registered.") } // Finally it worked! // The workers have to tell taskCounterVec their id to increment the // right element in the metric vector. taskCounterVec.WithLabelValues("42").Inc() // Code from worker 42. // Each worker could also keep a reference to their own counter element // around. Pick the counter at initialization time of the worker. myCounter := taskCounterVec.WithLabelValues("42") // From worker 42 initialization code. myCounter.Inc() // Somewhere in the code of that worker. // Note that something like WithLabelValues("42", "spurious arg") would // panic (because you have provided too many label values). If you want // to get an error instead, use GetMetricWithLabelValues(...) instead. notMyCounter, err := taskCounterVec.GetMetricWithLabelValues("42", "spurious arg") if err != nil { fmt.Println("Worker initialization failed:", err) } if notMyCounter == nil { fmt.Println("notMyCounter is nil.") } // A different (and somewhat tricky) approach is to use // ConstLabels. ConstLabels are pairs of label names and label values // that never change. You might ask what those labels are good for (and // rightfully so - if they never change, they could as well be part of // the metric name). There are essentially two use-cases: The first is // if labels are constant throughout the lifetime of a binary execution, // but they vary over time or between different instances of a running // binary. The second is what we have here: Each worker creates and // registers an own Counter instance where the only difference is in the // value of the ConstLabels. Those Counters can all be registered // because the different ConstLabel values guarantee that each worker // will increment a different Counter metric. counterOpts := prometheus.CounterOpts{ Subsystem: "worker_pool", Name: "completed_tasks", Help: "Total number of tasks completed.", ConstLabels: prometheus.Labels{"worker_id": "42"}, } taskCounterForWorker42 := prometheus.NewCounter(counterOpts) if err := prometheus.Register(taskCounterForWorker42); err != nil { fmt.Println("taskCounterVForWorker42 not registered:", err) } else { fmt.Println("taskCounterForWorker42 registered.") } // Obviously, in real code, taskCounterForWorker42 would be a member // variable of a worker struct, and the "42" would be retrieved with a // GetId() method or something. The Counter would be created and // registered in the initialization code of the worker. // For the creation of the next Counter, we can recycle // counterOpts. Just change the ConstLabels. counterOpts.ConstLabels = prometheus.Labels{"worker_id": "2001"} taskCounterForWorker2001 := prometheus.NewCounter(counterOpts) if err := prometheus.Register(taskCounterForWorker2001); err != nil { fmt.Println("taskCounterVForWorker2001 not registered:", err) } else { fmt.Println("taskCounterForWorker2001 registered.") } taskCounterForWorker2001.Inc() taskCounterForWorker42.Inc() taskCounterForWorker2001.Inc() // Yet another approach would be to turn the workers themselves into // Collectors and register them. See the Collector example for details. // Output: // taskCounter registered. // taskCounterVec not registered: a previously registered descriptor with the same fully-qualified name as Desc{fqName: "worker_pool_completed_tasks_total", help: "Total number of tasks completed.", constLabels: {}, variableLabels: [worker_id]} has different label names or a different help string // taskCounter unregistered. // taskCounterVec not registered: a previously registered descriptor with the same fully-qualified name as Desc{fqName: "worker_pool_completed_tasks_total", help: "Total number of tasks completed.", constLabels: {}, variableLabels: [worker_id]} has different label names or a different help string // taskCounterVec registered. // Worker initialization failed: inconsistent label cardinality // notMyCounter is nil. // taskCounterForWorker42 registered. // taskCounterForWorker2001 registered. } func ExampleSummary() { temps := prometheus.NewSummary(prometheus.SummaryOpts{ Name: "pond_temperature_celsius", Help: "The temperature of the frog pond.", // Sorry, we can't measure how badly it smells. }) // Simulate some observations. for i := 0; i < 1000; i++ { temps.Observe(30 + math.Floor(120*math.Sin(float64(i)*0.1))/10) } // Just for demonstration, let's check the state of the summary by // (ab)using its Write method (which is usually only used by Prometheus // internally). metric := &dto.Metric{} temps.Write(metric) fmt.Println(proto.MarshalTextString(metric)) // Output: // summary: < // sample_count: 1000 // sample_sum: 29969.50000000001 // quantile: < // quantile: 0.5 // value: 31.1 // > // quantile: < // quantile: 0.9 // value: 41.3 // > // quantile: < // quantile: 0.99 // value: 41.9 // > // > } func ExampleSummaryVec() { temps := prometheus.NewSummaryVec( prometheus.SummaryOpts{ Name: "pond_temperature_celsius", Help: "The temperature of the frog pond.", // Sorry, we can't measure how badly it smells. }, []string{"species"}, ) // Simulate some observations. for i := 0; i < 1000; i++ { temps.WithLabelValues("litoria-caerulea").Observe(30 + math.Floor(120*math.Sin(float64(i)*0.1))/10) temps.WithLabelValues("lithobates-catesbeianus").Observe(32 + math.Floor(100*math.Cos(float64(i)*0.11))/10) } // Create a Summary without any observations. temps.WithLabelValues("leiopelma-hochstetteri") // Just for demonstration, let's check the state of the summary vector // by (ab)using its Collect method and the Write method of its elements // (which is usually only used by Prometheus internally - code like the // following will never appear in your own code). metricChan := make(chan prometheus.Metric) go func() { defer close(metricChan) temps.Collect(metricChan) }() metricStrings := []string{} for metric := range metricChan { dtoMetric := &dto.Metric{} metric.Write(dtoMetric) metricStrings = append(metricStrings, proto.MarshalTextString(dtoMetric)) } sort.Strings(metricStrings) // For reproducible print order. fmt.Println(metricStrings) // Output: // [label: < // name: "species" // value: "leiopelma-hochstetteri" // > // summary: < // sample_count: 0 // sample_sum: 0 // quantile: < // quantile: 0.5 // value: nan // > // quantile: < // quantile: 0.9 // value: nan // > // quantile: < // quantile: 0.99 // value: nan // > // > // label: < // name: "species" // value: "lithobates-catesbeianus" // > // summary: < // sample_count: 1000 // sample_sum: 31956.100000000017 // quantile: < // quantile: 0.5 // value: 32.4 // > // quantile: < // quantile: 0.9 // value: 41.4 // > // quantile: < // quantile: 0.99 // value: 41.9 // > // > // label: < // name: "species" // value: "litoria-caerulea" // > // summary: < // sample_count: 1000 // sample_sum: 29969.50000000001 // quantile: < // quantile: 0.5 // value: 31.1 // > // quantile: < // quantile: 0.9 // value: 41.3 // > // quantile: < // quantile: 0.99 // value: 41.9 // > // > // ] } func ExampleConstSummary() { desc := prometheus.NewDesc( "http_request_duration_seconds", "A summary of the HTTP request durations.", []string{"code", "method"}, prometheus.Labels{"owner": "example"}, ) // Create a constant summary from values we got from a 3rd party telemetry system. s := prometheus.MustNewConstSummary( desc, 4711, 403.34, map[float64]float64{0.5: 42.3, 0.9: 323.3}, "200", "get", ) // Just for demonstration, let's check the state of the summary by // (ab)using its Write method (which is usually only used by Prometheus // internally). metric := &dto.Metric{} s.Write(metric) fmt.Println(proto.MarshalTextString(metric)) // Output: // label: < // name: "code" // value: "200" // > // label: < // name: "method" // value: "get" // > // label: < // name: "owner" // value: "example" // > // summary: < // sample_count: 4711 // sample_sum: 403.34 // quantile: < // quantile: 0.5 // value: 42.3 // > // quantile: < // quantile: 0.9 // value: 323.3 // > // > } func ExampleHistogram() { temps := prometheus.NewHistogram(prometheus.HistogramOpts{ Name: "pond_temperature_celsius", Help: "The temperature of the frog pond.", // Sorry, we can't measure how badly it smells. Buckets: prometheus.LinearBuckets(20, 5, 5), // 5 buckets, each 5 centigrade wide. }) // Simulate some observations. for i := 0; i < 1000; i++ { temps.Observe(30 + math.Floor(120*math.Sin(float64(i)*0.1))/10) } // Just for demonstration, let's check the state of the histogram by // (ab)using its Write method (which is usually only used by Prometheus // internally). metric := &dto.Metric{} temps.Write(metric) fmt.Println(proto.MarshalTextString(metric)) // Output: // histogram: < // sample_count: 1000 // sample_sum: 29969.50000000001 // bucket: < // cumulative_count: 192 // upper_bound: 20 // > // bucket: < // cumulative_count: 366 // upper_bound: 25 // > // bucket: < // cumulative_count: 501 // upper_bound: 30 // > // bucket: < // cumulative_count: 638 // upper_bound: 35 // > // bucket: < // cumulative_count: 816 // upper_bound: 40 // > // > } func ExampleConstHistogram() { desc := prometheus.NewDesc( "http_request_duration_seconds", "A histogram of the HTTP request durations.", []string{"code", "method"}, prometheus.Labels{"owner": "example"}, ) // Create a constant histogram from values we got from a 3rd party telemetry system. h := prometheus.MustNewConstHistogram( desc, 4711, 403.34, map[float64]uint64{25: 121, 50: 2403, 100: 3221, 200: 4233}, "200", "get", ) // Just for demonstration, let's check the state of the histogram by // (ab)using its Write method (which is usually only used by Prometheus // internally). metric := &dto.Metric{} h.Write(metric) fmt.Println(proto.MarshalTextString(metric)) // Output: // label: < // name: "code" // value: "200" // > // label: < // name: "method" // value: "get" // > // label: < // name: "owner" // value: "example" // > // histogram: < // sample_count: 4711 // sample_sum: 403.34 // bucket: < // cumulative_count: 121 // upper_bound: 25 // > // bucket: < // cumulative_count: 2403 // upper_bound: 50 // > // bucket: < // cumulative_count: 3221 // upper_bound: 100 // > // bucket: < // cumulative_count: 4233 // upper_bound: 200 // > // > } func ExamplePushCollectors() { hostname, _ := os.Hostname() completionTime := prometheus.NewGauge(prometheus.GaugeOpts{ Name: "db_backup_last_completion_time", Help: "The timestamp of the last succesful completion of a DB backup.", }) completionTime.Set(float64(time.Now().Unix())) if err := prometheus.PushCollectors( "db_backup", hostname, "http://pushgateway:9091", completionTime, ); err != nil { fmt.Println("Could not push completion time to Pushgateway:", err) } } golang-prometheus-client-0.7.0+ds/prometheus/expvar.go000066400000000000000000000101021256572052500230660ustar00rootroot00000000000000// Copyright 2014 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package prometheus import ( "encoding/json" "expvar" ) // ExpvarCollector collects metrics from the expvar interface. It provides a // quick way to expose numeric values that are already exported via expvar as // Prometheus metrics. Note that the data models of expvar and Prometheus are // fundamentally different, and that the ExpvarCollector is inherently // slow. Thus, the ExpvarCollector is probably great for experiments and // prototying, but you should seriously consider a more direct implementation of // Prometheus metrics for monitoring production systems. // // Use NewExpvarCollector to create new instances. type ExpvarCollector struct { exports map[string]*Desc } // NewExpvarCollector returns a newly allocated ExpvarCollector that still has // to be registered with the Prometheus registry. // // The exports map has the following meaning: // // The keys in the map correspond to expvar keys, i.e. for every expvar key you // want to export as Prometheus metric, you need an entry in the exports // map. The descriptor mapped to each key describes how to export the expvar // value. It defines the name and the help string of the Prometheus metric // proxying the expvar value. The type will always be Untyped. // // For descriptors without variable labels, the expvar value must be a number or // a bool. The number is then directly exported as the Prometheus sample // value. (For a bool, 'false' translates to 0 and 'true' to 1). Expvar values // that are not numbers or bools are silently ignored. // // If the descriptor has one variable label, the expvar value must be an expvar // map. The keys in the expvar map become the various values of the one // Prometheus label. The values in the expvar map must be numbers or bools again // as above. // // For descriptors with more than one variable label, the expvar must be a // nested expvar map, i.e. where the values of the topmost map are maps again // etc. until a depth is reached that corresponds to the number of labels. The // leaves of that structure must be numbers or bools as above to serve as the // sample values. // // Anything that does not fit into the scheme above is silently ignored. func NewExpvarCollector(exports map[string]*Desc) *ExpvarCollector { return &ExpvarCollector{ exports: exports, } } // Describe implements Collector. func (e *ExpvarCollector) Describe(ch chan<- *Desc) { for _, desc := range e.exports { ch <- desc } } // Collect implements Collector. func (e *ExpvarCollector) Collect(ch chan<- Metric) { for name, desc := range e.exports { var m Metric expVar := expvar.Get(name) if expVar == nil { continue } var v interface{} labels := make([]string, len(desc.variableLabels)) if err := json.Unmarshal([]byte(expVar.String()), &v); err != nil { ch <- NewInvalidMetric(desc, err) continue } var processValue func(v interface{}, i int) processValue = func(v interface{}, i int) { if i >= len(labels) { copiedLabels := append(make([]string, 0, len(labels)), labels...) switch v := v.(type) { case float64: m = MustNewConstMetric(desc, UntypedValue, v, copiedLabels...) case bool: if v { m = MustNewConstMetric(desc, UntypedValue, 1, copiedLabels...) } else { m = MustNewConstMetric(desc, UntypedValue, 0, copiedLabels...) } default: return } ch <- m return } vm, ok := v.(map[string]interface{}) if !ok { return } for lv, val := range vm { labels[i] = lv processValue(val, i+1) } } processValue(v, 0) } } golang-prometheus-client-0.7.0+ds/prometheus/expvar_test.go000066400000000000000000000064161256572052500241420ustar00rootroot00000000000000// Copyright 2014 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package prometheus_test import ( "expvar" "fmt" "sort" "strings" dto "github.com/prometheus/client_model/go" "github.com/prometheus/client_golang/prometheus" ) func ExampleExpvarCollector() { expvarCollector := prometheus.NewExpvarCollector(map[string]*prometheus.Desc{ "memstats": prometheus.NewDesc( "expvar_memstats", "All numeric memstats as one metric family. Not a good role-model, actually... ;-)", []string{"type"}, nil, ), "lone-int": prometheus.NewDesc( "expvar_lone_int", "Just an expvar int as an example.", nil, nil, ), "http-request-map": prometheus.NewDesc( "expvar_http_request_total", "How many http requests processed, partitioned by status code and http method.", []string{"code", "method"}, nil, ), }) prometheus.MustRegister(expvarCollector) // The Prometheus part is done here. But to show that this example is // doing anything, we have to manually export something via expvar. In // real-life use-cases, some library would already have exported via // expvar what we want to re-export as Prometheus metrics. expvar.NewInt("lone-int").Set(42) expvarMap := expvar.NewMap("http-request-map") var ( expvarMap1, expvarMap2 expvar.Map expvarInt11, expvarInt12, expvarInt21, expvarInt22 expvar.Int ) expvarMap1.Init() expvarMap2.Init() expvarInt11.Set(3) expvarInt12.Set(13) expvarInt21.Set(11) expvarInt22.Set(212) expvarMap1.Set("POST", &expvarInt11) expvarMap1.Set("GET", &expvarInt12) expvarMap2.Set("POST", &expvarInt21) expvarMap2.Set("GET", &expvarInt22) expvarMap.Set("404", &expvarMap1) expvarMap.Set("200", &expvarMap2) // Results in the following expvar map: // "http-request-count": {"200": {"POST": 11, "GET": 212}, "404": {"POST": 3, "GET": 13}} // Let's see what the scrape would yield, but exclude the memstats metrics. metricStrings := []string{} metric := dto.Metric{} metricChan := make(chan prometheus.Metric) go func() { expvarCollector.Collect(metricChan) close(metricChan) }() for m := range metricChan { if strings.Index(m.Desc().String(), "expvar_memstats") == -1 { metric.Reset() m.Write(&metric) metricStrings = append(metricStrings, metric.String()) } } sort.Strings(metricStrings) for _, s := range metricStrings { fmt.Println(strings.TrimRight(s, " ")) } // Output: // label: label: untyped: // label: label: untyped: // label: label: untyped: // label: label: untyped: // untyped: } golang-prometheus-client-0.7.0+ds/prometheus/gauge.go000066400000000000000000000116221256572052500226610ustar00rootroot00000000000000// Copyright 2014 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package prometheus import "hash/fnv" // Gauge is a Metric that represents a single numerical value that can // arbitrarily go up and down. // // A Gauge is typically used for measured values like temperatures or current // memory usage, but also "counts" that can go up and down, like the number of // running goroutines. // // To create Gauge instances, use NewGauge. type Gauge interface { Metric Collector // Set sets the Gauge to an arbitrary value. Set(float64) // Inc increments the Gauge by 1. Inc() // Dec decrements the Gauge by 1. Dec() // Add adds the given value to the Gauge. (The value can be // negative, resulting in a decrease of the Gauge.) Add(float64) // Sub subtracts the given value from the Gauge. (The value can be // negative, resulting in an increase of the Gauge.) Sub(float64) } // GaugeOpts is an alias for Opts. See there for doc comments. type GaugeOpts Opts // NewGauge creates a new Gauge based on the provided GaugeOpts. func NewGauge(opts GaugeOpts) Gauge { return newValue(NewDesc( BuildFQName(opts.Namespace, opts.Subsystem, opts.Name), opts.Help, nil, opts.ConstLabels, ), GaugeValue, 0) } // GaugeVec is a Collector that bundles a set of Gauges that all share the same // Desc, but have different values for their variable labels. This is used if // you want to count the same thing partitioned by various dimensions // (e.g. number of operations queued, partitioned by user and operation // type). Create instances with NewGaugeVec. type GaugeVec struct { MetricVec } // NewGaugeVec creates a new GaugeVec based on the provided GaugeOpts and // partitioned by the given label names. At least one label name must be // provided. func NewGaugeVec(opts GaugeOpts, labelNames []string) *GaugeVec { desc := NewDesc( BuildFQName(opts.Namespace, opts.Subsystem, opts.Name), opts.Help, labelNames, opts.ConstLabels, ) return &GaugeVec{ MetricVec: MetricVec{ children: map[uint64]Metric{}, desc: desc, hash: fnv.New64a(), newMetric: func(lvs ...string) Metric { return newValue(desc, GaugeValue, 0, lvs...) }, }, } } // GetMetricWithLabelValues replaces the method of the same name in // MetricVec. The difference is that this method returns a Gauge and not a // Metric so that no type conversion is required. func (m *GaugeVec) GetMetricWithLabelValues(lvs ...string) (Gauge, error) { metric, err := m.MetricVec.GetMetricWithLabelValues(lvs...) if metric != nil { return metric.(Gauge), err } return nil, err } // GetMetricWith replaces the method of the same name in MetricVec. The // difference is that this method returns a Gauge and not a Metric so that no // type conversion is required. func (m *GaugeVec) GetMetricWith(labels Labels) (Gauge, error) { metric, err := m.MetricVec.GetMetricWith(labels) if metric != nil { return metric.(Gauge), err } return nil, err } // WithLabelValues works as GetMetricWithLabelValues, but panics where // GetMetricWithLabelValues would have returned an error. By not returning an // error, WithLabelValues allows shortcuts like // myVec.WithLabelValues("404", "GET").Add(42) func (m *GaugeVec) WithLabelValues(lvs ...string) Gauge { return m.MetricVec.WithLabelValues(lvs...).(Gauge) } // With works as GetMetricWith, but panics where GetMetricWithLabels would have // returned an error. By not returning an error, With allows shortcuts like // myVec.With(Labels{"code": "404", "method": "GET"}).Add(42) func (m *GaugeVec) With(labels Labels) Gauge { return m.MetricVec.With(labels).(Gauge) } // GaugeFunc is a Gauge whose value is determined at collect time by calling a // provided function. // // To create GaugeFunc instances, use NewGaugeFunc. type GaugeFunc interface { Metric Collector } // NewGaugeFunc creates a new GaugeFunc based on the provided GaugeOpts. The // value reported is determined by calling the given function from within the // Write method. Take into account that metric collection may happen // concurrently. If that results in concurrent calls to Write, like in the case // where a GaugeFunc is directly registered with Prometheus, the provided // function must be concurrency-safe. func NewGaugeFunc(opts GaugeOpts, function func() float64) GaugeFunc { return newValueFunc(NewDesc( BuildFQName(opts.Namespace, opts.Subsystem, opts.Name), opts.Help, nil, opts.ConstLabels, ), GaugeValue, function) } golang-prometheus-client-0.7.0+ds/prometheus/gauge_test.go000066400000000000000000000102131256572052500237130ustar00rootroot00000000000000// Copyright 2014 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package prometheus import ( "math" "math/rand" "sync" "testing" "testing/quick" dto "github.com/prometheus/client_model/go" ) func listenGaugeStream(vals, result chan float64, done chan struct{}) { var sum float64 outer: for { select { case <-done: close(vals) for v := range vals { sum += v } break outer case v := <-vals: sum += v } } result <- sum close(result) } func TestGaugeConcurrency(t *testing.T) { it := func(n uint32) bool { mutations := int(n % 10000) concLevel := int(n%15 + 1) var start, end sync.WaitGroup start.Add(1) end.Add(concLevel) sStream := make(chan float64, mutations*concLevel) result := make(chan float64) done := make(chan struct{}) go listenGaugeStream(sStream, result, done) go func() { end.Wait() close(done) }() gge := NewGauge(GaugeOpts{ Name: "test_gauge", Help: "no help can be found here", }) for i := 0; i < concLevel; i++ { vals := make([]float64, mutations) for j := 0; j < mutations; j++ { vals[j] = rand.Float64() - 0.5 } go func(vals []float64) { start.Wait() for _, v := range vals { sStream <- v gge.Add(v) } end.Done() }(vals) } start.Done() if expected, got := <-result, math.Float64frombits(gge.(*value).valBits); math.Abs(expected-got) > 0.000001 { t.Fatalf("expected approx. %f, got %f", expected, got) return false } return true } if err := quick.Check(it, nil); err != nil { t.Fatal(err) } } func TestGaugeVecConcurrency(t *testing.T) { it := func(n uint32) bool { mutations := int(n % 10000) concLevel := int(n%15 + 1) vecLength := int(n%5 + 1) var start, end sync.WaitGroup start.Add(1) end.Add(concLevel) sStreams := make([]chan float64, vecLength) results := make([]chan float64, vecLength) done := make(chan struct{}) for i := 0; i < vecLength; i++ { sStreams[i] = make(chan float64, mutations*concLevel) results[i] = make(chan float64) go listenGaugeStream(sStreams[i], results[i], done) } go func() { end.Wait() close(done) }() gge := NewGaugeVec( GaugeOpts{ Name: "test_gauge", Help: "no help can be found here", }, []string{"label"}, ) for i := 0; i < concLevel; i++ { vals := make([]float64, mutations) pick := make([]int, mutations) for j := 0; j < mutations; j++ { vals[j] = rand.Float64() - 0.5 pick[j] = rand.Intn(vecLength) } go func(vals []float64) { start.Wait() for i, v := range vals { sStreams[pick[i]] <- v gge.WithLabelValues(string('A' + pick[i])).Add(v) } end.Done() }(vals) } start.Done() for i := range sStreams { if expected, got := <-results[i], math.Float64frombits(gge.WithLabelValues(string('A'+i)).(*value).valBits); math.Abs(expected-got) > 0.000001 { t.Fatalf("expected approx. %f, got %f", expected, got) return false } } return true } if err := quick.Check(it, nil); err != nil { t.Fatal(err) } } func TestGaugeFunc(t *testing.T) { gf := NewGaugeFunc( GaugeOpts{ Name: "test_name", Help: "test help", ConstLabels: Labels{"a": "1", "b": "2"}, }, func() float64 { return 3.1415 }, ) if expected, got := `Desc{fqName: "test_name", help: "test help", constLabels: {a="1",b="2"}, variableLabels: []}`, gf.Desc().String(); expected != got { t.Errorf("expected %q, got %q", expected, got) } m := &dto.Metric{} gf.Write(m) if expected, got := `label: label: gauge: `, m.String(); expected != got { t.Errorf("expected %q, got %q", expected, got) } } golang-prometheus-client-0.7.0+ds/prometheus/go_collector.go000066400000000000000000000024231256572052500242430ustar00rootroot00000000000000package prometheus import ( "runtime" "runtime/debug" "time" ) type goCollector struct { goroutines Gauge gcDesc *Desc } // NewGoCollector returns a collector which exports metrics about the current // go process. func NewGoCollector() *goCollector { return &goCollector{ goroutines: NewGauge(GaugeOpts{ Name: "go_goroutines", Help: "Number of goroutines that currently exist.", }), gcDesc: NewDesc( "go_gc_duration_seconds", "A summary of the GC invocation durations.", nil, nil), } } // Describe returns all descriptions of the collector. func (c *goCollector) Describe(ch chan<- *Desc) { ch <- c.goroutines.Desc() ch <- c.gcDesc } // Collect returns the current state of all metrics of the collector. func (c *goCollector) Collect(ch chan<- Metric) { c.goroutines.Set(float64(runtime.NumGoroutine())) ch <- c.goroutines var stats debug.GCStats stats.PauseQuantiles = make([]time.Duration, 5) debug.ReadGCStats(&stats) quantiles := make(map[float64]float64) for idx, pq := range stats.PauseQuantiles[1:] { quantiles[float64(idx+1)/float64(len(stats.PauseQuantiles)-1)] = pq.Seconds() } quantiles[0.0] = stats.PauseQuantiles[0].Seconds() ch <- MustNewConstSummary(c.gcDesc, uint64(stats.NumGC), float64(stats.PauseTotal.Seconds()), quantiles) } golang-prometheus-client-0.7.0+ds/prometheus/go_collector_test.go000066400000000000000000000046241256572052500253070ustar00rootroot00000000000000package prometheus import ( "runtime" "testing" "time" dto "github.com/prometheus/client_model/go" ) func TestGoCollector(t *testing.T) { var ( c = NewGoCollector() ch = make(chan Metric) waitc = make(chan struct{}) closec = make(chan struct{}) old = -1 ) defer close(closec) go func() { c.Collect(ch) go func(c <-chan struct{}) { <-c }(closec) <-waitc c.Collect(ch) }() for { select { case metric := <-ch: switch m := metric.(type) { // Attention, this also catches Counter... case Gauge: pb := &dto.Metric{} m.Write(pb) if pb.GetGauge() == nil { continue } if old == -1 { old = int(pb.GetGauge().GetValue()) close(waitc) continue } if diff := int(pb.GetGauge().GetValue()) - old; diff != 1 { // TODO: This is flaky in highly concurrent situations. t.Errorf("want 1 new goroutine, got %d", diff) } return } case <-time.After(1 * time.Second): t.Fatalf("expected collect timed out") } } } func TestGCCollector(t *testing.T) { var ( c = NewGoCollector() ch = make(chan Metric) waitc = make(chan struct{}) closec = make(chan struct{}) oldGC uint64 oldPause float64 ) defer close(closec) go func() { c.Collect(ch) // force GC runtime.GC() <-waitc c.Collect(ch) }() first := true for { select { case metric := <-ch: switch m := metric.(type) { case *constSummary, *value: pb := &dto.Metric{} m.Write(pb) if pb.GetSummary() == nil { continue } if len(pb.GetSummary().Quantile) != 5 { t.Errorf("expected 4 buckets, got %d", len(pb.GetSummary().Quantile)) } for idx, want := range []float64{0.0, 0.25, 0.5, 0.75, 1.0} { if *pb.GetSummary().Quantile[idx].Quantile != want { t.Errorf("bucket #%d is off, got %f, want %f", idx, *pb.GetSummary().Quantile[idx].Quantile, want) } } if first { first = false oldGC = *pb.GetSummary().SampleCount oldPause = *pb.GetSummary().SampleSum close(waitc) continue } if diff := *pb.GetSummary().SampleCount - oldGC; diff != 1 { t.Errorf("want 1 new garbage collection run, got %d", diff) } if diff := *pb.GetSummary().SampleSum - oldPause; diff <= 0 { t.Errorf("want moar pause, got %f", diff) } return } case <-time.After(1 * time.Second): t.Fatalf("expected collect timed out") } } } golang-prometheus-client-0.7.0+ds/prometheus/histogram.go000066400000000000000000000336471256572052500236010ustar00rootroot00000000000000// Copyright 2015 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package prometheus import ( "fmt" "hash/fnv" "math" "sort" "sync/atomic" "github.com/golang/protobuf/proto" "github.com/prometheus/client_golang/model" dto "github.com/prometheus/client_model/go" ) // A Histogram counts individual observations from an event or sample stream in // configurable buckets. Similar to a summary, it also provides a sum of // observations and an observation count. // // On the Prometheus server, quantiles can be calculated from a Histogram using // the histogram_quantile function in the query language. // // Note that Histograms, in contrast to Summaries, can be aggregated with the // Prometheus query language (see the documentation for detailed // procedures). However, Histograms require the user to pre-define suitable // buckets, and they are in general less accurate. The Observe method of a // Histogram has a very low performance overhead in comparison with the Observe // method of a Summary. // // To create Histogram instances, use NewHistogram. type Histogram interface { Metric Collector // Observe adds a single observation to the histogram. Observe(float64) } var ( // DefBuckets are the default Histogram buckets. The default buckets are // tailored to broadly measure the response time (in seconds) of a // network service. Most likely, however, you will be required to define // buckets customized to your use case. DefBuckets = []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10} errBucketLabelNotAllowed = fmt.Errorf( "%q is not allowed as label name in histograms", model.BucketLabel, ) ) // LinearBuckets creates 'count' buckets, each 'width' wide, where the lowest // bucket has an upper bound of 'start'. The final +Inf bucket is not counted // and not included in the returned slice. The returned slice is meant to be // used for the Buckets field of HistogramOpts. // // The function panics if 'count' is zero or negative. func LinearBuckets(start, width float64, count int) []float64 { if count < 1 { panic("LinearBuckets needs a positive count") } buckets := make([]float64, count) for i := range buckets { buckets[i] = start start += width } return buckets } // ExponentialBuckets creates 'count' buckets, where the lowest bucket has an // upper bound of 'start' and each following bucket's upper bound is 'factor' // times the previous bucket's upper bound. The final +Inf bucket is not counted // and not included in the returned slice. The returned slice is meant to be // used for the Buckets field of HistogramOpts. // // The function panics if 'count' is 0 or negative, if 'start' is 0 or negative, // or if 'factor' is less than or equal 1. func ExponentialBuckets(start, factor float64, count int) []float64 { if count < 1 { panic("ExponentialBuckets needs a positive count") } if start <= 0 { panic("ExponentialBuckets needs a positive start value") } if factor <= 1 { panic("ExponentialBuckets needs a factor greater than 1") } buckets := make([]float64, count) for i := range buckets { buckets[i] = start start *= factor } return buckets } // HistogramOpts bundles the options for creating a Histogram metric. It is // mandatory to set Name and Help to a non-empty string. All other fields are // optional and can safely be left at their zero value. type HistogramOpts struct { // Namespace, Subsystem, and Name are components of the fully-qualified // name of the Histogram (created by joining these components with // "_"). Only Name is mandatory, the others merely help structuring the // name. Note that the fully-qualified name of the Histogram must be a // valid Prometheus metric name. Namespace string Subsystem string Name string // Help provides information about this Histogram. Mandatory! // // Metrics with the same fully-qualified name must have the same Help // string. Help string // ConstLabels are used to attach fixed labels to this // Histogram. Histograms with the same fully-qualified name must have the // same label names in their ConstLabels. // // Note that in most cases, labels have a value that varies during the // lifetime of a process. Those labels are usually managed with a // HistogramVec. ConstLabels serve only special purposes. One is for the // special case where the value of a label does not change during the // lifetime of a process, e.g. if the revision of the running binary is // put into a label. Another, more advanced purpose is if more than one // Collector needs to collect Histograms with the same fully-qualified // name. In that case, those Summaries must differ in the values of // their ConstLabels. See the Collector examples. // // If the value of a label never changes (not even between binaries), // that label most likely should not be a label at all (but part of the // metric name). ConstLabels Labels // Buckets defines the buckets into which observations are counted. Each // element in the slice is the upper inclusive bound of a bucket. The // values must be sorted in strictly increasing order. There is no need // to add a highest bucket with +Inf bound, it will be added // implicitly. The default value is DefBuckets. Buckets []float64 } // NewHistogram creates a new Histogram based on the provided HistogramOpts. It // panics if the buckets in HistogramOpts are not in strictly increasing order. func NewHistogram(opts HistogramOpts) Histogram { return newHistogram( NewDesc( BuildFQName(opts.Namespace, opts.Subsystem, opts.Name), opts.Help, nil, opts.ConstLabels, ), opts, ) } func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogram { if len(desc.variableLabels) != len(labelValues) { panic(errInconsistentCardinality) } for _, n := range desc.variableLabels { if n == model.BucketLabel { panic(errBucketLabelNotAllowed) } } for _, lp := range desc.constLabelPairs { if lp.GetName() == model.BucketLabel { panic(errBucketLabelNotAllowed) } } if len(opts.Buckets) == 0 { opts.Buckets = DefBuckets } h := &histogram{ desc: desc, upperBounds: opts.Buckets, labelPairs: makeLabelPairs(desc, labelValues), } for i, upperBound := range h.upperBounds { if i < len(h.upperBounds)-1 { if upperBound >= h.upperBounds[i+1] { panic(fmt.Errorf( "histogram buckets must be in increasing order: %f >= %f", upperBound, h.upperBounds[i+1], )) } } else { if math.IsInf(upperBound, +1) { // The +Inf bucket is implicit. Remove it here. h.upperBounds = h.upperBounds[:i] } } } // Finally we know the final length of h.upperBounds and can make counts. h.counts = make([]uint64, len(h.upperBounds)) h.Init(h) // Init self-collection. return h } type histogram struct { // sumBits contains the bits of the float64 representing the sum of all // observations. sumBits and count have to go first in the struct to // guarantee alignment for atomic operations. // http://golang.org/pkg/sync/atomic/#pkg-note-BUG sumBits uint64 count uint64 SelfCollector // Note that there is no mutex required. desc *Desc upperBounds []float64 counts []uint64 labelPairs []*dto.LabelPair } func (h *histogram) Desc() *Desc { return h.desc } func (h *histogram) Observe(v float64) { // TODO(beorn7): For small numbers of buckets (<30), a linear search is // slightly faster than the binary search. If we really care, we could // switch from one search strategy to the other depending on the number // of buckets. // // Microbenchmarks (BenchmarkHistogramNoLabels): // 11 buckets: 38.3 ns/op linear - binary 48.7 ns/op // 100 buckets: 78.1 ns/op linear - binary 54.9 ns/op // 300 buckets: 154 ns/op linear - binary 61.6 ns/op i := sort.SearchFloat64s(h.upperBounds, v) if i < len(h.counts) { atomic.AddUint64(&h.counts[i], 1) } atomic.AddUint64(&h.count, 1) for { oldBits := atomic.LoadUint64(&h.sumBits) newBits := math.Float64bits(math.Float64frombits(oldBits) + v) if atomic.CompareAndSwapUint64(&h.sumBits, oldBits, newBits) { break } } } func (h *histogram) Write(out *dto.Metric) error { his := &dto.Histogram{} buckets := make([]*dto.Bucket, len(h.upperBounds)) his.SampleSum = proto.Float64(math.Float64frombits(atomic.LoadUint64(&h.sumBits))) his.SampleCount = proto.Uint64(atomic.LoadUint64(&h.count)) var count uint64 for i, upperBound := range h.upperBounds { count += atomic.LoadUint64(&h.counts[i]) buckets[i] = &dto.Bucket{ CumulativeCount: proto.Uint64(count), UpperBound: proto.Float64(upperBound), } } his.Bucket = buckets out.Histogram = his out.Label = h.labelPairs return nil } // HistogramVec is a Collector that bundles a set of Histograms that all share the // same Desc, but have different values for their variable labels. This is used // if you want to count the same thing partitioned by various dimensions // (e.g. HTTP request latencies, partitioned by status code and method). Create // instances with NewHistogramVec. type HistogramVec struct { MetricVec } // NewHistogramVec creates a new HistogramVec based on the provided HistogramOpts and // partitioned by the given label names. At least one label name must be // provided. func NewHistogramVec(opts HistogramOpts, labelNames []string) *HistogramVec { desc := NewDesc( BuildFQName(opts.Namespace, opts.Subsystem, opts.Name), opts.Help, labelNames, opts.ConstLabels, ) return &HistogramVec{ MetricVec: MetricVec{ children: map[uint64]Metric{}, desc: desc, hash: fnv.New64a(), newMetric: func(lvs ...string) Metric { return newHistogram(desc, opts, lvs...) }, }, } } // GetMetricWithLabelValues replaces the method of the same name in // MetricVec. The difference is that this method returns a Histogram and not a // Metric so that no type conversion is required. func (m *HistogramVec) GetMetricWithLabelValues(lvs ...string) (Histogram, error) { metric, err := m.MetricVec.GetMetricWithLabelValues(lvs...) if metric != nil { return metric.(Histogram), err } return nil, err } // GetMetricWith replaces the method of the same name in MetricVec. The // difference is that this method returns a Histogram and not a Metric so that no // type conversion is required. func (m *HistogramVec) GetMetricWith(labels Labels) (Histogram, error) { metric, err := m.MetricVec.GetMetricWith(labels) if metric != nil { return metric.(Histogram), err } return nil, err } // WithLabelValues works as GetMetricWithLabelValues, but panics where // GetMetricWithLabelValues would have returned an error. By not returning an // error, WithLabelValues allows shortcuts like // myVec.WithLabelValues("404", "GET").Observe(42.21) func (m *HistogramVec) WithLabelValues(lvs ...string) Histogram { return m.MetricVec.WithLabelValues(lvs...).(Histogram) } // With works as GetMetricWith, but panics where GetMetricWithLabels would have // returned an error. By not returning an error, With allows shortcuts like // myVec.With(Labels{"code": "404", "method": "GET"}).Observe(42.21) func (m *HistogramVec) With(labels Labels) Histogram { return m.MetricVec.With(labels).(Histogram) } type constHistogram struct { desc *Desc count uint64 sum float64 buckets map[float64]uint64 labelPairs []*dto.LabelPair } func (h *constHistogram) Desc() *Desc { return h.desc } func (h *constHistogram) Write(out *dto.Metric) error { his := &dto.Histogram{} buckets := make([]*dto.Bucket, 0, len(h.buckets)) his.SampleCount = proto.Uint64(h.count) his.SampleSum = proto.Float64(h.sum) for upperBound, count := range h.buckets { buckets = append(buckets, &dto.Bucket{ CumulativeCount: proto.Uint64(count), UpperBound: proto.Float64(upperBound), }) } if len(buckets) > 0 { sort.Sort(buckSort(buckets)) } his.Bucket = buckets out.Histogram = his out.Label = h.labelPairs return nil } // NewConstHistogram returns a metric representing a Prometheus histogram with // fixed values for the count, sum, and bucket counts. As those parameters // cannot be changed, the returned value does not implement the Histogram // interface (but only the Metric interface). Users of this package will not // have much use for it in regular operations. However, when implementing custom // Collectors, it is useful as a throw-away metric that is generated on the fly // to send it to Prometheus in the Collect method. // // buckets is a map of upper bounds to cumulative counts, excluding the +Inf // bucket. // // NewConstHistogram returns an error if the length of labelValues is not // consistent with the variable labels in Desc. func NewConstHistogram( desc *Desc, count uint64, sum float64, buckets map[float64]uint64, labelValues ...string, ) (Metric, error) { if len(desc.variableLabels) != len(labelValues) { return nil, errInconsistentCardinality } return &constHistogram{ desc: desc, count: count, sum: sum, buckets: buckets, labelPairs: makeLabelPairs(desc, labelValues), }, nil } // MustNewConstHistogram is a version of NewConstHistogram that panics where // NewConstMetric would have returned an error. func MustNewConstHistogram( desc *Desc, count uint64, sum float64, buckets map[float64]uint64, labelValues ...string, ) Metric { m, err := NewConstHistogram(desc, count, sum, buckets, labelValues...) if err != nil { panic(err) } return m } type buckSort []*dto.Bucket func (s buckSort) Len() int { return len(s) } func (s buckSort) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s buckSort) Less(i, j int) bool { return s[i].GetUpperBound() < s[j].GetUpperBound() } golang-prometheus-client-0.7.0+ds/prometheus/histogram_test.go000066400000000000000000000162261256572052500246320ustar00rootroot00000000000000// Copyright 2015 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package prometheus import ( "math" "math/rand" "reflect" "sort" "sync" "testing" "testing/quick" dto "github.com/prometheus/client_model/go" ) func benchmarkHistogramObserve(w int, b *testing.B) { b.StopTimer() wg := new(sync.WaitGroup) wg.Add(w) g := new(sync.WaitGroup) g.Add(1) s := NewHistogram(HistogramOpts{}) for i := 0; i < w; i++ { go func() { g.Wait() for i := 0; i < b.N; i++ { s.Observe(float64(i)) } wg.Done() }() } b.StartTimer() g.Done() wg.Wait() } func BenchmarkHistogramObserve1(b *testing.B) { benchmarkHistogramObserve(1, b) } func BenchmarkHistogramObserve2(b *testing.B) { benchmarkHistogramObserve(2, b) } func BenchmarkHistogramObserve4(b *testing.B) { benchmarkHistogramObserve(4, b) } func BenchmarkHistogramObserve8(b *testing.B) { benchmarkHistogramObserve(8, b) } func benchmarkHistogramWrite(w int, b *testing.B) { b.StopTimer() wg := new(sync.WaitGroup) wg.Add(w) g := new(sync.WaitGroup) g.Add(1) s := NewHistogram(HistogramOpts{}) for i := 0; i < 1000000; i++ { s.Observe(float64(i)) } for j := 0; j < w; j++ { outs := make([]dto.Metric, b.N) go func(o []dto.Metric) { g.Wait() for i := 0; i < b.N; i++ { s.Write(&o[i]) } wg.Done() }(outs) } b.StartTimer() g.Done() wg.Wait() } func BenchmarkHistogramWrite1(b *testing.B) { benchmarkHistogramWrite(1, b) } func BenchmarkHistogramWrite2(b *testing.B) { benchmarkHistogramWrite(2, b) } func BenchmarkHistogramWrite4(b *testing.B) { benchmarkHistogramWrite(4, b) } func BenchmarkHistogramWrite8(b *testing.B) { benchmarkHistogramWrite(8, b) } // Intentionally adding +Inf here to test if that case is handled correctly. // Also, getCumulativeCounts depends on it. var testBuckets = []float64{-2, -1, -0.5, 0, 0.5, 1, 2, math.Inf(+1)} func TestHistogramConcurrency(t *testing.T) { rand.Seed(42) it := func(n uint32) bool { mutations := int(n%1e4 + 1e4) concLevel := int(n%5 + 1) total := mutations * concLevel var start, end sync.WaitGroup start.Add(1) end.Add(concLevel) sum := NewHistogram(HistogramOpts{ Name: "test_histogram", Help: "helpless", Buckets: testBuckets, }) allVars := make([]float64, total) var sampleSum float64 for i := 0; i < concLevel; i++ { vals := make([]float64, mutations) for j := 0; j < mutations; j++ { v := rand.NormFloat64() vals[j] = v allVars[i*mutations+j] = v sampleSum += v } go func(vals []float64) { start.Wait() for _, v := range vals { sum.Observe(v) } end.Done() }(vals) } sort.Float64s(allVars) start.Done() end.Wait() m := &dto.Metric{} sum.Write(m) if got, want := int(*m.Histogram.SampleCount), total; got != want { t.Errorf("got sample count %d, want %d", got, want) } if got, want := *m.Histogram.SampleSum, sampleSum; math.Abs((got-want)/want) > 0.001 { t.Errorf("got sample sum %f, want %f", got, want) } wantCounts := getCumulativeCounts(allVars) if got, want := len(m.Histogram.Bucket), len(testBuckets)-1; got != want { t.Errorf("got %d buckets in protobuf, want %d", got, want) } for i, wantBound := range testBuckets { if i == len(testBuckets)-1 { break // No +Inf bucket in protobuf. } if gotBound := *m.Histogram.Bucket[i].UpperBound; gotBound != wantBound { t.Errorf("got bound %f, want %f", gotBound, wantBound) } if gotCount, wantCount := *m.Histogram.Bucket[i].CumulativeCount, wantCounts[i]; gotCount != wantCount { t.Errorf("got count %d, want %d", gotCount, wantCount) } } return true } if err := quick.Check(it, nil); err != nil { t.Error(err) } } func TestHistogramVecConcurrency(t *testing.T) { rand.Seed(42) objectives := make([]float64, 0, len(DefObjectives)) for qu := range DefObjectives { objectives = append(objectives, qu) } sort.Float64s(objectives) it := func(n uint32) bool { mutations := int(n%1e4 + 1e4) concLevel := int(n%7 + 1) vecLength := int(n%3 + 1) var start, end sync.WaitGroup start.Add(1) end.Add(concLevel) his := NewHistogramVec( HistogramOpts{ Name: "test_histogram", Help: "helpless", Buckets: []float64{-2, -1, -0.5, 0, 0.5, 1, 2, math.Inf(+1)}, }, []string{"label"}, ) allVars := make([][]float64, vecLength) sampleSums := make([]float64, vecLength) for i := 0; i < concLevel; i++ { vals := make([]float64, mutations) picks := make([]int, mutations) for j := 0; j < mutations; j++ { v := rand.NormFloat64() vals[j] = v pick := rand.Intn(vecLength) picks[j] = pick allVars[pick] = append(allVars[pick], v) sampleSums[pick] += v } go func(vals []float64) { start.Wait() for i, v := range vals { his.WithLabelValues(string('A' + picks[i])).Observe(v) } end.Done() }(vals) } for _, vars := range allVars { sort.Float64s(vars) } start.Done() end.Wait() for i := 0; i < vecLength; i++ { m := &dto.Metric{} s := his.WithLabelValues(string('A' + i)) s.Write(m) if got, want := len(m.Histogram.Bucket), len(testBuckets)-1; got != want { t.Errorf("got %d buckets in protobuf, want %d", got, want) } if got, want := int(*m.Histogram.SampleCount), len(allVars[i]); got != want { t.Errorf("got sample count %d, want %d", got, want) } if got, want := *m.Histogram.SampleSum, sampleSums[i]; math.Abs((got-want)/want) > 0.001 { t.Errorf("got sample sum %f, want %f", got, want) } wantCounts := getCumulativeCounts(allVars[i]) for j, wantBound := range testBuckets { if j == len(testBuckets)-1 { break // No +Inf bucket in protobuf. } if gotBound := *m.Histogram.Bucket[j].UpperBound; gotBound != wantBound { t.Errorf("got bound %f, want %f", gotBound, wantBound) } if gotCount, wantCount := *m.Histogram.Bucket[j].CumulativeCount, wantCounts[j]; gotCount != wantCount { t.Errorf("got count %d, want %d", gotCount, wantCount) } } } return true } if err := quick.Check(it, nil); err != nil { t.Error(err) } } func getCumulativeCounts(vars []float64) []uint64 { counts := make([]uint64, len(testBuckets)) for _, v := range vars { for i := len(testBuckets) - 1; i >= 0; i-- { if v > testBuckets[i] { break } counts[i]++ } } return counts } func TestBuckets(t *testing.T) { got := LinearBuckets(-15, 5, 6) want := []float64{-15, -10, -5, 0, 5, 10} if !reflect.DeepEqual(got, want) { t.Errorf("linear buckets: got %v, want %v", got, want) } got = ExponentialBuckets(100, 1.2, 3) want = []float64{100, 120, 144} if !reflect.DeepEqual(got, want) { t.Errorf("linear buckets: got %v, want %v", got, want) } } golang-prometheus-client-0.7.0+ds/prometheus/http.go000066400000000000000000000212401256572052500225450ustar00rootroot00000000000000// Copyright 2014 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package prometheus import ( "bufio" "io" "net" "net/http" "strconv" "strings" "time" ) var instLabels = []string{"method", "code"} type nower interface { Now() time.Time } type nowFunc func() time.Time func (n nowFunc) Now() time.Time { return n() } var now nower = nowFunc(func() time.Time { return time.Now() }) func nowSeries(t ...time.Time) nower { return nowFunc(func() time.Time { defer func() { t = t[1:] }() return t[0] }) } // InstrumentHandler wraps the given HTTP handler for instrumentation. It // registers four metric collectors (if not already done) and reports HTTP // metrics to the (newly or already) registered collectors: http_requests_total // (CounterVec), http_request_duration_microseconds (Summary), // http_request_size_bytes (Summary), http_response_size_bytes (Summary). Each // has a constant label named "handler" with the provided handlerName as // value. http_requests_total is a metric vector partitioned by HTTP method // (label name "method") and HTTP status code (label name "code"). func InstrumentHandler(handlerName string, handler http.Handler) http.HandlerFunc { return InstrumentHandlerFunc(handlerName, handler.ServeHTTP) } // InstrumentHandlerFunc wraps the given function for instrumentation. It // otherwise works in the same way as InstrumentHandler. func InstrumentHandlerFunc(handlerName string, handlerFunc func(http.ResponseWriter, *http.Request)) http.HandlerFunc { return InstrumentHandlerFuncWithOpts( SummaryOpts{ Subsystem: "http", ConstLabels: Labels{"handler": handlerName}, }, handlerFunc, ) } // InstrumentHandlerWithOpts works like InstrumentHandler but provides more // flexibility (at the cost of a more complex call syntax). As // InstrumentHandler, this function registers four metric collectors, but it // uses the provided SummaryOpts to create them. However, the fields "Name" and // "Help" in the SummaryOpts are ignored. "Name" is replaced by // "requests_total", "request_duration_microseconds", "request_size_bytes", and // "response_size_bytes", respectively. "Help" is replaced by an appropriate // help string. The names of the variable labels of the http_requests_total // CounterVec are "method" (get, post, etc.), and "code" (HTTP status code). // // If InstrumentHandlerWithOpts is called as follows, it mimics exactly the // behavior of InstrumentHandler: // // prometheus.InstrumentHandlerWithOpts( // prometheus.SummaryOpts{ // Subsystem: "http", // ConstLabels: prometheus.Labels{"handler": handlerName}, // }, // handler, // ) // // Technical detail: "requests_total" is a CounterVec, not a SummaryVec, so it // cannot use SummaryOpts. Instead, a CounterOpts struct is created internally, // and all its fields are set to the equally named fields in the provided // SummaryOpts. func InstrumentHandlerWithOpts(opts SummaryOpts, handler http.Handler) http.HandlerFunc { return InstrumentHandlerFuncWithOpts(opts, handler.ServeHTTP) } // InstrumentHandlerFuncWithOpts works like InstrumentHandlerFunc but provides // more flexibility (at the cost of a more complex call syntax). See // InstrumentHandlerWithOpts for details how the provided SummaryOpts are used. func InstrumentHandlerFuncWithOpts(opts SummaryOpts, handlerFunc func(http.ResponseWriter, *http.Request)) http.HandlerFunc { reqCnt := NewCounterVec( CounterOpts{ Namespace: opts.Namespace, Subsystem: opts.Subsystem, Name: "requests_total", Help: "Total number of HTTP requests made.", ConstLabels: opts.ConstLabels, }, instLabels, ) opts.Name = "request_duration_microseconds" opts.Help = "The HTTP request latencies in microseconds." reqDur := NewSummary(opts) opts.Name = "request_size_bytes" opts.Help = "The HTTP request sizes in bytes." reqSz := NewSummary(opts) opts.Name = "response_size_bytes" opts.Help = "The HTTP response sizes in bytes." resSz := NewSummary(opts) regReqCnt := MustRegisterOrGet(reqCnt).(*CounterVec) regReqDur := MustRegisterOrGet(reqDur).(Summary) regReqSz := MustRegisterOrGet(reqSz).(Summary) regResSz := MustRegisterOrGet(resSz).(Summary) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { now := time.Now() delegate := &responseWriterDelegator{ResponseWriter: w} out := make(chan int) urlLen := 0 if r.URL != nil { urlLen = len(r.URL.String()) } go computeApproximateRequestSize(r, out, urlLen) _, cn := w.(http.CloseNotifier) _, fl := w.(http.Flusher) _, hj := w.(http.Hijacker) _, rf := w.(io.ReaderFrom) var rw http.ResponseWriter if cn && fl && hj && rf { rw = &fancyResponseWriterDelegator{delegate} } else { rw = delegate } handlerFunc(rw, r) elapsed := float64(time.Since(now)) / float64(time.Microsecond) method := sanitizeMethod(r.Method) code := sanitizeCode(delegate.status) regReqCnt.WithLabelValues(method, code).Inc() regReqDur.Observe(elapsed) regResSz.Observe(float64(delegate.written)) regReqSz.Observe(float64(<-out)) }) } func computeApproximateRequestSize(r *http.Request, out chan int, s int) { s += len(r.Method) s += len(r.Proto) for name, values := range r.Header { s += len(name) for _, value := range values { s += len(value) } } s += len(r.Host) // N.B. r.Form and r.MultipartForm are assumed to be included in r.URL. if r.ContentLength != -1 { s += int(r.ContentLength) } out <- s } type responseWriterDelegator struct { http.ResponseWriter handler, method string status int written int64 wroteHeader bool } func (r *responseWriterDelegator) WriteHeader(code int) { r.status = code r.wroteHeader = true r.ResponseWriter.WriteHeader(code) } func (r *responseWriterDelegator) Write(b []byte) (int, error) { if !r.wroteHeader { r.WriteHeader(http.StatusOK) } n, err := r.ResponseWriter.Write(b) r.written += int64(n) return n, err } type fancyResponseWriterDelegator struct { *responseWriterDelegator } func (f *fancyResponseWriterDelegator) CloseNotify() <-chan bool { return f.ResponseWriter.(http.CloseNotifier).CloseNotify() } func (f *fancyResponseWriterDelegator) Flush() { f.ResponseWriter.(http.Flusher).Flush() } func (f *fancyResponseWriterDelegator) Hijack() (net.Conn, *bufio.ReadWriter, error) { return f.ResponseWriter.(http.Hijacker).Hijack() } func (f *fancyResponseWriterDelegator) ReadFrom(r io.Reader) (int64, error) { if !f.wroteHeader { f.WriteHeader(http.StatusOK) } n, err := f.ResponseWriter.(io.ReaderFrom).ReadFrom(r) f.written += n return n, err } func sanitizeMethod(m string) string { switch m { case "GET", "get": return "get" case "PUT", "put": return "put" case "HEAD", "head": return "head" case "POST", "post": return "post" case "DELETE", "delete": return "delete" case "CONNECT", "connect": return "connect" case "OPTIONS", "options": return "options" case "NOTIFY", "notify": return "notify" default: return strings.ToLower(m) } } func sanitizeCode(s int) string { switch s { case 100: return "100" case 101: return "101" case 200: return "200" case 201: return "201" case 202: return "202" case 203: return "203" case 204: return "204" case 205: return "205" case 206: return "206" case 300: return "300" case 301: return "301" case 302: return "302" case 304: return "304" case 305: return "305" case 307: return "307" case 400: return "400" case 401: return "401" case 402: return "402" case 403: return "403" case 404: return "404" case 405: return "405" case 406: return "406" case 407: return "407" case 408: return "408" case 409: return "409" case 410: return "410" case 411: return "411" case 412: return "412" case 413: return "413" case 414: return "414" case 415: return "415" case 416: return "416" case 417: return "417" case 418: return "418" case 500: return "500" case 501: return "501" case 502: return "502" case 503: return "503" case 504: return "504" case 505: return "505" case 428: return "428" case 429: return "429" case 431: return "431" case 511: return "511" default: return strconv.Itoa(s) } } golang-prometheus-client-0.7.0+ds/prometheus/http_test.go000066400000000000000000000065771256572052500236240ustar00rootroot00000000000000// Copyright 2014 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package prometheus import ( "net/http" "net/http/httptest" "testing" "time" dto "github.com/prometheus/client_model/go" ) type respBody string func (b respBody) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusTeapot) w.Write([]byte(b)) } func TestInstrumentHandler(t *testing.T) { defer func(n nower) { now = n.(nower) }(now) instant := time.Now() end := instant.Add(30 * time.Second) now = nowSeries(instant, end) respBody := respBody("Howdy there!") hndlr := InstrumentHandler("test-handler", respBody) opts := SummaryOpts{ Subsystem: "http", ConstLabels: Labels{"handler": "test-handler"}, } reqCnt := MustRegisterOrGet(NewCounterVec( CounterOpts{ Namespace: opts.Namespace, Subsystem: opts.Subsystem, Name: "requests_total", Help: "Total number of HTTP requests made.", ConstLabels: opts.ConstLabels, }, instLabels, )).(*CounterVec) opts.Name = "request_duration_microseconds" opts.Help = "The HTTP request latencies in microseconds." reqDur := MustRegisterOrGet(NewSummary(opts)).(Summary) opts.Name = "request_size_bytes" opts.Help = "The HTTP request sizes in bytes." MustRegisterOrGet(NewSummary(opts)) opts.Name = "response_size_bytes" opts.Help = "The HTTP response sizes in bytes." MustRegisterOrGet(NewSummary(opts)) reqCnt.Reset() resp := httptest.NewRecorder() req := &http.Request{ Method: "GET", } hndlr.ServeHTTP(resp, req) if resp.Code != http.StatusTeapot { t.Fatalf("expected status %d, got %d", http.StatusTeapot, resp.Code) } if string(resp.Body.Bytes()) != "Howdy there!" { t.Fatalf("expected body %s, got %s", "Howdy there!", string(resp.Body.Bytes())) } out := &dto.Metric{} reqDur.Write(out) if want, got := "test-handler", out.Label[0].GetValue(); want != got { t.Errorf("want label value %q in reqDur, got %q", want, got) } if want, got := uint64(1), out.Summary.GetSampleCount(); want != got { t.Errorf("want sample count %d in reqDur, got %d", want, got) } out.Reset() if want, got := 1, len(reqCnt.children); want != got { t.Errorf("want %d children in reqCnt, got %d", want, got) } cnt, err := reqCnt.GetMetricWithLabelValues("get", "418") if err != nil { t.Fatal(err) } cnt.Write(out) if want, got := "418", out.Label[0].GetValue(); want != got { t.Errorf("want label value %q in reqCnt, got %q", want, got) } if want, got := "test-handler", out.Label[1].GetValue(); want != got { t.Errorf("want label value %q in reqCnt, got %q", want, got) } if want, got := "get", out.Label[2].GetValue(); want != got { t.Errorf("want label value %q in reqCnt, got %q", want, got) } if out.Counter == nil { t.Fatal("expected non-nil counter in reqCnt") } if want, got := 1., out.Counter.GetValue(); want != got { t.Errorf("want reqCnt of %f, got %f", want, got) } } golang-prometheus-client-0.7.0+ds/prometheus/metric.go000066400000000000000000000140751256572052500230610ustar00rootroot00000000000000// Copyright 2014 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package prometheus import ( "strings" dto "github.com/prometheus/client_model/go" ) // A Metric models a single sample value with its meta data being exported to // Prometheus. Implementers of Metric in this package inclued Gauge, Counter, // Untyped, and Summary. Users can implement their own Metric types, but that // should be rarely needed. See the example for SelfCollector, which is also an // example for a user-implemented Metric. type Metric interface { // Desc returns the descriptor for the Metric. This method idempotently // returns the same descriptor throughout the lifetime of the // Metric. The returned descriptor is immutable by contract. A Metric // unable to describe itself must return an invalid descriptor (created // with NewInvalidDesc). Desc() *Desc // Write encodes the Metric into a "Metric" Protocol Buffer data // transmission object. // // Implementers of custom Metric types must observe concurrency safety // as reads of this metric may occur at any time, and any blocking // occurs at the expense of total performance of rendering all // registered metrics. Ideally Metric implementations should support // concurrent readers. // // The Prometheus client library attempts to minimize memory allocations // and will provide a pre-existing reset dto.Metric pointer. Prometheus // may recycle the dto.Metric proto message, so Metric implementations // should just populate the provided dto.Metric and then should not keep // any reference to it. // // While populating dto.Metric, labels must be sorted lexicographically. // (Implementers may find LabelPairSorter useful for that.) Write(*dto.Metric) error } // Opts bundles the options for creating most Metric types. Each metric // implementation XXX has its own XXXOpts type, but in most cases, it is just be // an alias of this type (which might change when the requirement arises.) // // It is mandatory to set Name and Help to a non-empty string. All other fields // are optional and can safely be left at their zero value. type Opts struct { // Namespace, Subsystem, and Name are components of the fully-qualified // name of the Metric (created by joining these components with // "_"). Only Name is mandatory, the others merely help structuring the // name. Note that the fully-qualified name of the metric must be a // valid Prometheus metric name. Namespace string Subsystem string Name string // Help provides information about this metric. Mandatory! // // Metrics with the same fully-qualified name must have the same Help // string. Help string // ConstLabels are used to attach fixed labels to this metric. Metrics // with the same fully-qualified name must have the same label names in // their ConstLabels. // // Note that in most cases, labels have a value that varies during the // lifetime of a process. Those labels are usually managed with a metric // vector collector (like CounterVec, GaugeVec, UntypedVec). ConstLabels // serve only special purposes. One is for the special case where the // value of a label does not change during the lifetime of a process, // e.g. if the revision of the running binary is put into a // label. Another, more advanced purpose is if more than one Collector // needs to collect Metrics with the same fully-qualified name. In that // case, those Metrics must differ in the values of their // ConstLabels. See the Collector examples. // // If the value of a label never changes (not even between binaries), // that label most likely should not be a label at all (but part of the // metric name). ConstLabels Labels } // BuildFQName joins the given three name components by "_". Empty name // components are ignored. If the name parameter itself is empty, an empty // string is returned, no matter what. Metric implementations included in this // library use this function internally to generate the fully-qualified metric // name from the name component in their Opts. Users of the library will only // need this function if they implement their own Metric or instantiate a Desc // (with NewDesc) directly. func BuildFQName(namespace, subsystem, name string) string { if name == "" { return "" } switch { case namespace != "" && subsystem != "": return strings.Join([]string{namespace, subsystem, name}, "_") case namespace != "": return strings.Join([]string{namespace, name}, "_") case subsystem != "": return strings.Join([]string{subsystem, name}, "_") } return name } // LabelPairSorter implements sort.Interface. It is used to sort a slice of // dto.LabelPair pointers. This is useful for implementing the Write method of // custom metrics. type LabelPairSorter []*dto.LabelPair func (s LabelPairSorter) Len() int { return len(s) } func (s LabelPairSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s LabelPairSorter) Less(i, j int) bool { return s[i].GetName() < s[j].GetName() } type hashSorter []uint64 func (s hashSorter) Len() int { return len(s) } func (s hashSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s hashSorter) Less(i, j int) bool { return s[i] < s[j] } type invalidMetric struct { desc *Desc err error } // NewInvalidMetric returns a metric whose Write method always returns the // provided error. It is useful if a Collector finds itself unable to collect // a metric and wishes to report an error to the registry. func NewInvalidMetric(desc *Desc, err error) Metric { return &invalidMetric{desc, err} } func (m *invalidMetric) Desc() *Desc { return m.desc } func (m *invalidMetric) Write(*dto.Metric) error { return m.err } golang-prometheus-client-0.7.0+ds/prometheus/metric_test.go000066400000000000000000000021201256572052500241040ustar00rootroot00000000000000// Copyright 2014 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package prometheus import "testing" func TestBuildFQName(t *testing.T) { scenarios := []struct{ namespace, subsystem, name, result string }{ {"a", "b", "c", "a_b_c"}, {"", "b", "c", "b_c"}, {"a", "", "c", "a_c"}, {"", "", "c", "c"}, {"a", "b", "", ""}, {"a", "", "", ""}, {"", "b", "", ""}, {" ", "", "", ""}, } for i, s := range scenarios { if want, got := s.result, BuildFQName(s.namespace, s.subsystem, s.name); want != got { t.Errorf("%d. want %s, got %s", i, want, got) } } } golang-prometheus-client-0.7.0+ds/prometheus/process_collector.go000066400000000000000000000100751256572052500253160ustar00rootroot00000000000000// Copyright 2015 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package prometheus import "github.com/prometheus/procfs" type processCollector struct { pid int collectFn func(chan<- Metric) pidFn func() (int, error) cpuTotal Counter openFDs, maxFDs Gauge vsize, rss Gauge startTime Gauge } // NewProcessCollector returns a collector which exports the current state of // process metrics including cpu, memory and file descriptor usage as well as // the process start time for the given process id under the given namespace. func NewProcessCollector(pid int, namespace string) *processCollector { return NewProcessCollectorPIDFn( func() (int, error) { return pid, nil }, namespace, ) } // NewProcessCollectorPIDFn returns a collector which exports the current state // of process metrics including cpu, memory and file descriptor usage as well // as the process start time under the given namespace. The given pidFn is // called on each collect and is used to determine the process to export // metrics for. func NewProcessCollectorPIDFn( pidFn func() (int, error), namespace string, ) *processCollector { c := processCollector{ pidFn: pidFn, collectFn: func(chan<- Metric) {}, cpuTotal: NewCounter(CounterOpts{ Namespace: namespace, Name: "process_cpu_seconds_total", Help: "Total user and system CPU time spent in seconds.", }), openFDs: NewGauge(GaugeOpts{ Namespace: namespace, Name: "process_open_fds", Help: "Number of open file descriptors.", }), maxFDs: NewGauge(GaugeOpts{ Namespace: namespace, Name: "process_max_fds", Help: "Maximum number of open file descriptors.", }), vsize: NewGauge(GaugeOpts{ Namespace: namespace, Name: "process_virtual_memory_bytes", Help: "Virtual memory size in bytes.", }), rss: NewGauge(GaugeOpts{ Namespace: namespace, Name: "process_resident_memory_bytes", Help: "Resident memory size in bytes.", }), startTime: NewGauge(GaugeOpts{ Namespace: namespace, Name: "process_start_time_seconds", Help: "Start time of the process since unix epoch in seconds.", }), } // Set up process metric collection if supported by the runtime. if _, err := procfs.NewStat(); err == nil { c.collectFn = c.processCollect } return &c } // Describe returns all descriptions of the collector. func (c *processCollector) Describe(ch chan<- *Desc) { ch <- c.cpuTotal.Desc() ch <- c.openFDs.Desc() ch <- c.maxFDs.Desc() ch <- c.vsize.Desc() ch <- c.rss.Desc() ch <- c.startTime.Desc() } // Collect returns the current state of all metrics of the collector. func (c *processCollector) Collect(ch chan<- Metric) { c.collectFn(ch) } // TODO(ts): Bring back error reporting by reverting 7faf9e7 as soon as the // client allows users to configure the error behavior. func (c *processCollector) processCollect(ch chan<- Metric) { pid, err := c.pidFn() if err != nil { return } p, err := procfs.NewProc(pid) if err != nil { return } if stat, err := p.NewStat(); err == nil { c.cpuTotal.Set(stat.CPUTime()) ch <- c.cpuTotal c.vsize.Set(float64(stat.VirtualMemory())) ch <- c.vsize c.rss.Set(float64(stat.ResidentMemory())) ch <- c.rss if startTime, err := stat.StartTime(); err == nil { c.startTime.Set(startTime) ch <- c.startTime } } if fds, err := p.FileDescriptorsLen(); err == nil { c.openFDs.Set(float64(fds)) ch <- c.openFDs } if limits, err := p.NewLimits(); err == nil { c.maxFDs.Set(float64(limits.OpenFiles)) ch <- c.maxFDs } } golang-prometheus-client-0.7.0+ds/prometheus/process_collector_test.go000066400000000000000000000030601256572052500263510ustar00rootroot00000000000000package prometheus import ( "io/ioutil" "net/http" "net/http/httptest" "os" "regexp" "testing" "github.com/prometheus/procfs" ) func TestProcessCollector(t *testing.T) { if _, err := procfs.Self(); err != nil { t.Skipf("skipping TestProcessCollector, procfs not available: %s", err) } registry := newRegistry() registry.Register(NewProcessCollector(os.Getpid(), "")) registry.Register(NewProcessCollectorPIDFn( func() (int, error) { return os.Getpid(), nil }, "foobar")) s := httptest.NewServer(InstrumentHandler("prometheus", registry)) defer s.Close() r, err := http.Get(s.URL) if err != nil { t.Fatal(err) } defer r.Body.Close() body, err := ioutil.ReadAll(r.Body) if err != nil { t.Fatal(err) } for _, re := range []*regexp.Regexp{ regexp.MustCompile("process_cpu_seconds_total [0-9]"), regexp.MustCompile("process_max_fds [0-9]{2,}"), regexp.MustCompile("process_open_fds [1-9]"), regexp.MustCompile("process_virtual_memory_bytes [1-9]"), regexp.MustCompile("process_resident_memory_bytes [1-9]"), regexp.MustCompile("process_start_time_seconds [0-9.]{10,}"), regexp.MustCompile("foobar_process_cpu_seconds_total [0-9]"), regexp.MustCompile("foobar_process_max_fds [0-9]{2,}"), regexp.MustCompile("foobar_process_open_fds [1-9]"), regexp.MustCompile("foobar_process_virtual_memory_bytes [1-9]"), regexp.MustCompile("foobar_process_resident_memory_bytes [1-9]"), regexp.MustCompile("foobar_process_start_time_seconds [0-9.]{10,}"), } { if !re.Match(body) { t.Errorf("want body to match %s\n%s", re, body) } } } golang-prometheus-client-0.7.0+ds/prometheus/push.go000066400000000000000000000053651256572052500225570ustar00rootroot00000000000000// Copyright 2015 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Copyright (c) 2013, The Prometheus Authors // All rights reserved. // // Use of this source code is governed by a BSD-style license that can be found // in the LICENSE file. package prometheus // Push triggers a metric collection by the default registry and pushes all // collected metrics to the Pushgateway specified by addr. See the Pushgateway // documentation for detailed implications of the job and instance // parameter. instance can be left empty. You can use just host:port or ip:port // as url, in which case 'http://' is added automatically. You can also include // the schema in the URL. However, do not include the '/metrics/jobs/...' part. // // Note that all previously pushed metrics with the same job and instance will // be replaced with the metrics pushed by this call. (It uses HTTP method 'PUT' // to push to the Pushgateway.) func Push(job, instance, url string) error { return defRegistry.Push(job, instance, url, "PUT") } // PushAdd works like Push, but only previously pushed metrics with the same // name (and the same job and instance) will be replaced. (It uses HTTP method // 'POST' to push to the Pushgateway.) func PushAdd(job, instance, url string) error { return defRegistry.Push(job, instance, url, "POST") } // PushCollectors works like Push, but it does not collect from the default // registry. Instead, it collects from the provided collectors. It is a // convenient way to push only a few metrics. func PushCollectors(job, instance, url string, collectors ...Collector) error { return pushCollectors(job, instance, url, "PUT", collectors...) } // PushAddCollectors works like PushAdd, but it does not collect from the // default registry. Instead, it collects from the provided collectors. It is a // convenient way to push only a few metrics. func PushAddCollectors(job, instance, url string, collectors ...Collector) error { return pushCollectors(job, instance, url, "POST", collectors...) } func pushCollectors(job, instance, url, method string, collectors ...Collector) error { r := newRegistry() for _, collector := range collectors { if _, err := r.Register(collector); err != nil { return err } } return r.Push(job, instance, url, method) } golang-prometheus-client-0.7.0+ds/prometheus/registry.go000066400000000000000000000577121256572052500234530ustar00rootroot00000000000000// Copyright 2014 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Copyright (c) 2013, The Prometheus Authors // All rights reserved. // // Use of this source code is governed by a BSD-style license that can be found // in the LICENSE file. package prometheus import ( "bytes" "compress/gzip" "errors" "fmt" "hash/fnv" "io" "net/http" "net/url" "os" "sort" "strings" "sync" "bitbucket.org/ww/goautoneg" "github.com/golang/protobuf/proto" dto "github.com/prometheus/client_model/go" "github.com/prometheus/client_golang/model" "github.com/prometheus/client_golang/text" ) var ( defRegistry = newDefaultRegistry() errAlreadyReg = errors.New("duplicate metrics collector registration attempted") ) // Constants relevant to the HTTP interface. const ( // APIVersion is the version of the format of the exported data. This // will match this library's version, which subscribes to the Semantic // Versioning scheme. APIVersion = "0.0.4" // DelimitedTelemetryContentType is the content type set on telemetry // data responses in delimited protobuf format. DelimitedTelemetryContentType = `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited` // TextTelemetryContentType is the content type set on telemetry data // responses in text format. TextTelemetryContentType = `text/plain; version=` + APIVersion // ProtoTextTelemetryContentType is the content type set on telemetry // data responses in protobuf text format. (Only used for debugging.) ProtoTextTelemetryContentType = `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=text` // ProtoCompactTextTelemetryContentType is the content type set on // telemetry data responses in protobuf compact text format. (Only used // for debugging.) ProtoCompactTextTelemetryContentType = `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=compact-text` // Constants for object pools. numBufs = 4 numMetricFamilies = 1000 numMetrics = 10000 // Capacity for the channel to collect metrics and descriptors. capMetricChan = 1000 capDescChan = 10 contentTypeHeader = "Content-Type" contentLengthHeader = "Content-Length" contentEncodingHeader = "Content-Encoding" acceptEncodingHeader = "Accept-Encoding" acceptHeader = "Accept" ) // Handler returns the HTTP handler for the global Prometheus registry. It is // already instrumented with InstrumentHandler (using "prometheus" as handler // name). Usually the handler is used to handle the "/metrics" endpoint. func Handler() http.Handler { return InstrumentHandler("prometheus", defRegistry) } // UninstrumentedHandler works in the same way as Handler, but the returned HTTP // handler is not instrumented. This is useful if no instrumentation is desired // (for whatever reason) or if the instrumentation has to happen with a // different handler name (or with a different instrumentation approach // altogether). See the InstrumentHandler example. func UninstrumentedHandler() http.Handler { return defRegistry } // Register registers a new Collector to be included in metrics collection. It // returns an error if the descriptors provided by the Collector are invalid or // if they - in combination with descriptors of already registered Collectors - // do not fulfill the consistency and uniqueness criteria described in the Desc // documentation. // // Do not register the same Collector multiple times concurrently. (Registering // the same Collector twice would result in an error anyway, but on top of that, // it is not safe to do so concurrently.) func Register(m Collector) error { _, err := defRegistry.Register(m) return err } // MustRegister works like Register but panics where Register would have // returned an error. func MustRegister(m Collector) { err := Register(m) if err != nil { panic(err) } } // RegisterOrGet works like Register but does not return an error if a Collector // is registered that equals a previously registered Collector. (Two Collectors // are considered equal if their Describe method yields the same set of // descriptors.) Instead, the previously registered Collector is returned (which // is helpful if the new and previously registered Collectors are equal but not // identical, i.e. not pointers to the same object). // // As for Register, it is still not safe to call RegisterOrGet with the same // Collector multiple times concurrently. func RegisterOrGet(m Collector) (Collector, error) { return defRegistry.RegisterOrGet(m) } // MustRegisterOrGet works like Register but panics where RegisterOrGet would // have returned an error. func MustRegisterOrGet(m Collector) Collector { existing, err := RegisterOrGet(m) if err != nil { panic(err) } return existing } // Unregister unregisters the Collector that equals the Collector passed in as // an argument. (Two Collectors are considered equal if their Describe method // yields the same set of descriptors.) The function returns whether a Collector // was unregistered. func Unregister(c Collector) bool { return defRegistry.Unregister(c) } // SetMetricFamilyInjectionHook sets a function that is called whenever metrics // are collected. The hook function must be set before metrics collection begins // (i.e. call SetMetricFamilyInjectionHook before setting the HTTP handler.) The // MetricFamily protobufs returned by the hook function are merged with the // metrics collected in the usual way. // // This is a way to directly inject MetricFamily protobufs managed and owned by // the caller. The caller has full responsibility. As no registration of the // injected metrics has happened, there is no descriptor to check against, and // there are no registration-time checks. If collect-time checks are disabled // (see function EnableCollectChecks), no sanity checks are performed on the // returned protobufs at all. If collect-checks are enabled, type and uniqueness // checks are performed, but no further consistency checks (which would require // knowledge of a metric descriptor). // // Sorting concerns: The caller is responsible for sorting the label pairs in // each metric. However, the order of metrics will be sorted by the registry as // it is required anyway after merging with the metric families collected // conventionally. // // The function must be callable at any time and concurrently. func SetMetricFamilyInjectionHook(hook func() []*dto.MetricFamily) { defRegistry.metricFamilyInjectionHook = hook } // PanicOnCollectError sets the behavior whether a panic is caused upon an error // while metrics are collected and served to the HTTP endpoint. By default, an // internal server error (status code 500) is served with an error message. func PanicOnCollectError(b bool) { defRegistry.panicOnCollectError = b } // EnableCollectChecks enables (or disables) additional consistency checks // during metrics collection. These additional checks are not enabled by default // because they inflict a performance penalty and the errors they check for can // only happen if the used Metric and Collector types have internal programming // errors. It can be helpful to enable these checks while working with custom // Collectors or Metrics whose correctness is not well established yet. func EnableCollectChecks(b bool) { defRegistry.collectChecksEnabled = b } // encoder is a function that writes a dto.MetricFamily to an io.Writer in a // certain encoding. It returns the number of bytes written and any error // encountered. Note that pbutil.WriteDelimited and pbutil.MetricFamilyToText // are encoders. type encoder func(io.Writer, *dto.MetricFamily) (int, error) type registry struct { mtx sync.RWMutex collectorsByID map[uint64]Collector // ID is a hash of the descIDs. descIDs map[uint64]struct{} dimHashesByName map[string]uint64 bufPool chan *bytes.Buffer metricFamilyPool chan *dto.MetricFamily metricPool chan *dto.Metric metricFamilyInjectionHook func() []*dto.MetricFamily panicOnCollectError, collectChecksEnabled bool } func (r *registry) Register(c Collector) (Collector, error) { descChan := make(chan *Desc, capDescChan) go func() { c.Describe(descChan) close(descChan) }() newDescIDs := map[uint64]struct{}{} newDimHashesByName := map[string]uint64{} var collectorID uint64 // Just a sum of all desc IDs. var duplicateDescErr error r.mtx.Lock() defer r.mtx.Unlock() // Coduct various tests... for desc := range descChan { // Is the descriptor valid at all? if desc.err != nil { return c, fmt.Errorf("descriptor %s is invalid: %s", desc, desc.err) } // Is the descID unique? // (In other words: Is the fqName + constLabel combination unique?) if _, exists := r.descIDs[desc.id]; exists { duplicateDescErr = fmt.Errorf("descriptor %s already exists with the same fully-qualified name and const label values", desc) } // If it is not a duplicate desc in this collector, add it to // the collectorID. (We allow duplicate descs within the same // collector, but their existence must be a no-op.) if _, exists := newDescIDs[desc.id]; !exists { newDescIDs[desc.id] = struct{}{} collectorID += desc.id } // Are all the label names and the help string consistent with // previous descriptors of the same name? // First check existing descriptors... if dimHash, exists := r.dimHashesByName[desc.fqName]; exists { if dimHash != desc.dimHash { return nil, fmt.Errorf("a previously registered descriptor with the same fully-qualified name as %s has different label names or a different help string", desc) } } else { // ...then check the new descriptors already seen. if dimHash, exists := newDimHashesByName[desc.fqName]; exists { if dimHash != desc.dimHash { return nil, fmt.Errorf("descriptors reported by collector have inconsistent label names or help strings for the same fully-qualified name, offender is %s", desc) } } else { newDimHashesByName[desc.fqName] = desc.dimHash } } } // Did anything happen at all? if len(newDescIDs) == 0 { return nil, errors.New("collector has no descriptors") } if existing, exists := r.collectorsByID[collectorID]; exists { return existing, errAlreadyReg } // If the collectorID is new, but at least one of the descs existed // before, we are in trouble. if duplicateDescErr != nil { return nil, duplicateDescErr } // Only after all tests have passed, actually register. r.collectorsByID[collectorID] = c for hash := range newDescIDs { r.descIDs[hash] = struct{}{} } for name, dimHash := range newDimHashesByName { r.dimHashesByName[name] = dimHash } return c, nil } func (r *registry) RegisterOrGet(m Collector) (Collector, error) { existing, err := r.Register(m) if err != nil && err != errAlreadyReg { return nil, err } return existing, nil } func (r *registry) Unregister(c Collector) bool { descChan := make(chan *Desc, capDescChan) go func() { c.Describe(descChan) close(descChan) }() descIDs := map[uint64]struct{}{} var collectorID uint64 // Just a sum of the desc IDs. for desc := range descChan { if _, exists := descIDs[desc.id]; !exists { collectorID += desc.id descIDs[desc.id] = struct{}{} } } r.mtx.RLock() if _, exists := r.collectorsByID[collectorID]; !exists { r.mtx.RUnlock() return false } r.mtx.RUnlock() r.mtx.Lock() defer r.mtx.Unlock() delete(r.collectorsByID, collectorID) for id := range descIDs { delete(r.descIDs, id) } // dimHashesByName is left untouched as those must be consistent // throughout the lifetime of a program. return true } func (r *registry) Push(job, instance, pushURL, method string) error { if !strings.Contains(pushURL, "://") { pushURL = "http://" + pushURL } pushURL = fmt.Sprintf("%s/metrics/jobs/%s", pushURL, url.QueryEscape(job)) if instance != "" { pushURL += "/instances/" + url.QueryEscape(instance) } buf := r.getBuf() defer r.giveBuf(buf) if _, err := r.writePB(buf, text.WriteProtoDelimited); err != nil { if r.panicOnCollectError { panic(err) } return err } req, err := http.NewRequest(method, pushURL, buf) if err != nil { return err } req.Header.Set(contentTypeHeader, DelimitedTelemetryContentType) resp, err := http.DefaultClient.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != 202 { return fmt.Errorf("unexpected status code %d while pushing to %s", resp.StatusCode, pushURL) } return nil } func (r *registry) ServeHTTP(w http.ResponseWriter, req *http.Request) { enc, contentType := chooseEncoder(req) buf := r.getBuf() defer r.giveBuf(buf) writer, encoding := decorateWriter(req, buf) if _, err := r.writePB(writer, enc); err != nil { if r.panicOnCollectError { panic(err) } http.Error(w, "An error has occurred:\n\n"+err.Error(), http.StatusInternalServerError) return } if closer, ok := writer.(io.Closer); ok { closer.Close() } header := w.Header() header.Set(contentTypeHeader, contentType) header.Set(contentLengthHeader, fmt.Sprint(buf.Len())) if encoding != "" { header.Set(contentEncodingHeader, encoding) } w.Write(buf.Bytes()) } func (r *registry) writePB(w io.Writer, writeEncoded encoder) (int, error) { var metricHashes map[uint64]struct{} if r.collectChecksEnabled { metricHashes = make(map[uint64]struct{}) } metricChan := make(chan Metric, capMetricChan) wg := sync.WaitGroup{} r.mtx.RLock() metricFamiliesByName := make(map[string]*dto.MetricFamily, len(r.dimHashesByName)) // Scatter. // (Collectors could be complex and slow, so we call them all at once.) wg.Add(len(r.collectorsByID)) go func() { wg.Wait() close(metricChan) }() for _, collector := range r.collectorsByID { go func(collector Collector) { defer wg.Done() collector.Collect(metricChan) }(collector) } r.mtx.RUnlock() // Drain metricChan in case of premature return. defer func() { for _ = range metricChan { } }() // Gather. for metric := range metricChan { // This could be done concurrently, too, but it required locking // of metricFamiliesByName (and of metricHashes if checks are // enabled). Most likely not worth it. desc := metric.Desc() metricFamily, ok := metricFamiliesByName[desc.fqName] if !ok { metricFamily = r.getMetricFamily() defer r.giveMetricFamily(metricFamily) metricFamily.Name = proto.String(desc.fqName) metricFamily.Help = proto.String(desc.help) metricFamiliesByName[desc.fqName] = metricFamily } dtoMetric := r.getMetric() defer r.giveMetric(dtoMetric) if err := metric.Write(dtoMetric); err != nil { // TODO: Consider different means of error reporting so // that a single erroneous metric could be skipped // instead of blowing up the whole collection. return 0, fmt.Errorf("error collecting metric %v: %s", desc, err) } switch { case metricFamily.Type != nil: // Type already set. We are good. case dtoMetric.Gauge != nil: metricFamily.Type = dto.MetricType_GAUGE.Enum() case dtoMetric.Counter != nil: metricFamily.Type = dto.MetricType_COUNTER.Enum() case dtoMetric.Summary != nil: metricFamily.Type = dto.MetricType_SUMMARY.Enum() case dtoMetric.Untyped != nil: metricFamily.Type = dto.MetricType_UNTYPED.Enum() case dtoMetric.Histogram != nil: metricFamily.Type = dto.MetricType_HISTOGRAM.Enum() default: return 0, fmt.Errorf("empty metric collected: %s", dtoMetric) } if r.collectChecksEnabled { if err := r.checkConsistency(metricFamily, dtoMetric, desc, metricHashes); err != nil { return 0, err } } metricFamily.Metric = append(metricFamily.Metric, dtoMetric) } if r.metricFamilyInjectionHook != nil { for _, mf := range r.metricFamilyInjectionHook() { existingMF, exists := metricFamiliesByName[mf.GetName()] if !exists { metricFamiliesByName[mf.GetName()] = mf if r.collectChecksEnabled { for _, m := range mf.Metric { if err := r.checkConsistency(mf, m, nil, metricHashes); err != nil { return 0, err } } } continue } for _, m := range mf.Metric { if r.collectChecksEnabled { if err := r.checkConsistency(existingMF, m, nil, metricHashes); err != nil { return 0, err } } existingMF.Metric = append(existingMF.Metric, m) } } } // Now that MetricFamilies are all set, sort their Metrics // lexicographically by their label values. for _, mf := range metricFamiliesByName { sort.Sort(metricSorter(mf.Metric)) } // Write out MetricFamilies sorted by their name. names := make([]string, 0, len(metricFamiliesByName)) for name := range metricFamiliesByName { names = append(names, name) } sort.Strings(names) var written int for _, name := range names { w, err := writeEncoded(w, metricFamiliesByName[name]) written += w if err != nil { return written, err } } return written, nil } func (r *registry) checkConsistency(metricFamily *dto.MetricFamily, dtoMetric *dto.Metric, desc *Desc, metricHashes map[uint64]struct{}) error { // Type consistency with metric family. if metricFamily.GetType() == dto.MetricType_GAUGE && dtoMetric.Gauge == nil || metricFamily.GetType() == dto.MetricType_COUNTER && dtoMetric.Counter == nil || metricFamily.GetType() == dto.MetricType_SUMMARY && dtoMetric.Summary == nil || metricFamily.GetType() == dto.MetricType_HISTOGRAM && dtoMetric.Histogram == nil || metricFamily.GetType() == dto.MetricType_UNTYPED && dtoMetric.Untyped == nil { return fmt.Errorf( "collected metric %s %s is not a %s", metricFamily.GetName(), dtoMetric, metricFamily.GetType(), ) } // Is the metric unique (i.e. no other metric with the same name and the same label values)? h := fnv.New64a() var buf bytes.Buffer buf.WriteString(metricFamily.GetName()) buf.WriteByte(model.SeparatorByte) h.Write(buf.Bytes()) // Make sure label pairs are sorted. We depend on it for the consistency // check. Label pairs must be sorted by contract. But the point of this // method is to check for contract violations. So we better do the sort // now. sort.Sort(LabelPairSorter(dtoMetric.Label)) for _, lp := range dtoMetric.Label { buf.Reset() buf.WriteString(lp.GetValue()) buf.WriteByte(model.SeparatorByte) h.Write(buf.Bytes()) } metricHash := h.Sum64() if _, exists := metricHashes[metricHash]; exists { return fmt.Errorf( "collected metric %s %s was collected before with the same name and label values", metricFamily.GetName(), dtoMetric, ) } metricHashes[metricHash] = struct{}{} if desc == nil { return nil // Nothing left to check if we have no desc. } // Desc consistency with metric family. if metricFamily.GetName() != desc.fqName { return fmt.Errorf( "collected metric %s %s has name %q but should have %q", metricFamily.GetName(), dtoMetric, metricFamily.GetName(), desc.fqName, ) } if metricFamily.GetHelp() != desc.help { return fmt.Errorf( "collected metric %s %s has help %q but should have %q", metricFamily.GetName(), dtoMetric, metricFamily.GetHelp(), desc.help, ) } // Is the desc consistent with the content of the metric? lpsFromDesc := make([]*dto.LabelPair, 0, len(dtoMetric.Label)) lpsFromDesc = append(lpsFromDesc, desc.constLabelPairs...) for _, l := range desc.variableLabels { lpsFromDesc = append(lpsFromDesc, &dto.LabelPair{ Name: proto.String(l), }) } if len(lpsFromDesc) != len(dtoMetric.Label) { return fmt.Errorf( "labels in collected metric %s %s are inconsistent with descriptor %s", metricFamily.GetName(), dtoMetric, desc, ) } sort.Sort(LabelPairSorter(lpsFromDesc)) for i, lpFromDesc := range lpsFromDesc { lpFromMetric := dtoMetric.Label[i] if lpFromDesc.GetName() != lpFromMetric.GetName() || lpFromDesc.Value != nil && lpFromDesc.GetValue() != lpFromMetric.GetValue() { return fmt.Errorf( "labels in collected metric %s %s are inconsistent with descriptor %s", metricFamily.GetName(), dtoMetric, desc, ) } } r.mtx.RLock() // Remaining checks need the read lock. defer r.mtx.RUnlock() // Is the desc registered? if _, exist := r.descIDs[desc.id]; !exist { return fmt.Errorf( "collected metric %s %s with unregistered descriptor %s", metricFamily.GetName(), dtoMetric, desc, ) } return nil } func (r *registry) getBuf() *bytes.Buffer { select { case buf := <-r.bufPool: return buf default: return &bytes.Buffer{} } } func (r *registry) giveBuf(buf *bytes.Buffer) { buf.Reset() select { case r.bufPool <- buf: default: } } func (r *registry) getMetricFamily() *dto.MetricFamily { select { case mf := <-r.metricFamilyPool: return mf default: return &dto.MetricFamily{} } } func (r *registry) giveMetricFamily(mf *dto.MetricFamily) { mf.Reset() select { case r.metricFamilyPool <- mf: default: } } func (r *registry) getMetric() *dto.Metric { select { case m := <-r.metricPool: return m default: return &dto.Metric{} } } func (r *registry) giveMetric(m *dto.Metric) { m.Reset() select { case r.metricPool <- m: default: } } func newRegistry() *registry { return ®istry{ collectorsByID: map[uint64]Collector{}, descIDs: map[uint64]struct{}{}, dimHashesByName: map[string]uint64{}, bufPool: make(chan *bytes.Buffer, numBufs), metricFamilyPool: make(chan *dto.MetricFamily, numMetricFamilies), metricPool: make(chan *dto.Metric, numMetrics), } } func newDefaultRegistry() *registry { r := newRegistry() r.Register(NewProcessCollector(os.Getpid(), "")) r.Register(NewGoCollector()) return r } func chooseEncoder(req *http.Request) (encoder, string) { accepts := goautoneg.ParseAccept(req.Header.Get(acceptHeader)) for _, accept := range accepts { switch { case accept.Type == "application" && accept.SubType == "vnd.google.protobuf" && accept.Params["proto"] == "io.prometheus.client.MetricFamily": switch accept.Params["encoding"] { case "delimited": return text.WriteProtoDelimited, DelimitedTelemetryContentType case "text": return text.WriteProtoText, ProtoTextTelemetryContentType case "compact-text": return text.WriteProtoCompactText, ProtoCompactTextTelemetryContentType default: continue } case accept.Type == "text" && accept.SubType == "plain" && (accept.Params["version"] == "0.0.4" || accept.Params["version"] == ""): return text.MetricFamilyToText, TextTelemetryContentType default: continue } } return text.MetricFamilyToText, TextTelemetryContentType } // decorateWriter wraps a writer to handle gzip compression if requested. It // returns the decorated writer and the appropriate "Content-Encoding" header // (which is empty if no compression is enabled). func decorateWriter(request *http.Request, writer io.Writer) (io.Writer, string) { header := request.Header.Get(acceptEncodingHeader) parts := strings.Split(header, ",") for _, part := range parts { part := strings.TrimSpace(part) if part == "gzip" || strings.HasPrefix(part, "gzip;") { return gzip.NewWriter(writer), "gzip" } } return writer, "" } type metricSorter []*dto.Metric func (s metricSorter) Len() int { return len(s) } func (s metricSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s metricSorter) Less(i, j int) bool { if len(s[i].Label) != len(s[j].Label) { // This should not happen. The metrics are // inconsistent. However, we have to deal with the fact, as // people might use custom collectors or metric family injection // to create inconsistent metrics. So let's simply compare the // number of labels in this case. That will still yield // reproducible sorting. return len(s[i].Label) < len(s[j].Label) } for n, lp := range s[i].Label { vi := lp.GetValue() vj := s[j].Label[n].GetValue() if vi != vj { return vi < vj } } return true } golang-prometheus-client-0.7.0+ds/prometheus/registry_test.go000066400000000000000000000341361256572052500245050ustar00rootroot00000000000000// Copyright 2014 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Copyright (c) 2013, The Prometheus Authors // All rights reserved. // // Use of this source code is governed by a BSD-style license that can be found // in the LICENSE file. package prometheus import ( "bytes" "encoding/binary" "net/http" "testing" "github.com/golang/protobuf/proto" dto "github.com/prometheus/client_model/go" ) type fakeResponseWriter struct { header http.Header body bytes.Buffer } func (r *fakeResponseWriter) Header() http.Header { return r.header } func (r *fakeResponseWriter) Write(d []byte) (l int, err error) { return r.body.Write(d) } func (r *fakeResponseWriter) WriteHeader(c int) { } func testHandler(t testing.TB) { metricVec := NewCounterVec( CounterOpts{ Name: "name", Help: "docstring", ConstLabels: Labels{"constname": "constvalue"}, }, []string{"labelname"}, ) metricVec.WithLabelValues("val1").Inc() metricVec.WithLabelValues("val2").Inc() varintBuf := make([]byte, binary.MaxVarintLen32) externalMetricFamily := &dto.MetricFamily{ Name: proto.String("externalname"), Help: proto.String("externaldocstring"), Type: dto.MetricType_COUNTER.Enum(), Metric: []*dto.Metric{ { Label: []*dto.LabelPair{ { Name: proto.String("externalconstname"), Value: proto.String("externalconstvalue"), }, { Name: proto.String("externallabelname"), Value: proto.String("externalval1"), }, }, Counter: &dto.Counter{ Value: proto.Float64(1), }, }, }, } marshaledExternalMetricFamily, err := proto.Marshal(externalMetricFamily) if err != nil { t.Fatal(err) } var externalBuf bytes.Buffer l := binary.PutUvarint(varintBuf, uint64(len(marshaledExternalMetricFamily))) _, err = externalBuf.Write(varintBuf[:l]) if err != nil { t.Fatal(err) } _, err = externalBuf.Write(marshaledExternalMetricFamily) if err != nil { t.Fatal(err) } externalMetricFamilyAsBytes := externalBuf.Bytes() externalMetricFamilyAsText := []byte(`# HELP externalname externaldocstring # TYPE externalname counter externalname{externalconstname="externalconstvalue",externallabelname="externalval1"} 1 `) externalMetricFamilyAsProtoText := []byte(`name: "externalname" help: "externaldocstring" type: COUNTER metric: < label: < name: "externalconstname" value: "externalconstvalue" > label: < name: "externallabelname" value: "externalval1" > counter: < value: 1 > > `) externalMetricFamilyAsProtoCompactText := []byte(`name:"externalname" help:"externaldocstring" type:COUNTER metric: label: counter: > `) expectedMetricFamily := &dto.MetricFamily{ Name: proto.String("name"), Help: proto.String("docstring"), Type: dto.MetricType_COUNTER.Enum(), Metric: []*dto.Metric{ { Label: []*dto.LabelPair{ { Name: proto.String("constname"), Value: proto.String("constvalue"), }, { Name: proto.String("labelname"), Value: proto.String("val1"), }, }, Counter: &dto.Counter{ Value: proto.Float64(1), }, }, { Label: []*dto.LabelPair{ { Name: proto.String("constname"), Value: proto.String("constvalue"), }, { Name: proto.String("labelname"), Value: proto.String("val2"), }, }, Counter: &dto.Counter{ Value: proto.Float64(1), }, }, }, } marshaledExpectedMetricFamily, err := proto.Marshal(expectedMetricFamily) if err != nil { t.Fatal(err) } var buf bytes.Buffer l = binary.PutUvarint(varintBuf, uint64(len(marshaledExpectedMetricFamily))) _, err = buf.Write(varintBuf[:l]) if err != nil { t.Fatal(err) } _, err = buf.Write(marshaledExpectedMetricFamily) if err != nil { t.Fatal(err) } expectedMetricFamilyAsBytes := buf.Bytes() expectedMetricFamilyAsText := []byte(`# HELP name docstring # TYPE name counter name{constname="constvalue",labelname="val1"} 1 name{constname="constvalue",labelname="val2"} 1 `) expectedMetricFamilyAsProtoText := []byte(`name: "name" help: "docstring" type: COUNTER metric: < label: < name: "constname" value: "constvalue" > label: < name: "labelname" value: "val1" > counter: < value: 1 > > metric: < label: < name: "constname" value: "constvalue" > label: < name: "labelname" value: "val2" > counter: < value: 1 > > `) expectedMetricFamilyAsProtoCompactText := []byte(`name:"name" help:"docstring" type:COUNTER metric: label: counter: > metric: label: counter: > `) externalMetricFamilyWithSameName := &dto.MetricFamily{ Name: proto.String("name"), Help: proto.String("inconsistent help string does not matter here"), Type: dto.MetricType_COUNTER.Enum(), Metric: []*dto.Metric{ { Label: []*dto.LabelPair{ { Name: proto.String("constname"), Value: proto.String("constvalue"), }, { Name: proto.String("labelname"), Value: proto.String("different_val"), }, }, Counter: &dto.Counter{ Value: proto.Float64(42), }, }, }, } expectedMetricFamilyMergedWithExternalAsProtoCompactText := []byte(`name:"name" help:"docstring" type:COUNTER metric: label: counter: > metric: label: counter: > metric: label: counter: > `) type output struct { headers map[string]string body []byte } var scenarios = []struct { headers map[string]string out output collector Collector externalMF []*dto.MetricFamily }{ { // 0 headers: map[string]string{ "Accept": "foo/bar;q=0.2, dings/bums;q=0.8", }, out: output{ headers: map[string]string{ "Content-Type": `text/plain; version=0.0.4`, }, body: []byte{}, }, }, { // 1 headers: map[string]string{ "Accept": "foo/bar;q=0.2, application/quark;q=0.8", }, out: output{ headers: map[string]string{ "Content-Type": `text/plain; version=0.0.4`, }, body: []byte{}, }, }, { // 2 headers: map[string]string{ "Accept": "foo/bar;q=0.2, application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=bla;q=0.8", }, out: output{ headers: map[string]string{ "Content-Type": `text/plain; version=0.0.4`, }, body: []byte{}, }, }, { // 3 headers: map[string]string{ "Accept": "text/plain;q=0.2, application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited;q=0.8", }, out: output{ headers: map[string]string{ "Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited`, }, body: []byte{}, }, }, { // 4 headers: map[string]string{ "Accept": "application/json", }, out: output{ headers: map[string]string{ "Content-Type": `text/plain; version=0.0.4`, }, body: expectedMetricFamilyAsText, }, collector: metricVec, }, { // 5 headers: map[string]string{ "Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited", }, out: output{ headers: map[string]string{ "Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited`, }, body: expectedMetricFamilyAsBytes, }, collector: metricVec, }, { // 6 headers: map[string]string{ "Accept": "application/json", }, out: output{ headers: map[string]string{ "Content-Type": `text/plain; version=0.0.4`, }, body: externalMetricFamilyAsText, }, externalMF: []*dto.MetricFamily{externalMetricFamily}, }, { // 7 headers: map[string]string{ "Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited", }, out: output{ headers: map[string]string{ "Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited`, }, body: externalMetricFamilyAsBytes, }, externalMF: []*dto.MetricFamily{externalMetricFamily}, }, { // 8 headers: map[string]string{ "Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited", }, out: output{ headers: map[string]string{ "Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited`, }, body: bytes.Join( [][]byte{ externalMetricFamilyAsBytes, expectedMetricFamilyAsBytes, }, []byte{}, ), }, collector: metricVec, externalMF: []*dto.MetricFamily{externalMetricFamily}, }, { // 9 headers: map[string]string{ "Accept": "text/plain", }, out: output{ headers: map[string]string{ "Content-Type": `text/plain; version=0.0.4`, }, body: []byte{}, }, }, { // 10 headers: map[string]string{ "Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=bla;q=0.2, text/plain;q=0.5", }, out: output{ headers: map[string]string{ "Content-Type": `text/plain; version=0.0.4`, }, body: expectedMetricFamilyAsText, }, collector: metricVec, }, { // 11 headers: map[string]string{ "Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=bla;q=0.2, text/plain;q=0.5;version=0.0.4", }, out: output{ headers: map[string]string{ "Content-Type": `text/plain; version=0.0.4`, }, body: bytes.Join( [][]byte{ externalMetricFamilyAsText, expectedMetricFamilyAsText, }, []byte{}, ), }, collector: metricVec, externalMF: []*dto.MetricFamily{externalMetricFamily}, }, { // 12 headers: map[string]string{ "Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited;q=0.2, text/plain;q=0.5;version=0.0.2", }, out: output{ headers: map[string]string{ "Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited`, }, body: bytes.Join( [][]byte{ externalMetricFamilyAsBytes, expectedMetricFamilyAsBytes, }, []byte{}, ), }, collector: metricVec, externalMF: []*dto.MetricFamily{externalMetricFamily}, }, { // 13 headers: map[string]string{ "Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=text;q=0.5, application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited;q=0.4", }, out: output{ headers: map[string]string{ "Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=text`, }, body: bytes.Join( [][]byte{ externalMetricFamilyAsProtoText, expectedMetricFamilyAsProtoText, }, []byte{}, ), }, collector: metricVec, externalMF: []*dto.MetricFamily{externalMetricFamily}, }, { // 14 headers: map[string]string{ "Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=compact-text", }, out: output{ headers: map[string]string{ "Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=compact-text`, }, body: bytes.Join( [][]byte{ externalMetricFamilyAsProtoCompactText, expectedMetricFamilyAsProtoCompactText, }, []byte{}, ), }, collector: metricVec, externalMF: []*dto.MetricFamily{externalMetricFamily}, }, { // 15 headers: map[string]string{ "Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=compact-text", }, out: output{ headers: map[string]string{ "Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=compact-text`, }, body: bytes.Join( [][]byte{ externalMetricFamilyAsProtoCompactText, expectedMetricFamilyMergedWithExternalAsProtoCompactText, }, []byte{}, ), }, collector: metricVec, externalMF: []*dto.MetricFamily{ externalMetricFamily, externalMetricFamilyWithSameName, }, }, } for i, scenario := range scenarios { registry := newRegistry() registry.collectChecksEnabled = true if scenario.collector != nil { registry.Register(scenario.collector) } if scenario.externalMF != nil { registry.metricFamilyInjectionHook = func() []*dto.MetricFamily { return scenario.externalMF } } writer := &fakeResponseWriter{ header: http.Header{}, } handler := InstrumentHandler("prometheus", registry) request, _ := http.NewRequest("GET", "/", nil) for key, value := range scenario.headers { request.Header.Add(key, value) } handler(writer, request) for key, value := range scenario.out.headers { if writer.Header().Get(key) != value { t.Errorf( "%d. expected %q for header %q, got %q", i, value, key, writer.Header().Get(key), ) } } if !bytes.Equal(scenario.out.body, writer.body.Bytes()) { t.Errorf( "%d. expected %q for body, got %q", i, scenario.out.body, writer.body.Bytes(), ) } } } func TestHandler(t *testing.T) { testHandler(t) } func BenchmarkHandler(b *testing.B) { for i := 0; i < b.N; i++ { testHandler(b) } } golang-prometheus-client-0.7.0+ds/prometheus/summary.go000066400000000000000000000374511256572052500232760ustar00rootroot00000000000000// Copyright 2014 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package prometheus import ( "fmt" "hash/fnv" "math" "sort" "sync" "time" "github.com/beorn7/perks/quantile" "github.com/golang/protobuf/proto" dto "github.com/prometheus/client_model/go" "github.com/prometheus/client_golang/model" ) // A Summary captures individual observations from an event or sample stream and // summarizes them in a manner similar to traditional summary statistics: 1. sum // of observations, 2. observation count, 3. rank estimations. // // A typical use-case is the observation of request latencies. By default, a // Summary provides the median, the 90th and the 99th percentile of the latency // as rank estimations. // // Note that the rank estimations cannot be aggregated in a meaningful way with // the Prometheus query language (i.e. you cannot average or add them). If you // need aggregatable quantiles (e.g. you want the 99th percentile latency of all // queries served across all instances of a service), consider the Histogram // metric type. See the Prometheus documentation for more details. // // To create Summary instances, use NewSummary. type Summary interface { Metric Collector // Observe adds a single observation to the summary. Observe(float64) } var ( // DefObjectives are the default Summary quantile values. DefObjectives = map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001} errQuantileLabelNotAllowed = fmt.Errorf( "%q is not allowed as label name in summaries", model.QuantileLabel, ) ) // Default values for SummaryOpts. const ( // DefMaxAge is the default duration for which observations stay // relevant. DefMaxAge time.Duration = 10 * time.Minute // DefAgeBuckets is the default number of buckets used to calculate the // age of observations. DefAgeBuckets = 5 // DefBufCap is the standard buffer size for collecting Summary observations. DefBufCap = 500 ) // SummaryOpts bundles the options for creating a Summary metric. It is // mandatory to set Name and Help to a non-empty string. All other fields are // optional and can safely be left at their zero value. type SummaryOpts struct { // Namespace, Subsystem, and Name are components of the fully-qualified // name of the Summary (created by joining these components with // "_"). Only Name is mandatory, the others merely help structuring the // name. Note that the fully-qualified name of the Summary must be a // valid Prometheus metric name. Namespace string Subsystem string Name string // Help provides information about this Summary. Mandatory! // // Metrics with the same fully-qualified name must have the same Help // string. Help string // ConstLabels are used to attach fixed labels to this // Summary. Summaries with the same fully-qualified name must have the // same label names in their ConstLabels. // // Note that in most cases, labels have a value that varies during the // lifetime of a process. Those labels are usually managed with a // SummaryVec. ConstLabels serve only special purposes. One is for the // special case where the value of a label does not change during the // lifetime of a process, e.g. if the revision of the running binary is // put into a label. Another, more advanced purpose is if more than one // Collector needs to collect Summaries with the same fully-qualified // name. In that case, those Summaries must differ in the values of // their ConstLabels. See the Collector examples. // // If the value of a label never changes (not even between binaries), // that label most likely should not be a label at all (but part of the // metric name). ConstLabels Labels // Objectives defines the quantile rank estimates with their respective // absolute error. The default value is DefObjectives. Objectives map[float64]float64 // MaxAge defines the duration for which an observation stays relevant // for the summary. Must be positive. The default value is DefMaxAge. MaxAge time.Duration // AgeBuckets is the number of buckets used to exclude observations that // are older than MaxAge from the summary. A higher number has a // resource penalty, so only increase it if the higher resolution is // really required. For very high observation rates, you might want to // reduce the number of age buckets. With only one age bucket, you will // effectively see a complete reset of the summary each time MaxAge has // passed. The default value is DefAgeBuckets. AgeBuckets uint32 // BufCap defines the default sample stream buffer size. The default // value of DefBufCap should suffice for most uses. If there is a need // to increase the value, a multiple of 500 is recommended (because that // is the internal buffer size of the underlying package // "github.com/bmizerany/perks/quantile"). BufCap uint32 } // TODO: Great fuck-up with the sliding-window decay algorithm... The Merge // method of perk/quantile is actually not working as advertised - and it might // be unfixable, as the underlying algorithm is apparently not capable of // merging summaries in the first place. To avoid using Merge, we are currently // adding observations to _each_ age bucket, i.e. the effort to add a sample is // essentially multiplied by the number of age buckets. When rotating age // buckets, we empty the previous head stream. On scrape time, we simply take // the quantiles from the head stream (no merging required). Result: More effort // on observation time, less effort on scrape time, which is exactly the // opposite of what we try to accomplish, but at least the results are correct. // // The quite elegant previous contraption to merge the age buckets efficiently // on scrape time (see code up commit 6b9530d72ea715f0ba612c0120e6e09fbf1d49d0) // can't be used anymore. // NewSummary creates a new Summary based on the provided SummaryOpts. func NewSummary(opts SummaryOpts) Summary { return newSummary( NewDesc( BuildFQName(opts.Namespace, opts.Subsystem, opts.Name), opts.Help, nil, opts.ConstLabels, ), opts, ) } func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary { if len(desc.variableLabels) != len(labelValues) { panic(errInconsistentCardinality) } for _, n := range desc.variableLabels { if n == model.QuantileLabel { panic(errQuantileLabelNotAllowed) } } for _, lp := range desc.constLabelPairs { if lp.GetName() == model.QuantileLabel { panic(errQuantileLabelNotAllowed) } } if len(opts.Objectives) == 0 { opts.Objectives = DefObjectives } if opts.MaxAge < 0 { panic(fmt.Errorf("illegal max age MaxAge=%v", opts.MaxAge)) } if opts.MaxAge == 0 { opts.MaxAge = DefMaxAge } if opts.AgeBuckets == 0 { opts.AgeBuckets = DefAgeBuckets } if opts.BufCap == 0 { opts.BufCap = DefBufCap } s := &summary{ desc: desc, objectives: opts.Objectives, sortedObjectives: make([]float64, 0, len(opts.Objectives)), labelPairs: makeLabelPairs(desc, labelValues), hotBuf: make([]float64, 0, opts.BufCap), coldBuf: make([]float64, 0, opts.BufCap), streamDuration: opts.MaxAge / time.Duration(opts.AgeBuckets), } s.headStreamExpTime = time.Now().Add(s.streamDuration) s.hotBufExpTime = s.headStreamExpTime for i := uint32(0); i < opts.AgeBuckets; i++ { s.streams = append(s.streams, s.newStream()) } s.headStream = s.streams[0] for qu := range s.objectives { s.sortedObjectives = append(s.sortedObjectives, qu) } sort.Float64s(s.sortedObjectives) s.Init(s) // Init self-collection. return s } type summary struct { SelfCollector bufMtx sync.Mutex // Protects hotBuf and hotBufExpTime. mtx sync.Mutex // Protects every other moving part. // Lock bufMtx before mtx if both are needed. desc *Desc objectives map[float64]float64 sortedObjectives []float64 labelPairs []*dto.LabelPair sum float64 cnt uint64 hotBuf, coldBuf []float64 streams []*quantile.Stream streamDuration time.Duration headStream *quantile.Stream headStreamIdx int headStreamExpTime, hotBufExpTime time.Time } func (s *summary) Desc() *Desc { return s.desc } func (s *summary) Observe(v float64) { s.bufMtx.Lock() defer s.bufMtx.Unlock() now := time.Now() if now.After(s.hotBufExpTime) { s.asyncFlush(now) } s.hotBuf = append(s.hotBuf, v) if len(s.hotBuf) == cap(s.hotBuf) { s.asyncFlush(now) } } func (s *summary) Write(out *dto.Metric) error { sum := &dto.Summary{} qs := make([]*dto.Quantile, 0, len(s.objectives)) s.bufMtx.Lock() s.mtx.Lock() // Swap bufs even if hotBuf is empty to set new hotBufExpTime. s.swapBufs(time.Now()) s.bufMtx.Unlock() s.flushColdBuf() sum.SampleCount = proto.Uint64(s.cnt) sum.SampleSum = proto.Float64(s.sum) for _, rank := range s.sortedObjectives { var q float64 if s.headStream.Count() == 0 { q = math.NaN() } else { q = s.headStream.Query(rank) } qs = append(qs, &dto.Quantile{ Quantile: proto.Float64(rank), Value: proto.Float64(q), }) } s.mtx.Unlock() if len(qs) > 0 { sort.Sort(quantSort(qs)) } sum.Quantile = qs out.Summary = sum out.Label = s.labelPairs return nil } func (s *summary) newStream() *quantile.Stream { return quantile.NewTargeted(s.objectives) } // asyncFlush needs bufMtx locked. func (s *summary) asyncFlush(now time.Time) { s.mtx.Lock() s.swapBufs(now) // Unblock the original goroutine that was responsible for the mutation // that triggered the compaction. But hold onto the global non-buffer // state mutex until the operation finishes. go func() { s.flushColdBuf() s.mtx.Unlock() }() } // rotateStreams needs mtx AND bufMtx locked. func (s *summary) maybeRotateStreams() { for !s.hotBufExpTime.Equal(s.headStreamExpTime) { s.headStream.Reset() s.headStreamIdx++ if s.headStreamIdx >= len(s.streams) { s.headStreamIdx = 0 } s.headStream = s.streams[s.headStreamIdx] s.headStreamExpTime = s.headStreamExpTime.Add(s.streamDuration) } } // flushColdBuf needs mtx locked. func (s *summary) flushColdBuf() { for _, v := range s.coldBuf { for _, stream := range s.streams { stream.Insert(v) } s.cnt++ s.sum += v } s.coldBuf = s.coldBuf[0:0] s.maybeRotateStreams() } // swapBufs needs mtx AND bufMtx locked, coldBuf must be empty. func (s *summary) swapBufs(now time.Time) { if len(s.coldBuf) != 0 { panic("coldBuf is not empty") } s.hotBuf, s.coldBuf = s.coldBuf, s.hotBuf // hotBuf is now empty and gets new expiration set. for now.After(s.hotBufExpTime) { s.hotBufExpTime = s.hotBufExpTime.Add(s.streamDuration) } } type quantSort []*dto.Quantile func (s quantSort) Len() int { return len(s) } func (s quantSort) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s quantSort) Less(i, j int) bool { return s[i].GetQuantile() < s[j].GetQuantile() } // SummaryVec is a Collector that bundles a set of Summaries that all share the // same Desc, but have different values for their variable labels. This is used // if you want to count the same thing partitioned by various dimensions // (e.g. HTTP request latencies, partitioned by status code and method). Create // instances with NewSummaryVec. type SummaryVec struct { MetricVec } // NewSummaryVec creates a new SummaryVec based on the provided SummaryOpts and // partitioned by the given label names. At least one label name must be // provided. func NewSummaryVec(opts SummaryOpts, labelNames []string) *SummaryVec { desc := NewDesc( BuildFQName(opts.Namespace, opts.Subsystem, opts.Name), opts.Help, labelNames, opts.ConstLabels, ) return &SummaryVec{ MetricVec: MetricVec{ children: map[uint64]Metric{}, desc: desc, hash: fnv.New64a(), newMetric: func(lvs ...string) Metric { return newSummary(desc, opts, lvs...) }, }, } } // GetMetricWithLabelValues replaces the method of the same name in // MetricVec. The difference is that this method returns a Summary and not a // Metric so that no type conversion is required. func (m *SummaryVec) GetMetricWithLabelValues(lvs ...string) (Summary, error) { metric, err := m.MetricVec.GetMetricWithLabelValues(lvs...) if metric != nil { return metric.(Summary), err } return nil, err } // GetMetricWith replaces the method of the same name in MetricVec. The // difference is that this method returns a Summary and not a Metric so that no // type conversion is required. func (m *SummaryVec) GetMetricWith(labels Labels) (Summary, error) { metric, err := m.MetricVec.GetMetricWith(labels) if metric != nil { return metric.(Summary), err } return nil, err } // WithLabelValues works as GetMetricWithLabelValues, but panics where // GetMetricWithLabelValues would have returned an error. By not returning an // error, WithLabelValues allows shortcuts like // myVec.WithLabelValues("404", "GET").Observe(42.21) func (m *SummaryVec) WithLabelValues(lvs ...string) Summary { return m.MetricVec.WithLabelValues(lvs...).(Summary) } // With works as GetMetricWith, but panics where GetMetricWithLabels would have // returned an error. By not returning an error, With allows shortcuts like // myVec.With(Labels{"code": "404", "method": "GET"}).Observe(42.21) func (m *SummaryVec) With(labels Labels) Summary { return m.MetricVec.With(labels).(Summary) } type constSummary struct { desc *Desc count uint64 sum float64 quantiles map[float64]float64 labelPairs []*dto.LabelPair } func (s *constSummary) Desc() *Desc { return s.desc } func (s *constSummary) Write(out *dto.Metric) error { sum := &dto.Summary{} qs := make([]*dto.Quantile, 0, len(s.quantiles)) sum.SampleCount = proto.Uint64(s.count) sum.SampleSum = proto.Float64(s.sum) for rank, q := range s.quantiles { qs = append(qs, &dto.Quantile{ Quantile: proto.Float64(rank), Value: proto.Float64(q), }) } if len(qs) > 0 { sort.Sort(quantSort(qs)) } sum.Quantile = qs out.Summary = sum out.Label = s.labelPairs return nil } // NewConstSummary returns a metric representing a Prometheus summary with fixed // values for the count, sum, and quantiles. As those parameters cannot be // changed, the returned value does not implement the Summary interface (but // only the Metric interface). Users of this package will not have much use for // it in regular operations. However, when implementing custom Collectors, it is // useful as a throw-away metric that is generated on the fly to send it to // Prometheus in the Collect method. // // quantiles maps ranks to quantile values. For example, a median latency of // 0.23s and a 99th percentile latency of 0.56s would be expressed as: // map[float64]float64{0.5: 0.23, 0.99: 0.56} // // NewConstSummary returns an error if the length of labelValues is not // consistent with the variable labels in Desc. func NewConstSummary( desc *Desc, count uint64, sum float64, quantiles map[float64]float64, labelValues ...string, ) (Metric, error) { if len(desc.variableLabels) != len(labelValues) { return nil, errInconsistentCardinality } return &constSummary{ desc: desc, count: count, sum: sum, quantiles: quantiles, labelPairs: makeLabelPairs(desc, labelValues), }, nil } // MustNewConstSummary is a version of NewConstSummary that panics where // NewConstMetric would have returned an error. func MustNewConstSummary( desc *Desc, count uint64, sum float64, quantiles map[float64]float64, labelValues ...string, ) Metric { m, err := NewConstSummary(desc, count, sum, quantiles, labelValues...) if err != nil { panic(err) } return m } golang-prometheus-client-0.7.0+ds/prometheus/summary_test.go000066400000000000000000000170371256572052500243330ustar00rootroot00000000000000// Copyright 2014 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package prometheus import ( "math" "math/rand" "sort" "sync" "testing" "testing/quick" "time" dto "github.com/prometheus/client_model/go" ) func benchmarkSummaryObserve(w int, b *testing.B) { b.StopTimer() wg := new(sync.WaitGroup) wg.Add(w) g := new(sync.WaitGroup) g.Add(1) s := NewSummary(SummaryOpts{}) for i := 0; i < w; i++ { go func() { g.Wait() for i := 0; i < b.N; i++ { s.Observe(float64(i)) } wg.Done() }() } b.StartTimer() g.Done() wg.Wait() } func BenchmarkSummaryObserve1(b *testing.B) { benchmarkSummaryObserve(1, b) } func BenchmarkSummaryObserve2(b *testing.B) { benchmarkSummaryObserve(2, b) } func BenchmarkSummaryObserve4(b *testing.B) { benchmarkSummaryObserve(4, b) } func BenchmarkSummaryObserve8(b *testing.B) { benchmarkSummaryObserve(8, b) } func benchmarkSummaryWrite(w int, b *testing.B) { b.StopTimer() wg := new(sync.WaitGroup) wg.Add(w) g := new(sync.WaitGroup) g.Add(1) s := NewSummary(SummaryOpts{}) for i := 0; i < 1000000; i++ { s.Observe(float64(i)) } for j := 0; j < w; j++ { outs := make([]dto.Metric, b.N) go func(o []dto.Metric) { g.Wait() for i := 0; i < b.N; i++ { s.Write(&o[i]) } wg.Done() }(outs) } b.StartTimer() g.Done() wg.Wait() } func BenchmarkSummaryWrite1(b *testing.B) { benchmarkSummaryWrite(1, b) } func BenchmarkSummaryWrite2(b *testing.B) { benchmarkSummaryWrite(2, b) } func BenchmarkSummaryWrite4(b *testing.B) { benchmarkSummaryWrite(4, b) } func BenchmarkSummaryWrite8(b *testing.B) { benchmarkSummaryWrite(8, b) } func TestSummaryConcurrency(t *testing.T) { if testing.Short() { t.Skip("Skipping test in short mode.") } rand.Seed(42) it := func(n uint32) bool { mutations := int(n%1e4 + 1e4) concLevel := int(n%5 + 1) total := mutations * concLevel var start, end sync.WaitGroup start.Add(1) end.Add(concLevel) sum := NewSummary(SummaryOpts{ Name: "test_summary", Help: "helpless", }) allVars := make([]float64, total) var sampleSum float64 for i := 0; i < concLevel; i++ { vals := make([]float64, mutations) for j := 0; j < mutations; j++ { v := rand.NormFloat64() vals[j] = v allVars[i*mutations+j] = v sampleSum += v } go func(vals []float64) { start.Wait() for _, v := range vals { sum.Observe(v) } end.Done() }(vals) } sort.Float64s(allVars) start.Done() end.Wait() m := &dto.Metric{} sum.Write(m) if got, want := int(*m.Summary.SampleCount), total; got != want { t.Errorf("got sample count %d, want %d", got, want) } if got, want := *m.Summary.SampleSum, sampleSum; math.Abs((got-want)/want) > 0.001 { t.Errorf("got sample sum %f, want %f", got, want) } objectives := make([]float64, 0, len(DefObjectives)) for qu := range DefObjectives { objectives = append(objectives, qu) } sort.Float64s(objectives) for i, wantQ := range objectives { ε := DefObjectives[wantQ] gotQ := *m.Summary.Quantile[i].Quantile gotV := *m.Summary.Quantile[i].Value min, max := getBounds(allVars, wantQ, ε) if gotQ != wantQ { t.Errorf("got quantile %f, want %f", gotQ, wantQ) } if gotV < min || gotV > max { t.Errorf("got %f for quantile %f, want [%f,%f]", gotV, gotQ, min, max) } } return true } if err := quick.Check(it, nil); err != nil { t.Error(err) } } func TestSummaryVecConcurrency(t *testing.T) { if testing.Short() { t.Skip("Skipping test in short mode.") } rand.Seed(42) objectives := make([]float64, 0, len(DefObjectives)) for qu := range DefObjectives { objectives = append(objectives, qu) } sort.Float64s(objectives) it := func(n uint32) bool { mutations := int(n%1e4 + 1e4) concLevel := int(n%7 + 1) vecLength := int(n%3 + 1) var start, end sync.WaitGroup start.Add(1) end.Add(concLevel) sum := NewSummaryVec( SummaryOpts{ Name: "test_summary", Help: "helpless", }, []string{"label"}, ) allVars := make([][]float64, vecLength) sampleSums := make([]float64, vecLength) for i := 0; i < concLevel; i++ { vals := make([]float64, mutations) picks := make([]int, mutations) for j := 0; j < mutations; j++ { v := rand.NormFloat64() vals[j] = v pick := rand.Intn(vecLength) picks[j] = pick allVars[pick] = append(allVars[pick], v) sampleSums[pick] += v } go func(vals []float64) { start.Wait() for i, v := range vals { sum.WithLabelValues(string('A' + picks[i])).Observe(v) } end.Done() }(vals) } for _, vars := range allVars { sort.Float64s(vars) } start.Done() end.Wait() for i := 0; i < vecLength; i++ { m := &dto.Metric{} s := sum.WithLabelValues(string('A' + i)) s.Write(m) if got, want := int(*m.Summary.SampleCount), len(allVars[i]); got != want { t.Errorf("got sample count %d for label %c, want %d", got, 'A'+i, want) } if got, want := *m.Summary.SampleSum, sampleSums[i]; math.Abs((got-want)/want) > 0.001 { t.Errorf("got sample sum %f for label %c, want %f", got, 'A'+i, want) } for j, wantQ := range objectives { ε := DefObjectives[wantQ] gotQ := *m.Summary.Quantile[j].Quantile gotV := *m.Summary.Quantile[j].Value min, max := getBounds(allVars[i], wantQ, ε) if gotQ != wantQ { t.Errorf("got quantile %f for label %c, want %f", gotQ, 'A'+i, wantQ) } if gotV < min || gotV > max { t.Errorf("got %f for quantile %f for label %c, want [%f,%f]", gotV, gotQ, 'A'+i, min, max) } } } return true } if err := quick.Check(it, nil); err != nil { t.Error(err) } } func TestSummaryDecay(t *testing.T) { if testing.Short() { t.Skip("Skipping test in short mode.") // More because it depends on timing than because it is particularly long... } sum := NewSummary(SummaryOpts{ Name: "test_summary", Help: "helpless", MaxAge: 100 * time.Millisecond, Objectives: map[float64]float64{0.1: 0.001}, AgeBuckets: 10, }) m := &dto.Metric{} i := 0 tick := time.NewTicker(time.Millisecond) for _ = range tick.C { i++ sum.Observe(float64(i)) if i%10 == 0 { sum.Write(m) if got, want := *m.Summary.Quantile[0].Value, math.Max(float64(i)/10, float64(i-90)); math.Abs(got-want) > 20 { t.Errorf("%d. got %f, want %f", i, got, want) } m.Reset() } if i >= 1000 { break } } tick.Stop() // Wait for MaxAge without observations and make sure quantiles are NaN. time.Sleep(100 * time.Millisecond) sum.Write(m) if got := *m.Summary.Quantile[0].Value; !math.IsNaN(got) { t.Errorf("got %f, want NaN after expiration", got) } } func getBounds(vars []float64, q, ε float64) (min, max float64) { // TODO: This currently tolerates an error of up to 2*ε. The error must // be at most ε, but for some reason, it's sometimes slightly // higher. That's a bug. n := float64(len(vars)) lower := int((q - 2*ε) * n) upper := int(math.Ceil((q + 2*ε) * n)) min = vars[0] if lower > 1 { min = vars[lower-1] } max = vars[len(vars)-1] if upper < len(vars) { max = vars[upper-1] } return } golang-prometheus-client-0.7.0+ds/prometheus/untyped.go000066400000000000000000000116131256572052500232610ustar00rootroot00000000000000// Copyright 2014 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package prometheus import "hash/fnv" // Untyped is a Metric that represents a single numerical value that can // arbitrarily go up and down. // // An Untyped metric works the same as a Gauge. The only difference is that to // no type information is implied. // // To create Untyped instances, use NewUntyped. type Untyped interface { Metric Collector // Set sets the Untyped metric to an arbitrary value. Set(float64) // Inc increments the Untyped metric by 1. Inc() // Dec decrements the Untyped metric by 1. Dec() // Add adds the given value to the Untyped metric. (The value can be // negative, resulting in a decrease.) Add(float64) // Sub subtracts the given value from the Untyped metric. (The value can // be negative, resulting in an increase.) Sub(float64) } // UntypedOpts is an alias for Opts. See there for doc comments. type UntypedOpts Opts // NewUntyped creates a new Untyped metric from the provided UntypedOpts. func NewUntyped(opts UntypedOpts) Untyped { return newValue(NewDesc( BuildFQName(opts.Namespace, opts.Subsystem, opts.Name), opts.Help, nil, opts.ConstLabels, ), UntypedValue, 0) } // UntypedVec is a Collector that bundles a set of Untyped metrics that all // share the same Desc, but have different values for their variable // labels. This is used if you want to count the same thing partitioned by // various dimensions. Create instances with NewUntypedVec. type UntypedVec struct { MetricVec } // NewUntypedVec creates a new UntypedVec based on the provided UntypedOpts and // partitioned by the given label names. At least one label name must be // provided. func NewUntypedVec(opts UntypedOpts, labelNames []string) *UntypedVec { desc := NewDesc( BuildFQName(opts.Namespace, opts.Subsystem, opts.Name), opts.Help, labelNames, opts.ConstLabels, ) return &UntypedVec{ MetricVec: MetricVec{ children: map[uint64]Metric{}, desc: desc, hash: fnv.New64a(), newMetric: func(lvs ...string) Metric { return newValue(desc, UntypedValue, 0, lvs...) }, }, } } // GetMetricWithLabelValues replaces the method of the same name in // MetricVec. The difference is that this method returns an Untyped and not a // Metric so that no type conversion is required. func (m *UntypedVec) GetMetricWithLabelValues(lvs ...string) (Untyped, error) { metric, err := m.MetricVec.GetMetricWithLabelValues(lvs...) if metric != nil { return metric.(Untyped), err } return nil, err } // GetMetricWith replaces the method of the same name in MetricVec. The // difference is that this method returns an Untyped and not a Metric so that no // type conversion is required. func (m *UntypedVec) GetMetricWith(labels Labels) (Untyped, error) { metric, err := m.MetricVec.GetMetricWith(labels) if metric != nil { return metric.(Untyped), err } return nil, err } // WithLabelValues works as GetMetricWithLabelValues, but panics where // GetMetricWithLabelValues would have returned an error. By not returning an // error, WithLabelValues allows shortcuts like // myVec.WithLabelValues("404", "GET").Add(42) func (m *UntypedVec) WithLabelValues(lvs ...string) Untyped { return m.MetricVec.WithLabelValues(lvs...).(Untyped) } // With works as GetMetricWith, but panics where GetMetricWithLabels would have // returned an error. By not returning an error, With allows shortcuts like // myVec.With(Labels{"code": "404", "method": "GET"}).Add(42) func (m *UntypedVec) With(labels Labels) Untyped { return m.MetricVec.With(labels).(Untyped) } // UntypedFunc is an Untyped whose value is determined at collect time by // calling a provided function. // // To create UntypedFunc instances, use NewUntypedFunc. type UntypedFunc interface { Metric Collector } // NewUntypedFunc creates a new UntypedFunc based on the provided // UntypedOpts. The value reported is determined by calling the given function // from within the Write method. Take into account that metric collection may // happen concurrently. If that results in concurrent calls to Write, like in // the case where an UntypedFunc is directly registered with Prometheus, the // provided function must be concurrency-safe. func NewUntypedFunc(opts UntypedOpts, function func() float64) UntypedFunc { return newValueFunc(NewDesc( BuildFQName(opts.Namespace, opts.Subsystem, opts.Name), opts.Help, nil, opts.ConstLabels, ), UntypedValue, function) } golang-prometheus-client-0.7.0+ds/prometheus/value.go000066400000000000000000000151241256572052500227060ustar00rootroot00000000000000// Copyright 2014 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package prometheus import ( "errors" "fmt" "math" "sort" "sync/atomic" dto "github.com/prometheus/client_model/go" "github.com/golang/protobuf/proto" ) // ValueType is an enumeration of metric types that represent a simple value. type ValueType int // Possible values for the ValueType enum. const ( _ ValueType = iota CounterValue GaugeValue UntypedValue ) var errInconsistentCardinality = errors.New("inconsistent label cardinality") // value is a generic metric for simple values. It implements Metric, Collector, // Counter, Gauge, and Untyped. Its effective type is determined by // ValueType. This is a low-level building block used by the library to back the // implementations of Counter, Gauge, and Untyped. type value struct { // valBits containst the bits of the represented float64 value. It has // to go first in the struct to guarantee alignment for atomic // operations. http://golang.org/pkg/sync/atomic/#pkg-note-BUG valBits uint64 SelfCollector desc *Desc valType ValueType labelPairs []*dto.LabelPair } // newValue returns a newly allocated value with the given Desc, ValueType, // sample value and label values. It panics if the number of label // values is different from the number of variable labels in Desc. func newValue(desc *Desc, valueType ValueType, val float64, labelValues ...string) *value { if len(labelValues) != len(desc.variableLabels) { panic(errInconsistentCardinality) } result := &value{ desc: desc, valType: valueType, valBits: math.Float64bits(val), labelPairs: makeLabelPairs(desc, labelValues), } result.Init(result) return result } func (v *value) Desc() *Desc { return v.desc } func (v *value) Set(val float64) { atomic.StoreUint64(&v.valBits, math.Float64bits(val)) } func (v *value) Inc() { v.Add(1) } func (v *value) Dec() { v.Add(-1) } func (v *value) Add(val float64) { for { oldBits := atomic.LoadUint64(&v.valBits) newBits := math.Float64bits(math.Float64frombits(oldBits) + val) if atomic.CompareAndSwapUint64(&v.valBits, oldBits, newBits) { return } } } func (v *value) Sub(val float64) { v.Add(val * -1) } func (v *value) Write(out *dto.Metric) error { val := math.Float64frombits(atomic.LoadUint64(&v.valBits)) return populateMetric(v.valType, val, v.labelPairs, out) } // valueFunc is a generic metric for simple values retrieved on collect time // from a function. It implements Metric and Collector. Its effective type is // determined by ValueType. This is a low-level building block used by the // library to back the implementations of CounterFunc, GaugeFunc, and // UntypedFunc. type valueFunc struct { SelfCollector desc *Desc valType ValueType function func() float64 labelPairs []*dto.LabelPair } // newValueFunc returns a newly allocated valueFunc with the given Desc and // ValueType. The value reported is determined by calling the given function // from within the Write method. Take into account that metric collection may // happen concurrently. If that results in concurrent calls to Write, like in // the case where a valueFunc is directly registered with Prometheus, the // provided function must be concurrency-safe. func newValueFunc(desc *Desc, valueType ValueType, function func() float64) *valueFunc { result := &valueFunc{ desc: desc, valType: valueType, function: function, labelPairs: makeLabelPairs(desc, nil), } result.Init(result) return result } func (v *valueFunc) Desc() *Desc { return v.desc } func (v *valueFunc) Write(out *dto.Metric) error { return populateMetric(v.valType, v.function(), v.labelPairs, out) } // NewConstMetric returns a metric with one fixed value that cannot be // changed. Users of this package will not have much use for it in regular // operations. However, when implementing custom Collectors, it is useful as a // throw-away metric that is generated on the fly to send it to Prometheus in // the Collect method. NewConstMetric returns an error if the length of // labelValues is not consistent with the variable labels in Desc. func NewConstMetric(desc *Desc, valueType ValueType, value float64, labelValues ...string) (Metric, error) { if len(desc.variableLabels) != len(labelValues) { return nil, errInconsistentCardinality } return &constMetric{ desc: desc, valType: valueType, val: value, labelPairs: makeLabelPairs(desc, labelValues), }, nil } // MustNewConstMetric is a version of NewConstMetric that panics where // NewConstMetric would have returned an error. func MustNewConstMetric(desc *Desc, valueType ValueType, value float64, labelValues ...string) Metric { m, err := NewConstMetric(desc, valueType, value, labelValues...) if err != nil { panic(err) } return m } type constMetric struct { desc *Desc valType ValueType val float64 labelPairs []*dto.LabelPair } func (m *constMetric) Desc() *Desc { return m.desc } func (m *constMetric) Write(out *dto.Metric) error { return populateMetric(m.valType, m.val, m.labelPairs, out) } func populateMetric( t ValueType, v float64, labelPairs []*dto.LabelPair, m *dto.Metric, ) error { m.Label = labelPairs switch t { case CounterValue: m.Counter = &dto.Counter{Value: proto.Float64(v)} case GaugeValue: m.Gauge = &dto.Gauge{Value: proto.Float64(v)} case UntypedValue: m.Untyped = &dto.Untyped{Value: proto.Float64(v)} default: return fmt.Errorf("encountered unknown type %v", t) } return nil } func makeLabelPairs(desc *Desc, labelValues []string) []*dto.LabelPair { totalLen := len(desc.variableLabels) + len(desc.constLabelPairs) if totalLen == 0 { // Super fast path. return nil } if len(desc.variableLabels) == 0 { // Moderately fast path. return desc.constLabelPairs } labelPairs := make([]*dto.LabelPair, 0, totalLen) for i, n := range desc.variableLabels { labelPairs = append(labelPairs, &dto.LabelPair{ Name: proto.String(n), Value: proto.String(labelValues[i]), }) } for _, lp := range desc.constLabelPairs { labelPairs = append(labelPairs, lp) } sort.Sort(LabelPairSorter(labelPairs)) return labelPairs } golang-prometheus-client-0.7.0+ds/prometheus/vec.go000066400000000000000000000177601256572052500223570ustar00rootroot00000000000000// Copyright 2014 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package prometheus import ( "bytes" "fmt" "hash" "sync" ) // MetricVec is a Collector to bundle metrics of the same name that // differ in their label values. MetricVec is usually not used directly but as a // building block for implementations of vectors of a given metric // type. GaugeVec, CounterVec, SummaryVec, and UntypedVec are examples already // provided in this package. type MetricVec struct { mtx sync.RWMutex // Protects not only children, but also hash and buf. children map[uint64]Metric desc *Desc // hash is our own hash instance to avoid repeated allocations. hash hash.Hash64 // buf is used to copy string contents into it for hashing, // again to avoid allocations. buf bytes.Buffer newMetric func(labelValues ...string) Metric } // Describe implements Collector. The length of the returned slice // is always one. func (m *MetricVec) Describe(ch chan<- *Desc) { ch <- m.desc } // Collect implements Collector. func (m *MetricVec) Collect(ch chan<- Metric) { m.mtx.RLock() defer m.mtx.RUnlock() for _, metric := range m.children { ch <- metric } } // GetMetricWithLabelValues returns the Metric for the given slice of label // values (same order as the VariableLabels in Desc). If that combination of // label values is accessed for the first time, a new Metric is created. // // It is possible to call this method without using the returned Metric to only // create the new Metric but leave it at its start value (e.g. a Summary or // Histogram without any observations). See also the SummaryVec example. // // Keeping the Metric for later use is possible (and should be considered if // performance is critical), but keep in mind that Reset, DeleteLabelValues and // Delete can be used to delete the Metric from the MetricVec. In that case, the // Metric will still exist, but it will not be exported anymore, even if a // Metric with the same label values is created later. See also the CounterVec // example. // // An error is returned if the number of label values is not the same as the // number of VariableLabels in Desc. // // Note that for more than one label value, this method is prone to mistakes // caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as // an alternative to avoid that type of mistake. For higher label numbers, the // latter has a much more readable (albeit more verbose) syntax, but it comes // with a performance overhead (for creating and processing the Labels map). // See also the GaugeVec example. func (m *MetricVec) GetMetricWithLabelValues(lvs ...string) (Metric, error) { m.mtx.Lock() defer m.mtx.Unlock() h, err := m.hashLabelValues(lvs) if err != nil { return nil, err } return m.getOrCreateMetric(h, lvs...), nil } // GetMetricWith returns the Metric for the given Labels map (the label names // must match those of the VariableLabels in Desc). If that label map is // accessed for the first time, a new Metric is created. Implications of // creating a Metric without using it and keeping the Metric for later use are // the same as for GetMetricWithLabelValues. // // An error is returned if the number and names of the Labels are inconsistent // with those of the VariableLabels in Desc. // // This method is used for the same purpose as // GetMetricWithLabelValues(...string). See there for pros and cons of the two // methods. func (m *MetricVec) GetMetricWith(labels Labels) (Metric, error) { m.mtx.Lock() defer m.mtx.Unlock() h, err := m.hashLabels(labels) if err != nil { return nil, err } lvs := make([]string, len(labels)) for i, label := range m.desc.variableLabels { lvs[i] = labels[label] } return m.getOrCreateMetric(h, lvs...), nil } // WithLabelValues works as GetMetricWithLabelValues, but panics if an error // occurs. The method allows neat syntax like: // httpReqs.WithLabelValues("404", "POST").Inc() func (m *MetricVec) WithLabelValues(lvs ...string) Metric { metric, err := m.GetMetricWithLabelValues(lvs...) if err != nil { panic(err) } return metric } // With works as GetMetricWith, but panics if an error occurs. The method allows // neat syntax like: // httpReqs.With(Labels{"status":"404", "method":"POST"}).Inc() func (m *MetricVec) With(labels Labels) Metric { metric, err := m.GetMetricWith(labels) if err != nil { panic(err) } return metric } // DeleteLabelValues removes the metric where the variable labels are the same // as those passed in as labels (same order as the VariableLabels in Desc). It // returns true if a metric was deleted. // // It is not an error if the number of label values is not the same as the // number of VariableLabels in Desc. However, such inconsistent label count can // never match an actual Metric, so the method will always return false in that // case. // // Note that for more than one label value, this method is prone to mistakes // caused by an incorrect order of arguments. Consider Delete(Labels) as an // alternative to avoid that type of mistake. For higher label numbers, the // latter has a much more readable (albeit more verbose) syntax, but it comes // with a performance overhead (for creating and processing the Labels map). // See also the CounterVec example. func (m *MetricVec) DeleteLabelValues(lvs ...string) bool { m.mtx.Lock() defer m.mtx.Unlock() h, err := m.hashLabelValues(lvs) if err != nil { return false } if _, has := m.children[h]; !has { return false } delete(m.children, h) return true } // Delete deletes the metric where the variable labels are the same as those // passed in as labels. It returns true if a metric was deleted. // // It is not an error if the number and names of the Labels are inconsistent // with those of the VariableLabels in the Desc of the MetricVec. However, such // inconsistent Labels can never match an actual Metric, so the method will // always return false in that case. // // This method is used for the same purpose as DeleteLabelValues(...string). See // there for pros and cons of the two methods. func (m *MetricVec) Delete(labels Labels) bool { m.mtx.Lock() defer m.mtx.Unlock() h, err := m.hashLabels(labels) if err != nil { return false } if _, has := m.children[h]; !has { return false } delete(m.children, h) return true } // Reset deletes all metrics in this vector. func (m *MetricVec) Reset() { m.mtx.Lock() defer m.mtx.Unlock() for h := range m.children { delete(m.children, h) } } func (m *MetricVec) hashLabelValues(vals []string) (uint64, error) { if len(vals) != len(m.desc.variableLabels) { return 0, errInconsistentCardinality } m.hash.Reset() for _, val := range vals { m.buf.Reset() m.buf.WriteString(val) m.hash.Write(m.buf.Bytes()) } return m.hash.Sum64(), nil } func (m *MetricVec) hashLabels(labels Labels) (uint64, error) { if len(labels) != len(m.desc.variableLabels) { return 0, errInconsistentCardinality } m.hash.Reset() for _, label := range m.desc.variableLabels { val, ok := labels[label] if !ok { return 0, fmt.Errorf("label name %q missing in label map", label) } m.buf.Reset() m.buf.WriteString(val) m.hash.Write(m.buf.Bytes()) } return m.hash.Sum64(), nil } func (m *MetricVec) getOrCreateMetric(hash uint64, labelValues ...string) Metric { metric, ok := m.children[hash] if !ok { // Copy labelValues. Otherwise, they would be allocated even if we don't go // down this code path. copiedLabelValues := append(make([]string, 0, len(labelValues)), labelValues...) metric = m.newMetric(copiedLabelValues...) m.children[hash] = metric } return metric } golang-prometheus-client-0.7.0+ds/prometheus/vec_test.go000066400000000000000000000056711256572052500234140ustar00rootroot00000000000000// Copyright 2014 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package prometheus import ( "hash/fnv" "testing" ) func TestDelete(t *testing.T) { desc := NewDesc("test", "helpless", []string{"l1", "l2"}, nil) vec := MetricVec{ children: map[uint64]Metric{}, desc: desc, hash: fnv.New64a(), newMetric: func(lvs ...string) Metric { return newValue(desc, UntypedValue, 0, lvs...) }, } if got, want := vec.Delete(Labels{"l1": "v1", "l2": "v2"}), false; got != want { t.Errorf("got %v, want %v", got, want) } vec.With(Labels{"l1": "v1", "l2": "v2"}).(Untyped).Set(42) if got, want := vec.Delete(Labels{"l1": "v1", "l2": "v2"}), true; got != want { t.Errorf("got %v, want %v", got, want) } if got, want := vec.Delete(Labels{"l1": "v1", "l2": "v2"}), false; got != want { t.Errorf("got %v, want %v", got, want) } vec.With(Labels{"l1": "v1", "l2": "v2"}).(Untyped).Set(42) if got, want := vec.Delete(Labels{"l2": "v2", "l1": "v1"}), true; got != want { t.Errorf("got %v, want %v", got, want) } if got, want := vec.Delete(Labels{"l2": "v2", "l1": "v1"}), false; got != want { t.Errorf("got %v, want %v", got, want) } vec.With(Labels{"l1": "v1", "l2": "v2"}).(Untyped).Set(42) if got, want := vec.Delete(Labels{"l2": "v1", "l1": "v2"}), false; got != want { t.Errorf("got %v, want %v", got, want) } if got, want := vec.Delete(Labels{"l1": "v1"}), false; got != want { t.Errorf("got %v, want %v", got, want) } } func TestDeleteLabelValues(t *testing.T) { desc := NewDesc("test", "helpless", []string{"l1", "l2"}, nil) vec := MetricVec{ children: map[uint64]Metric{}, desc: desc, hash: fnv.New64a(), newMetric: func(lvs ...string) Metric { return newValue(desc, UntypedValue, 0, lvs...) }, } if got, want := vec.DeleteLabelValues("v1", "v2"), false; got != want { t.Errorf("got %v, want %v", got, want) } vec.With(Labels{"l1": "v1", "l2": "v2"}).(Untyped).Set(42) if got, want := vec.DeleteLabelValues("v1", "v2"), true; got != want { t.Errorf("got %v, want %v", got, want) } if got, want := vec.DeleteLabelValues("v1", "v2"), false; got != want { t.Errorf("got %v, want %v", got, want) } vec.With(Labels{"l1": "v1", "l2": "v2"}).(Untyped).Set(42) if got, want := vec.DeleteLabelValues("v2", "v1"), false; got != want { t.Errorf("got %v, want %v", got, want) } if got, want := vec.DeleteLabelValues("v1"), false; got != want { t.Errorf("got %v, want %v", got, want) } } golang-prometheus-client-0.7.0+ds/text/000077500000000000000000000000001256572052500200315ustar00rootroot00000000000000golang-prometheus-client-0.7.0+ds/text/bench_test.go000066400000000000000000000116371256572052500225060ustar00rootroot00000000000000// Copyright 2015 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package text import ( "bytes" "compress/gzip" "io" "io/ioutil" "testing" "github.com/matttproud/golang_protobuf_extensions/pbutil" dto "github.com/prometheus/client_model/go" ) // Benchmarks to show how much penalty text format parsing actually inflicts. // // Example results on Linux 3.13.0, Intel(R) Core(TM) i7-4700MQ CPU @ 2.40GHz, go1.4. // // BenchmarkParseText 1000 1188535 ns/op 205085 B/op 6135 allocs/op // BenchmarkParseTextGzip 1000 1376567 ns/op 246224 B/op 6151 allocs/op // BenchmarkParseProto 10000 172790 ns/op 52258 B/op 1160 allocs/op // BenchmarkParseProtoGzip 5000 324021 ns/op 94931 B/op 1211 allocs/op // BenchmarkParseProtoMap 10000 187946 ns/op 58714 B/op 1203 allocs/op // // CONCLUSION: The overhead for the map is negligible. Text format needs ~5x more allocations. // Without compression, it needs ~7x longer, but with compression (the more relevant scenario), // the difference becomes less relevant, only ~4x. // // The test data contains 248 samples. // // BenchmarkProcessor002ParseOnly in the extraction package is not quite // comparable to the benchmarks here, but it gives an idea: JSON parsing is even // slower than text parsing and needs a comparable amount of allocs. // BenchmarkParseText benchmarks the parsing of a text-format scrape into metric // family DTOs. func BenchmarkParseText(b *testing.B) { b.StopTimer() data, err := ioutil.ReadFile("testdata/text") if err != nil { b.Fatal(err) } b.StartTimer() for i := 0; i < b.N; i++ { if _, err := parser.TextToMetricFamilies(bytes.NewReader(data)); err != nil { b.Fatal(err) } } } // BenchmarkParseTextGzip benchmarks the parsing of a gzipped text-format scrape // into metric family DTOs. func BenchmarkParseTextGzip(b *testing.B) { b.StopTimer() data, err := ioutil.ReadFile("testdata/text.gz") if err != nil { b.Fatal(err) } b.StartTimer() for i := 0; i < b.N; i++ { in, err := gzip.NewReader(bytes.NewReader(data)) if err != nil { b.Fatal(err) } if _, err := parser.TextToMetricFamilies(in); err != nil { b.Fatal(err) } } } // BenchmarkParseProto benchmarks the parsing of a protobuf-format scrape into // metric family DTOs. Note that this does not build a map of metric families // (as the text version does), because it is not required for Prometheus // ingestion either. (However, it is required for the text-format parsing, as // the metric family might be sprinkled all over the text, while the // protobuf-format guarantees bundling at one place.) func BenchmarkParseProto(b *testing.B) { b.StopTimer() data, err := ioutil.ReadFile("testdata/protobuf") if err != nil { b.Fatal(err) } b.StartTimer() for i := 0; i < b.N; i++ { family := &dto.MetricFamily{} in := bytes.NewReader(data) for { family.Reset() if _, err := pbutil.ReadDelimited(in, family); err != nil { if err == io.EOF { break } b.Fatal(err) } } } } // BenchmarkParseProtoGzip is like BenchmarkParseProto above, but parses gzipped // protobuf format. func BenchmarkParseProtoGzip(b *testing.B) { b.StopTimer() data, err := ioutil.ReadFile("testdata/protobuf.gz") if err != nil { b.Fatal(err) } b.StartTimer() for i := 0; i < b.N; i++ { family := &dto.MetricFamily{} in, err := gzip.NewReader(bytes.NewReader(data)) if err != nil { b.Fatal(err) } for { family.Reset() if _, err := pbutil.ReadDelimited(in, family); err != nil { if err == io.EOF { break } b.Fatal(err) } } } } // BenchmarkParseProtoMap is like BenchmarkParseProto but DOES put the parsed // metric family DTOs into a map. This is not happening during Prometheus // ingestion. It is just here to measure the overhead of that map creation and // separate it from the overhead of the text format parsing. func BenchmarkParseProtoMap(b *testing.B) { b.StopTimer() data, err := ioutil.ReadFile("testdata/protobuf") if err != nil { b.Fatal(err) } b.StartTimer() for i := 0; i < b.N; i++ { families := map[string]*dto.MetricFamily{} in := bytes.NewReader(data) for { family := &dto.MetricFamily{} if _, err := pbutil.ReadDelimited(in, family); err != nil { if err == io.EOF { break } b.Fatal(err) } families[family.GetName()] = family } } } golang-prometheus-client-0.7.0+ds/text/create.go000066400000000000000000000177031256572052500216330ustar00rootroot00000000000000// Copyright 2014 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package text contains helper functions to parse and create text-based // exchange formats. The package currently supports (only) version 0.0.4 of the // exchange format. Should other versions be supported in the future, some // versioning scheme has to be applied. Possibilities include separate packages // or separate functions. The best way depends on the nature of future changes, // which is the reason why no versioning scheme has been applied prematurely // here. package text import ( "bytes" "fmt" "io" "math" "strings" "github.com/prometheus/client_golang/model" dto "github.com/prometheus/client_model/go" ) // MetricFamilyToText converts a MetricFamily proto message into text format and // writes the resulting lines to 'out'. It returns the number of bytes written // and any error encountered. This function does not perform checks on the // content of the metric and label names, i.e. invalid metric or label names // will result in invalid text format output. // This method fulfills the type 'prometheus.encoder'. func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) { var written int // Fail-fast checks. if len(in.Metric) == 0 { return written, fmt.Errorf("MetricFamily has no metrics: %s", in) } name := in.GetName() if name == "" { return written, fmt.Errorf("MetricFamily has no name: %s", in) } if in.Type == nil { return written, fmt.Errorf("MetricFamily has no type: %s", in) } // Comments, first HELP, then TYPE. if in.Help != nil { n, err := fmt.Fprintf( out, "# HELP %s %s\n", name, escapeString(*in.Help, false), ) written += n if err != nil { return written, err } } metricType := in.GetType() n, err := fmt.Fprintf( out, "# TYPE %s %s\n", name, strings.ToLower(metricType.String()), ) written += n if err != nil { return written, err } // Finally the samples, one line for each. for _, metric := range in.Metric { switch metricType { case dto.MetricType_COUNTER: if metric.Counter == nil { return written, fmt.Errorf( "expected counter in metric %s %s", name, metric, ) } n, err = writeSample( name, metric, "", "", metric.Counter.GetValue(), out, ) case dto.MetricType_GAUGE: if metric.Gauge == nil { return written, fmt.Errorf( "expected gauge in metric %s %s", name, metric, ) } n, err = writeSample( name, metric, "", "", metric.Gauge.GetValue(), out, ) case dto.MetricType_UNTYPED: if metric.Untyped == nil { return written, fmt.Errorf( "expected untyped in metric %s %s", name, metric, ) } n, err = writeSample( name, metric, "", "", metric.Untyped.GetValue(), out, ) case dto.MetricType_SUMMARY: if metric.Summary == nil { return written, fmt.Errorf( "expected summary in metric %s %s", name, metric, ) } for _, q := range metric.Summary.Quantile { n, err = writeSample( name, metric, model.QuantileLabel, fmt.Sprint(q.GetQuantile()), q.GetValue(), out, ) written += n if err != nil { return written, err } } n, err = writeSample( name+"_sum", metric, "", "", metric.Summary.GetSampleSum(), out, ) if err != nil { return written, err } written += n n, err = writeSample( name+"_count", metric, "", "", float64(metric.Summary.GetSampleCount()), out, ) case dto.MetricType_HISTOGRAM: if metric.Histogram == nil { return written, fmt.Errorf( "expected histogram in metric %s %s", name, metric, ) } infSeen := false for _, q := range metric.Histogram.Bucket { n, err = writeSample( name+"_bucket", metric, model.BucketLabel, fmt.Sprint(q.GetUpperBound()), float64(q.GetCumulativeCount()), out, ) written += n if err != nil { return written, err } if math.IsInf(q.GetUpperBound(), +1) { infSeen = true } } if !infSeen { n, err = writeSample( name+"_bucket", metric, model.BucketLabel, "+Inf", float64(metric.Histogram.GetSampleCount()), out, ) if err != nil { return written, err } written += n } n, err = writeSample( name+"_sum", metric, "", "", metric.Histogram.GetSampleSum(), out, ) if err != nil { return written, err } written += n n, err = writeSample( name+"_count", metric, "", "", float64(metric.Histogram.GetSampleCount()), out, ) default: return written, fmt.Errorf( "unexpected type in metric %s %s", name, metric, ) } written += n if err != nil { return written, err } } return written, nil } // writeSample writes a single sample in text format to out, given the metric // name, the metric proto message itself, optionally an additional label name // and value (use empty strings if not required), and the value. The function // returns the number of bytes written and any error encountered. func writeSample( name string, metric *dto.Metric, additionalLabelName, additionalLabelValue string, value float64, out io.Writer, ) (int, error) { var written int n, err := fmt.Fprint(out, name) written += n if err != nil { return written, err } n, err = labelPairsToText( metric.Label, additionalLabelName, additionalLabelValue, out, ) written += n if err != nil { return written, err } n, err = fmt.Fprintf(out, " %v", value) written += n if err != nil { return written, err } if metric.TimestampMs != nil { n, err = fmt.Fprintf(out, " %v", *metric.TimestampMs) written += n if err != nil { return written, err } } n, err = out.Write([]byte{'\n'}) written += n if err != nil { return written, err } return written, nil } // labelPairsToText converts a slice of LabelPair proto messages plus the // explicitly given additional label pair into text formatted as required by the // text format and writes it to 'out'. An empty slice in combination with an // empty string 'additionalLabelName' results in nothing being // written. Otherwise, the label pairs are written, escaped as required by the // text format, and enclosed in '{...}'. The function returns the number of // bytes written and any error encountered. func labelPairsToText( in []*dto.LabelPair, additionalLabelName, additionalLabelValue string, out io.Writer, ) (int, error) { if len(in) == 0 && additionalLabelName == "" { return 0, nil } var written int separator := '{' for _, lp := range in { n, err := fmt.Fprintf( out, `%c%s="%s"`, separator, lp.GetName(), escapeString(lp.GetValue(), true), ) written += n if err != nil { return written, err } separator = ',' } if additionalLabelName != "" { n, err := fmt.Fprintf( out, `%c%s="%s"`, separator, additionalLabelName, escapeString(additionalLabelValue, true), ) written += n if err != nil { return written, err } } n, err := out.Write([]byte{'}'}) written += n if err != nil { return written, err } return written, nil } // escapeString replaces '\' by '\\', new line character by '\n', and - if // includeDoubleQuote is true - '"' by '\"'. func escapeString(v string, includeDoubleQuote bool) string { result := bytes.NewBuffer(make([]byte, 0, len(v))) for _, c := range v { switch { case c == '\\': result.WriteString(`\\`) case includeDoubleQuote && c == '"': result.WriteString(`\"`) case c == '\n': result.WriteString(`\n`) default: result.WriteRune(c) } } return result.String() } golang-prometheus-client-0.7.0+ds/text/create_test.go000066400000000000000000000265511256572052500226730ustar00rootroot00000000000000// Copyright 2014 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package text import ( "bytes" "math" "strings" "testing" "github.com/golang/protobuf/proto" dto "github.com/prometheus/client_model/go" ) func testCreate(t testing.TB) { var scenarios = []struct { in *dto.MetricFamily out string }{ // 0: Counter, NaN as value, timestamp given. { in: &dto.MetricFamily{ Name: proto.String("name"), Help: proto.String("two-line\n doc str\\ing"), Type: dto.MetricType_COUNTER.Enum(), Metric: []*dto.Metric{ &dto.Metric{ Label: []*dto.LabelPair{ &dto.LabelPair{ Name: proto.String("labelname"), Value: proto.String("val1"), }, &dto.LabelPair{ Name: proto.String("basename"), Value: proto.String("basevalue"), }, }, Counter: &dto.Counter{ Value: proto.Float64(math.NaN()), }, }, &dto.Metric{ Label: []*dto.LabelPair{ &dto.LabelPair{ Name: proto.String("labelname"), Value: proto.String("val2"), }, &dto.LabelPair{ Name: proto.String("basename"), Value: proto.String("basevalue"), }, }, Counter: &dto.Counter{ Value: proto.Float64(.23), }, TimestampMs: proto.Int64(1234567890), }, }, }, out: `# HELP name two-line\n doc str\\ing # TYPE name counter name{labelname="val1",basename="basevalue"} NaN name{labelname="val2",basename="basevalue"} 0.23 1234567890 `, }, // 1: Gauge, some escaping required, +Inf as value, multi-byte characters in label values. { in: &dto.MetricFamily{ Name: proto.String("gauge_name"), Help: proto.String("gauge\ndoc\nstr\"ing"), Type: dto.MetricType_GAUGE.Enum(), Metric: []*dto.Metric{ &dto.Metric{ Label: []*dto.LabelPair{ &dto.LabelPair{ Name: proto.String("name_1"), Value: proto.String("val with\nnew line"), }, &dto.LabelPair{ Name: proto.String("name_2"), Value: proto.String("val with \\backslash and \"quotes\""), }, }, Gauge: &dto.Gauge{ Value: proto.Float64(math.Inf(+1)), }, }, &dto.Metric{ Label: []*dto.LabelPair{ &dto.LabelPair{ Name: proto.String("name_1"), Value: proto.String("Björn"), }, &dto.LabelPair{ Name: proto.String("name_2"), Value: proto.String("佖佥"), }, }, Gauge: &dto.Gauge{ Value: proto.Float64(3.14E42), }, }, }, }, out: `# HELP gauge_name gauge\ndoc\nstr"ing # TYPE gauge_name gauge gauge_name{name_1="val with\nnew line",name_2="val with \\backslash and \"quotes\""} +Inf gauge_name{name_1="Björn",name_2="佖佥"} 3.14e+42 `, }, // 2: Untyped, no help, one sample with no labels and -Inf as value, another sample with one label. { in: &dto.MetricFamily{ Name: proto.String("untyped_name"), Type: dto.MetricType_UNTYPED.Enum(), Metric: []*dto.Metric{ &dto.Metric{ Untyped: &dto.Untyped{ Value: proto.Float64(math.Inf(-1)), }, }, &dto.Metric{ Label: []*dto.LabelPair{ &dto.LabelPair{ Name: proto.String("name_1"), Value: proto.String("value 1"), }, }, Untyped: &dto.Untyped{ Value: proto.Float64(-1.23e-45), }, }, }, }, out: `# TYPE untyped_name untyped untyped_name -Inf untyped_name{name_1="value 1"} -1.23e-45 `, }, // 3: Summary. { in: &dto.MetricFamily{ Name: proto.String("summary_name"), Help: proto.String("summary docstring"), Type: dto.MetricType_SUMMARY.Enum(), Metric: []*dto.Metric{ &dto.Metric{ Summary: &dto.Summary{ SampleCount: proto.Uint64(42), SampleSum: proto.Float64(-3.4567), Quantile: []*dto.Quantile{ &dto.Quantile{ Quantile: proto.Float64(0.5), Value: proto.Float64(-1.23), }, &dto.Quantile{ Quantile: proto.Float64(0.9), Value: proto.Float64(.2342354), }, &dto.Quantile{ Quantile: proto.Float64(0.99), Value: proto.Float64(0), }, }, }, }, &dto.Metric{ Label: []*dto.LabelPair{ &dto.LabelPair{ Name: proto.String("name_1"), Value: proto.String("value 1"), }, &dto.LabelPair{ Name: proto.String("name_2"), Value: proto.String("value 2"), }, }, Summary: &dto.Summary{ SampleCount: proto.Uint64(4711), SampleSum: proto.Float64(2010.1971), Quantile: []*dto.Quantile{ &dto.Quantile{ Quantile: proto.Float64(0.5), Value: proto.Float64(1), }, &dto.Quantile{ Quantile: proto.Float64(0.9), Value: proto.Float64(2), }, &dto.Quantile{ Quantile: proto.Float64(0.99), Value: proto.Float64(3), }, }, }, }, }, }, out: `# HELP summary_name summary docstring # TYPE summary_name summary summary_name{quantile="0.5"} -1.23 summary_name{quantile="0.9"} 0.2342354 summary_name{quantile="0.99"} 0 summary_name_sum -3.4567 summary_name_count 42 summary_name{name_1="value 1",name_2="value 2",quantile="0.5"} 1 summary_name{name_1="value 1",name_2="value 2",quantile="0.9"} 2 summary_name{name_1="value 1",name_2="value 2",quantile="0.99"} 3 summary_name_sum{name_1="value 1",name_2="value 2"} 2010.1971 summary_name_count{name_1="value 1",name_2="value 2"} 4711 `, }, // 4: Histogram { in: &dto.MetricFamily{ Name: proto.String("request_duration_microseconds"), Help: proto.String("The response latency."), Type: dto.MetricType_HISTOGRAM.Enum(), Metric: []*dto.Metric{ &dto.Metric{ Histogram: &dto.Histogram{ SampleCount: proto.Uint64(2693), SampleSum: proto.Float64(1756047.3), Bucket: []*dto.Bucket{ &dto.Bucket{ UpperBound: proto.Float64(100), CumulativeCount: proto.Uint64(123), }, &dto.Bucket{ UpperBound: proto.Float64(120), CumulativeCount: proto.Uint64(412), }, &dto.Bucket{ UpperBound: proto.Float64(144), CumulativeCount: proto.Uint64(592), }, &dto.Bucket{ UpperBound: proto.Float64(172.8), CumulativeCount: proto.Uint64(1524), }, &dto.Bucket{ UpperBound: proto.Float64(math.Inf(+1)), CumulativeCount: proto.Uint64(2693), }, }, }, }, }, }, out: `# HELP request_duration_microseconds The response latency. # TYPE request_duration_microseconds histogram request_duration_microseconds_bucket{le="100"} 123 request_duration_microseconds_bucket{le="120"} 412 request_duration_microseconds_bucket{le="144"} 592 request_duration_microseconds_bucket{le="172.8"} 1524 request_duration_microseconds_bucket{le="+Inf"} 2693 request_duration_microseconds_sum 1.7560473e+06 request_duration_microseconds_count 2693 `, }, // 5: Histogram with missing +Inf bucket. { in: &dto.MetricFamily{ Name: proto.String("request_duration_microseconds"), Help: proto.String("The response latency."), Type: dto.MetricType_HISTOGRAM.Enum(), Metric: []*dto.Metric{ &dto.Metric{ Histogram: &dto.Histogram{ SampleCount: proto.Uint64(2693), SampleSum: proto.Float64(1756047.3), Bucket: []*dto.Bucket{ &dto.Bucket{ UpperBound: proto.Float64(100), CumulativeCount: proto.Uint64(123), }, &dto.Bucket{ UpperBound: proto.Float64(120), CumulativeCount: proto.Uint64(412), }, &dto.Bucket{ UpperBound: proto.Float64(144), CumulativeCount: proto.Uint64(592), }, &dto.Bucket{ UpperBound: proto.Float64(172.8), CumulativeCount: proto.Uint64(1524), }, }, }, }, }, }, out: `# HELP request_duration_microseconds The response latency. # TYPE request_duration_microseconds histogram request_duration_microseconds_bucket{le="100"} 123 request_duration_microseconds_bucket{le="120"} 412 request_duration_microseconds_bucket{le="144"} 592 request_duration_microseconds_bucket{le="172.8"} 1524 request_duration_microseconds_bucket{le="+Inf"} 2693 request_duration_microseconds_sum 1.7560473e+06 request_duration_microseconds_count 2693 `, }, } for i, scenario := range scenarios { out := bytes.NewBuffer(make([]byte, 0, len(scenario.out))) n, err := MetricFamilyToText(out, scenario.in) if err != nil { t.Errorf("%d. error: %s", i, err) continue } if expected, got := len(scenario.out), n; expected != got { t.Errorf( "%d. expected %d bytes written, got %d", i, expected, got, ) } if expected, got := scenario.out, out.String(); expected != got { t.Errorf( "%d. expected out=%q, got %q", i, expected, got, ) } } } func TestCreate(t *testing.T) { testCreate(t) } func BenchmarkCreate(b *testing.B) { for i := 0; i < b.N; i++ { testCreate(b) } } func testCreateError(t testing.TB) { var scenarios = []struct { in *dto.MetricFamily err string }{ // 0: No metric. { in: &dto.MetricFamily{ Name: proto.String("name"), Help: proto.String("doc string"), Type: dto.MetricType_COUNTER.Enum(), Metric: []*dto.Metric{}, }, err: "MetricFamily has no metrics", }, // 1: No metric name. { in: &dto.MetricFamily{ Help: proto.String("doc string"), Type: dto.MetricType_UNTYPED.Enum(), Metric: []*dto.Metric{ &dto.Metric{ Untyped: &dto.Untyped{ Value: proto.Float64(math.Inf(-1)), }, }, }, }, err: "MetricFamily has no name", }, // 2: No metric type. { in: &dto.MetricFamily{ Name: proto.String("name"), Help: proto.String("doc string"), Metric: []*dto.Metric{ &dto.Metric{ Untyped: &dto.Untyped{ Value: proto.Float64(math.Inf(-1)), }, }, }, }, err: "MetricFamily has no type", }, // 3: Wrong type. { in: &dto.MetricFamily{ Name: proto.String("name"), Help: proto.String("doc string"), Type: dto.MetricType_COUNTER.Enum(), Metric: []*dto.Metric{ &dto.Metric{ Untyped: &dto.Untyped{ Value: proto.Float64(math.Inf(-1)), }, }, }, }, err: "expected counter in metric", }, } for i, scenario := range scenarios { var out bytes.Buffer _, err := MetricFamilyToText(&out, scenario.in) if err == nil { t.Errorf("%d. expected error, got nil", i) continue } if expected, got := scenario.err, err.Error(); strings.Index(got, expected) != 0 { t.Errorf( "%d. expected error starting with %q, got %q", i, expected, got, ) } } } func TestCreateError(t *testing.T) { testCreateError(t) } func BenchmarkCreateError(b *testing.B) { for i := 0; i < b.N; i++ { testCreateError(b) } } golang-prometheus-client-0.7.0+ds/text/parse.go000066400000000000000000000603561256572052500215040ustar00rootroot00000000000000// Copyright 2014 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package text import ( "bufio" "bytes" "fmt" "io" "math" "strconv" "strings" dto "github.com/prometheus/client_model/go" "github.com/golang/protobuf/proto" "github.com/prometheus/client_golang/model" ) // A stateFn is a function that represents a state in a state machine. By // executing it, the state is progressed to the next state. The stateFn returns // another stateFn, which represents the new state. The end state is represented // by nil. type stateFn func() stateFn // ParseError signals errors while parsing the simple and flat text-based // exchange format. type ParseError struct { Line int Msg string } // Error implements the error interface. func (e ParseError) Error() string { return fmt.Sprintf("text format parsing error in line %d: %s", e.Line, e.Msg) } // Parser is used to parse the simple and flat text-based exchange format. Its // nil value is ready to use. type Parser struct { metricFamiliesByName map[string]*dto.MetricFamily buf *bufio.Reader // Where the parsed input is read through. err error // Most recent error. lineCount int // Tracks the line count for error messages. currentByte byte // The most recent byte read. currentToken bytes.Buffer // Re-used each time a token has to be gathered from multiple bytes. currentMF *dto.MetricFamily currentMetric *dto.Metric currentLabelPair *dto.LabelPair // The remaining member variables are only used for summaries/histograms. currentLabels map[string]string // All labels including '__name__' but excluding 'quantile'/'le' // Summary specific. summaries map[uint64]*dto.Metric // Key is created with LabelsToSignature. currentQuantile float64 // Histogram specific. histograms map[uint64]*dto.Metric // Key is created with LabelsToSignature. currentBucket float64 // These tell us if the currently processed line ends on '_count' or // '_sum' respectively and belong to a summary/histogram, representing the sample // count and sum of that summary/histogram. currentIsSummaryCount, currentIsSummarySum bool currentIsHistogramCount, currentIsHistogramSum bool } // TextToMetricFamilies reads 'in' as the simple and flat text-based exchange // format and creates MetricFamily proto messages. It returns the MetricFamily // proto messages in a map where the metric names are the keys, along with any // error encountered. // // If the input contains duplicate metrics (i.e. lines with the same metric name // and exactly the same label set), the resulting MetricFamily will contain // duplicate Metric proto messages. Similar is true for duplicate label // names. Checks for duplicates have to be performed separately, if required. // Also note that neither the metrics within each MetricFamily are sorted nor // the label pairs within each Metric. Sorting is not required for the most // frequent use of this method, which is sample ingestion in the Prometheus // server. However, for presentation purposes, you might want to sort the // metrics, and in some cases, you must sort the labels, e.g. for consumption by // the metric family injection hook of the Prometheus registry. // // Summaries and histograms are rather special beasts. You would probably not // use them in the simple text format anyway. This method can deal with // summaries and histograms if they are presented in exactly the way the // text.Create function creates them. // // This method must not be called concurrently. If you want to parse different // input concurrently, instantiate a separate Parser for each goroutine. func (p *Parser) TextToMetricFamilies(in io.Reader) (map[string]*dto.MetricFamily, error) { p.reset(in) for nextState := p.startOfLine; nextState != nil; nextState = nextState() { // Magic happens here... } // Get rid of empty metric families. for k, mf := range p.metricFamiliesByName { if len(mf.GetMetric()) == 0 { delete(p.metricFamiliesByName, k) } } return p.metricFamiliesByName, p.err } func (p *Parser) reset(in io.Reader) { p.metricFamiliesByName = map[string]*dto.MetricFamily{} if p.buf == nil { p.buf = bufio.NewReader(in) } else { p.buf.Reset(in) } p.err = nil p.lineCount = 0 if p.summaries == nil || len(p.summaries) > 0 { p.summaries = map[uint64]*dto.Metric{} } if p.histograms == nil || len(p.histograms) > 0 { p.histograms = map[uint64]*dto.Metric{} } p.currentQuantile = math.NaN() p.currentBucket = math.NaN() } // startOfLine represents the state where the next byte read from p.buf is the // start of a line (or whitespace leading up to it). func (p *Parser) startOfLine() stateFn { p.lineCount++ if p.skipBlankTab(); p.err != nil { // End of input reached. This is the only case where // that is not an error but a signal that we are done. p.err = nil return nil } switch p.currentByte { case '#': return p.startComment case '\n': return p.startOfLine // Empty line, start the next one. } return p.readingMetricName } // startComment represents the state where the next byte read from p.buf is the // start of a comment (or whitespace leading up to it). func (p *Parser) startComment() stateFn { if p.skipBlankTab(); p.err != nil { return nil // Unexpected end of input. } if p.currentByte == '\n' { return p.startOfLine } if p.readTokenUntilWhitespace(); p.err != nil { return nil // Unexpected end of input. } // If we have hit the end of line already, there is nothing left // to do. This is not considered a syntax error. if p.currentByte == '\n' { return p.startOfLine } keyword := p.currentToken.String() if keyword != "HELP" && keyword != "TYPE" { // Generic comment, ignore by fast forwarding to end of line. for p.currentByte != '\n' { if p.currentByte, p.err = p.buf.ReadByte(); p.err != nil { return nil // Unexpected end of input. } } return p.startOfLine } // There is something. Next has to be a metric name. if p.skipBlankTab(); p.err != nil { return nil // Unexpected end of input. } if p.readTokenAsMetricName(); p.err != nil { return nil // Unexpected end of input. } if p.currentByte == '\n' { // At the end of the line already. // Again, this is not considered a syntax error. return p.startOfLine } if !isBlankOrTab(p.currentByte) { p.parseError("invalid metric name in comment") return nil } p.setOrCreateCurrentMF() if p.skipBlankTab(); p.err != nil { return nil // Unexpected end of input. } if p.currentByte == '\n' { // At the end of the line already. // Again, this is not considered a syntax error. return p.startOfLine } switch keyword { case "HELP": return p.readingHelp case "TYPE": return p.readingType } panic(fmt.Sprintf("code error: unexpected keyword %q", keyword)) } // readingMetricName represents the state where the last byte read (now in // p.currentByte) is the first byte of a metric name. func (p *Parser) readingMetricName() stateFn { if p.readTokenAsMetricName(); p.err != nil { return nil } if p.currentToken.Len() == 0 { p.parseError("invalid metric name") return nil } p.setOrCreateCurrentMF() // Now is the time to fix the type if it hasn't happened yet. if p.currentMF.Type == nil { p.currentMF.Type = dto.MetricType_UNTYPED.Enum() } p.currentMetric = &dto.Metric{} // Do not append the newly created currentMetric to // currentMF.Metric right now. First wait if this is a summary, // and the metric exists already, which we can only know after // having read all the labels. if p.skipBlankTabIfCurrentBlankTab(); p.err != nil { return nil // Unexpected end of input. } return p.readingLabels } // readingLabels represents the state where the last byte read (now in // p.currentByte) is either the first byte of the label set (i.e. a '{'), or the // first byte of the value (otherwise). func (p *Parser) readingLabels() stateFn { // Summaries/histograms are special. We have to reset the // currentLabels map, currentQuantile and currentBucket before starting to // read labels. if p.currentMF.GetType() == dto.MetricType_SUMMARY || p.currentMF.GetType() == dto.MetricType_HISTOGRAM { p.currentLabels = map[string]string{} p.currentLabels[string(model.MetricNameLabel)] = p.currentMF.GetName() p.currentQuantile = math.NaN() p.currentBucket = math.NaN() } if p.currentByte != '{' { return p.readingValue } return p.startLabelName } // startLabelName represents the state where the next byte read from p.buf is // the start of a label name (or whitespace leading up to it). func (p *Parser) startLabelName() stateFn { if p.skipBlankTab(); p.err != nil { return nil // Unexpected end of input. } if p.currentByte == '}' { if p.skipBlankTab(); p.err != nil { return nil // Unexpected end of input. } return p.readingValue } if p.readTokenAsLabelName(); p.err != nil { return nil // Unexpected end of input. } if p.currentToken.Len() == 0 { p.parseError(fmt.Sprintf("invalid label name for metric %q", p.currentMF.GetName())) return nil } p.currentLabelPair = &dto.LabelPair{Name: proto.String(p.currentToken.String())} if p.currentLabelPair.GetName() == string(model.MetricNameLabel) { p.parseError(fmt.Sprintf("label name %q is reserved", model.MetricNameLabel)) return nil } // Special summary/histogram treatment. Don't add 'quantile' and 'le' // labels to 'real' labels. if !(p.currentMF.GetType() == dto.MetricType_SUMMARY && p.currentLabelPair.GetName() == model.QuantileLabel) && !(p.currentMF.GetType() == dto.MetricType_HISTOGRAM && p.currentLabelPair.GetName() == model.BucketLabel) { p.currentMetric.Label = append(p.currentMetric.Label, p.currentLabelPair) } if p.skipBlankTabIfCurrentBlankTab(); p.err != nil { return nil // Unexpected end of input. } if p.currentByte != '=' { p.parseError(fmt.Sprintf("expected '=' after label name, found %q", p.currentByte)) return nil } return p.startLabelValue } // startLabelValue represents the state where the next byte read from p.buf is // the start of a (quoted) label value (or whitespace leading up to it). func (p *Parser) startLabelValue() stateFn { if p.skipBlankTab(); p.err != nil { return nil // Unexpected end of input. } if p.currentByte != '"' { p.parseError(fmt.Sprintf("expected '\"' at start of label value, found %q", p.currentByte)) return nil } if p.readTokenAsLabelValue(); p.err != nil { return nil } p.currentLabelPair.Value = proto.String(p.currentToken.String()) // Special treatment of summaries: // - Quantile labels are special, will result in dto.Quantile later. // - Other labels have to be added to currentLabels for signature calculation. if p.currentMF.GetType() == dto.MetricType_SUMMARY { if p.currentLabelPair.GetName() == model.QuantileLabel { if p.currentQuantile, p.err = strconv.ParseFloat(p.currentLabelPair.GetValue(), 64); p.err != nil { // Create a more helpful error message. p.parseError(fmt.Sprintf("expected float as value for 'quantile' label, got %q", p.currentLabelPair.GetValue())) return nil } } else { p.currentLabels[p.currentLabelPair.GetName()] = p.currentLabelPair.GetValue() } } // Similar special treatment of histograms. if p.currentMF.GetType() == dto.MetricType_HISTOGRAM { if p.currentLabelPair.GetName() == model.BucketLabel { if p.currentBucket, p.err = strconv.ParseFloat(p.currentLabelPair.GetValue(), 64); p.err != nil { // Create a more helpful error message. p.parseError(fmt.Sprintf("expected float as value for 'le' label, got %q", p.currentLabelPair.GetValue())) return nil } } else { p.currentLabels[p.currentLabelPair.GetName()] = p.currentLabelPair.GetValue() } } if p.skipBlankTab(); p.err != nil { return nil // Unexpected end of input. } switch p.currentByte { case ',': return p.startLabelName case '}': if p.skipBlankTab(); p.err != nil { return nil // Unexpected end of input. } return p.readingValue default: p.parseError(fmt.Sprintf("unexpected end of label value %q", p.currentLabelPair.Value)) return nil } } // readingValue represents the state where the last byte read (now in // p.currentByte) is the first byte of the sample value (i.e. a float). func (p *Parser) readingValue() stateFn { // When we are here, we have read all the labels, so for the // special case of a summary/histogram, we can finally find out // if the metric already exists. if p.currentMF.GetType() == dto.MetricType_SUMMARY { signature := model.LabelsToSignature(p.currentLabels) if summary := p.summaries[signature]; summary != nil { p.currentMetric = summary } else { p.summaries[signature] = p.currentMetric p.currentMF.Metric = append(p.currentMF.Metric, p.currentMetric) } } else if p.currentMF.GetType() == dto.MetricType_HISTOGRAM { signature := model.LabelsToSignature(p.currentLabels) if histogram := p.histograms[signature]; histogram != nil { p.currentMetric = histogram } else { p.histograms[signature] = p.currentMetric p.currentMF.Metric = append(p.currentMF.Metric, p.currentMetric) } } else { p.currentMF.Metric = append(p.currentMF.Metric, p.currentMetric) } if p.readTokenUntilWhitespace(); p.err != nil { return nil // Unexpected end of input. } value, err := strconv.ParseFloat(p.currentToken.String(), 64) if err != nil { // Create a more helpful error message. p.parseError(fmt.Sprintf("expected float as value, got %q", p.currentToken.String())) return nil } switch p.currentMF.GetType() { case dto.MetricType_COUNTER: p.currentMetric.Counter = &dto.Counter{Value: proto.Float64(value)} case dto.MetricType_GAUGE: p.currentMetric.Gauge = &dto.Gauge{Value: proto.Float64(value)} case dto.MetricType_UNTYPED: p.currentMetric.Untyped = &dto.Untyped{Value: proto.Float64(value)} case dto.MetricType_SUMMARY: // *sigh* if p.currentMetric.Summary == nil { p.currentMetric.Summary = &dto.Summary{} } switch { case p.currentIsSummaryCount: p.currentMetric.Summary.SampleCount = proto.Uint64(uint64(value)) case p.currentIsSummarySum: p.currentMetric.Summary.SampleSum = proto.Float64(value) case !math.IsNaN(p.currentQuantile): p.currentMetric.Summary.Quantile = append( p.currentMetric.Summary.Quantile, &dto.Quantile{ Quantile: proto.Float64(p.currentQuantile), Value: proto.Float64(value), }, ) } case dto.MetricType_HISTOGRAM: // *sigh* if p.currentMetric.Histogram == nil { p.currentMetric.Histogram = &dto.Histogram{} } switch { case p.currentIsHistogramCount: p.currentMetric.Histogram.SampleCount = proto.Uint64(uint64(value)) case p.currentIsHistogramSum: p.currentMetric.Histogram.SampleSum = proto.Float64(value) case !math.IsNaN(p.currentBucket): p.currentMetric.Histogram.Bucket = append( p.currentMetric.Histogram.Bucket, &dto.Bucket{ UpperBound: proto.Float64(p.currentBucket), CumulativeCount: proto.Uint64(uint64(value)), }, ) } default: p.err = fmt.Errorf("unexpected type for metric name %q", p.currentMF.GetName()) } if p.currentByte == '\n' { return p.startOfLine } return p.startTimestamp } // startTimestamp represents the state where the next byte read from p.buf is // the start of the timestamp (or whitespace leading up to it). func (p *Parser) startTimestamp() stateFn { if p.skipBlankTab(); p.err != nil { return nil // Unexpected end of input. } if p.readTokenUntilWhitespace(); p.err != nil { return nil // Unexpected end of input. } timestamp, err := strconv.ParseInt(p.currentToken.String(), 10, 64) if err != nil { // Create a more helpful error message. p.parseError(fmt.Sprintf("expected integer as timestamp, got %q", p.currentToken.String())) return nil } p.currentMetric.TimestampMs = proto.Int64(timestamp) if p.readTokenUntilNewline(false); p.err != nil { return nil // Unexpected end of input. } if p.currentToken.Len() > 0 { p.parseError(fmt.Sprintf("spurious string after timestamp: %q", p.currentToken.String())) return nil } return p.startOfLine } // readingHelp represents the state where the last byte read (now in // p.currentByte) is the first byte of the docstring after 'HELP'. func (p *Parser) readingHelp() stateFn { if p.currentMF.Help != nil { p.parseError(fmt.Sprintf("second HELP line for metric name %q", p.currentMF.GetName())) return nil } // Rest of line is the docstring. if p.readTokenUntilNewline(true); p.err != nil { return nil // Unexpected end of input. } p.currentMF.Help = proto.String(p.currentToken.String()) return p.startOfLine } // readingType represents the state where the last byte read (now in // p.currentByte) is the first byte of the type hint after 'HELP'. func (p *Parser) readingType() stateFn { if p.currentMF.Type != nil { p.parseError(fmt.Sprintf("second TYPE line for metric name %q, or TYPE reported after samples", p.currentMF.GetName())) return nil } // Rest of line is the type. if p.readTokenUntilNewline(false); p.err != nil { return nil // Unexpected end of input. } metricType, ok := dto.MetricType_value[strings.ToUpper(p.currentToken.String())] if !ok { p.parseError(fmt.Sprintf("unknown metric type %q", p.currentToken.String())) return nil } p.currentMF.Type = dto.MetricType(metricType).Enum() return p.startOfLine } // parseError sets p.err to a ParseError at the current line with the given // message. func (p *Parser) parseError(msg string) { p.err = ParseError{ Line: p.lineCount, Msg: msg, } } // skipBlankTab reads (and discards) bytes from p.buf until it encounters a byte // that is neither ' ' nor '\t'. That byte is left in p.currentByte. func (p *Parser) skipBlankTab() { for { if p.currentByte, p.err = p.buf.ReadByte(); p.err != nil || !isBlankOrTab(p.currentByte) { return } } } // skipBlankTabIfCurrentBlankTab works exactly as skipBlankTab but doesn't do // anything if p.currentByte is neither ' ' nor '\t'. func (p *Parser) skipBlankTabIfCurrentBlankTab() { if isBlankOrTab(p.currentByte) { p.skipBlankTab() } } // readTokenUntilWhitespace copies bytes from p.buf into p.currentToken. The // first byte considered is the byte already read (now in p.currentByte). The // first whitespace byte encountered is still copied into p.currentByte, but not // into p.currentToken. func (p *Parser) readTokenUntilWhitespace() { p.currentToken.Reset() for p.err == nil && !isBlankOrTab(p.currentByte) && p.currentByte != '\n' { p.currentToken.WriteByte(p.currentByte) p.currentByte, p.err = p.buf.ReadByte() } } // readTokenUntilNewline copies bytes from p.buf into p.currentToken. The first // byte considered is the byte already read (now in p.currentByte). The first // newline byte encountered is still copied into p.currentByte, but not into // p.currentToken. If recognizeEscapeSequence is true, two escape sequences are // recognized: '\\' tranlates into '\', and '\n' into a line-feed character. All // other escape sequences are invalid and cause an error. func (p *Parser) readTokenUntilNewline(recognizeEscapeSequence bool) { p.currentToken.Reset() escaped := false for p.err == nil { if recognizeEscapeSequence && escaped { switch p.currentByte { case '\\': p.currentToken.WriteByte(p.currentByte) case 'n': p.currentToken.WriteByte('\n') default: p.parseError(fmt.Sprintf("invalid escape sequence '\\%c'", p.currentByte)) return } escaped = false } else { switch p.currentByte { case '\n': return case '\\': escaped = true default: p.currentToken.WriteByte(p.currentByte) } } p.currentByte, p.err = p.buf.ReadByte() } } // readTokenAsMetricName copies a metric name from p.buf into p.currentToken. // The first byte considered is the byte already read (now in p.currentByte). // The first byte not part of a metric name is still copied into p.currentByte, // but not into p.currentToken. func (p *Parser) readTokenAsMetricName() { p.currentToken.Reset() if !isValidMetricNameStart(p.currentByte) { return } for { p.currentToken.WriteByte(p.currentByte) p.currentByte, p.err = p.buf.ReadByte() if p.err != nil || !isValidMetricNameContinuation(p.currentByte) { return } } } // readTokenAsLabelName copies a label name from p.buf into p.currentToken. // The first byte considered is the byte already read (now in p.currentByte). // The first byte not part of a label name is still copied into p.currentByte, // but not into p.currentToken. func (p *Parser) readTokenAsLabelName() { p.currentToken.Reset() if !isValidLabelNameStart(p.currentByte) { return } for { p.currentToken.WriteByte(p.currentByte) p.currentByte, p.err = p.buf.ReadByte() if p.err != nil || !isValidLabelNameContinuation(p.currentByte) { return } } } // readTokenAsLabelValue copies a label value from p.buf into p.currentToken. // In contrast to the other 'readTokenAs...' functions, which start with the // last read byte in p.currentByte, this method ignores p.currentByte and starts // with reading a new byte from p.buf. The first byte not part of a label value // is still copied into p.currentByte, but not into p.currentToken. func (p *Parser) readTokenAsLabelValue() { p.currentToken.Reset() escaped := false for { if p.currentByte, p.err = p.buf.ReadByte(); p.err != nil { return } if escaped { switch p.currentByte { case '"', '\\': p.currentToken.WriteByte(p.currentByte) case 'n': p.currentToken.WriteByte('\n') default: p.parseError(fmt.Sprintf("invalid escape sequence '\\%c'", p.currentByte)) return } escaped = false continue } switch p.currentByte { case '"': return case '\n': p.parseError(fmt.Sprintf("label value %q contains unescaped new-line", p.currentToken.String())) return case '\\': escaped = true default: p.currentToken.WriteByte(p.currentByte) } } } func (p *Parser) setOrCreateCurrentMF() { p.currentIsSummaryCount = false p.currentIsSummarySum = false p.currentIsHistogramCount = false p.currentIsHistogramSum = false name := p.currentToken.String() if p.currentMF = p.metricFamiliesByName[name]; p.currentMF != nil { return } // Try out if this is a _sum or _count for a summary/histogram. summaryName := summaryMetricName(name) if p.currentMF = p.metricFamiliesByName[summaryName]; p.currentMF != nil { if p.currentMF.GetType() == dto.MetricType_SUMMARY { if isCount(name) { p.currentIsSummaryCount = true } if isSum(name) { p.currentIsSummarySum = true } return } } histogramName := histogramMetricName(name) if p.currentMF = p.metricFamiliesByName[histogramName]; p.currentMF != nil { if p.currentMF.GetType() == dto.MetricType_HISTOGRAM { if isCount(name) { p.currentIsHistogramCount = true } if isSum(name) { p.currentIsHistogramSum = true } return } } p.currentMF = &dto.MetricFamily{Name: proto.String(name)} p.metricFamiliesByName[name] = p.currentMF } func isValidLabelNameStart(b byte) bool { return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' } func isValidLabelNameContinuation(b byte) bool { return isValidLabelNameStart(b) || (b >= '0' && b <= '9') } func isValidMetricNameStart(b byte) bool { return isValidLabelNameStart(b) || b == ':' } func isValidMetricNameContinuation(b byte) bool { return isValidLabelNameContinuation(b) || b == ':' } func isBlankOrTab(b byte) bool { return b == ' ' || b == '\t' } func isCount(name string) bool { return len(name) > 6 && name[len(name)-6:] == "_count" } func isSum(name string) bool { return len(name) > 4 && name[len(name)-4:] == "_sum" } func isBucket(name string) bool { return len(name) > 7 && name[len(name)-7:] == "_bucket" } func summaryMetricName(name string) string { switch { case isCount(name): return name[:len(name)-6] case isSum(name): return name[:len(name)-4] default: return name } } func histogramMetricName(name string) string { switch { case isCount(name): return name[:len(name)-6] case isSum(name): return name[:len(name)-4] case isBucket(name): return name[:len(name)-7] default: return name } } golang-prometheus-client-0.7.0+ds/text/parse_test.go000066400000000000000000000340131256572052500225320ustar00rootroot00000000000000// Copyright 2014 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package text import ( "math" "strings" "testing" "github.com/golang/protobuf/proto" dto "github.com/prometheus/client_model/go" ) var parser Parser func testParse(t testing.TB) { var scenarios = []struct { in string out []*dto.MetricFamily }{ // 0: Empty lines as input. { in: ` `, out: []*dto.MetricFamily{}, }, // 1: Minimal case. { in: ` minimal_metric 1.234 another_metric -3e3 103948 # Even that: no_labels{} 3 # HELP line for non-existing metric will be ignored. `, out: []*dto.MetricFamily{ &dto.MetricFamily{ Name: proto.String("minimal_metric"), Type: dto.MetricType_UNTYPED.Enum(), Metric: []*dto.Metric{ &dto.Metric{ Untyped: &dto.Untyped{ Value: proto.Float64(1.234), }, }, }, }, &dto.MetricFamily{ Name: proto.String("another_metric"), Type: dto.MetricType_UNTYPED.Enum(), Metric: []*dto.Metric{ &dto.Metric{ Untyped: &dto.Untyped{ Value: proto.Float64(-3e3), }, TimestampMs: proto.Int64(103948), }, }, }, &dto.MetricFamily{ Name: proto.String("no_labels"), Type: dto.MetricType_UNTYPED.Enum(), Metric: []*dto.Metric{ &dto.Metric{ Untyped: &dto.Untyped{ Value: proto.Float64(3), }, }, }, }, }, }, // 2: Counters & gauges, docstrings, various whitespace, escape sequences. { in: ` # A normal comment. # # TYPE name counter name{labelname="val1",basename="basevalue"} NaN name {labelname="val2",basename="base\"v\\al\nue"} 0.23 1234567890 # HELP name two-line\n doc str\\ing # HELP name2 doc str"ing 2 # TYPE name2 gauge name2{labelname="val2" ,basename = "basevalue2" } +Inf 54321 name2{ labelname = "val1" , }-Inf `, out: []*dto.MetricFamily{ &dto.MetricFamily{ Name: proto.String("name"), Help: proto.String("two-line\n doc str\\ing"), Type: dto.MetricType_COUNTER.Enum(), Metric: []*dto.Metric{ &dto.Metric{ Label: []*dto.LabelPair{ &dto.LabelPair{ Name: proto.String("labelname"), Value: proto.String("val1"), }, &dto.LabelPair{ Name: proto.String("basename"), Value: proto.String("basevalue"), }, }, Counter: &dto.Counter{ Value: proto.Float64(math.NaN()), }, }, &dto.Metric{ Label: []*dto.LabelPair{ &dto.LabelPair{ Name: proto.String("labelname"), Value: proto.String("val2"), }, &dto.LabelPair{ Name: proto.String("basename"), Value: proto.String("base\"v\\al\nue"), }, }, Counter: &dto.Counter{ Value: proto.Float64(.23), }, TimestampMs: proto.Int64(1234567890), }, }, }, &dto.MetricFamily{ Name: proto.String("name2"), Help: proto.String("doc str\"ing 2"), Type: dto.MetricType_GAUGE.Enum(), Metric: []*dto.Metric{ &dto.Metric{ Label: []*dto.LabelPair{ &dto.LabelPair{ Name: proto.String("labelname"), Value: proto.String("val2"), }, &dto.LabelPair{ Name: proto.String("basename"), Value: proto.String("basevalue2"), }, }, Gauge: &dto.Gauge{ Value: proto.Float64(math.Inf(+1)), }, TimestampMs: proto.Int64(54321), }, &dto.Metric{ Label: []*dto.LabelPair{ &dto.LabelPair{ Name: proto.String("labelname"), Value: proto.String("val1"), }, }, Gauge: &dto.Gauge{ Value: proto.Float64(math.Inf(-1)), }, }, }, }, }, }, // 3: The evil summary, mixed with other types and funny comments. { in: ` # TYPE my_summary summary my_summary{n1="val1",quantile="0.5"} 110 decoy -1 -2 my_summary{n1="val1",quantile="0.9"} 140 1 my_summary_count{n1="val1"} 42 # Latest timestamp wins in case of a summary. my_summary_sum{n1="val1"} 4711 2 fake_sum{n1="val1"} 2001 # TYPE another_summary summary another_summary_count{n2="val2",n1="val1"} 20 my_summary_count{n2="val2",n1="val1"} 5 5 another_summary{n1="val1",n2="val2",quantile=".3"} -1.2 my_summary_sum{n1="val2"} 08 15 my_summary{n1="val3", quantile="0.2"} 4711 my_summary{n1="val1",n2="val2",quantile="-12.34",} NaN # some # funny comments # HELP # HELP # HELP my_summary # HELP my_summary `, out: []*dto.MetricFamily{ &dto.MetricFamily{ Name: proto.String("fake_sum"), Type: dto.MetricType_UNTYPED.Enum(), Metric: []*dto.Metric{ &dto.Metric{ Label: []*dto.LabelPair{ &dto.LabelPair{ Name: proto.String("n1"), Value: proto.String("val1"), }, }, Untyped: &dto.Untyped{ Value: proto.Float64(2001), }, }, }, }, &dto.MetricFamily{ Name: proto.String("decoy"), Type: dto.MetricType_UNTYPED.Enum(), Metric: []*dto.Metric{ &dto.Metric{ Untyped: &dto.Untyped{ Value: proto.Float64(-1), }, TimestampMs: proto.Int64(-2), }, }, }, &dto.MetricFamily{ Name: proto.String("my_summary"), Type: dto.MetricType_SUMMARY.Enum(), Metric: []*dto.Metric{ &dto.Metric{ Label: []*dto.LabelPair{ &dto.LabelPair{ Name: proto.String("n1"), Value: proto.String("val1"), }, }, Summary: &dto.Summary{ SampleCount: proto.Uint64(42), SampleSum: proto.Float64(4711), Quantile: []*dto.Quantile{ &dto.Quantile{ Quantile: proto.Float64(0.5), Value: proto.Float64(110), }, &dto.Quantile{ Quantile: proto.Float64(0.9), Value: proto.Float64(140), }, }, }, TimestampMs: proto.Int64(2), }, &dto.Metric{ Label: []*dto.LabelPair{ &dto.LabelPair{ Name: proto.String("n2"), Value: proto.String("val2"), }, &dto.LabelPair{ Name: proto.String("n1"), Value: proto.String("val1"), }, }, Summary: &dto.Summary{ SampleCount: proto.Uint64(5), Quantile: []*dto.Quantile{ &dto.Quantile{ Quantile: proto.Float64(-12.34), Value: proto.Float64(math.NaN()), }, }, }, TimestampMs: proto.Int64(5), }, &dto.Metric{ Label: []*dto.LabelPair{ &dto.LabelPair{ Name: proto.String("n1"), Value: proto.String("val2"), }, }, Summary: &dto.Summary{ SampleSum: proto.Float64(8), }, TimestampMs: proto.Int64(15), }, &dto.Metric{ Label: []*dto.LabelPair{ &dto.LabelPair{ Name: proto.String("n1"), Value: proto.String("val3"), }, }, Summary: &dto.Summary{ Quantile: []*dto.Quantile{ &dto.Quantile{ Quantile: proto.Float64(0.2), Value: proto.Float64(4711), }, }, }, }, }, }, &dto.MetricFamily{ Name: proto.String("another_summary"), Type: dto.MetricType_SUMMARY.Enum(), Metric: []*dto.Metric{ &dto.Metric{ Label: []*dto.LabelPair{ &dto.LabelPair{ Name: proto.String("n2"), Value: proto.String("val2"), }, &dto.LabelPair{ Name: proto.String("n1"), Value: proto.String("val1"), }, }, Summary: &dto.Summary{ SampleCount: proto.Uint64(20), Quantile: []*dto.Quantile{ &dto.Quantile{ Quantile: proto.Float64(0.3), Value: proto.Float64(-1.2), }, }, }, }, }, }, }, }, // 4: The histogram. { in: ` # HELP request_duration_microseconds The response latency. # TYPE request_duration_microseconds histogram request_duration_microseconds_bucket{le="100"} 123 request_duration_microseconds_bucket{le="120"} 412 request_duration_microseconds_bucket{le="144"} 592 request_duration_microseconds_bucket{le="172.8"} 1524 request_duration_microseconds_bucket{le="+Inf"} 2693 request_duration_microseconds_sum 1.7560473e+06 request_duration_microseconds_count 2693 `, out: []*dto.MetricFamily{ { Name: proto.String("request_duration_microseconds"), Help: proto.String("The response latency."), Type: dto.MetricType_HISTOGRAM.Enum(), Metric: []*dto.Metric{ &dto.Metric{ Histogram: &dto.Histogram{ SampleCount: proto.Uint64(2693), SampleSum: proto.Float64(1756047.3), Bucket: []*dto.Bucket{ &dto.Bucket{ UpperBound: proto.Float64(100), CumulativeCount: proto.Uint64(123), }, &dto.Bucket{ UpperBound: proto.Float64(120), CumulativeCount: proto.Uint64(412), }, &dto.Bucket{ UpperBound: proto.Float64(144), CumulativeCount: proto.Uint64(592), }, &dto.Bucket{ UpperBound: proto.Float64(172.8), CumulativeCount: proto.Uint64(1524), }, &dto.Bucket{ UpperBound: proto.Float64(math.Inf(+1)), CumulativeCount: proto.Uint64(2693), }, }, }, }, }, }, }, }, } for i, scenario := range scenarios { out, err := parser.TextToMetricFamilies(strings.NewReader(scenario.in)) if err != nil { t.Errorf("%d. error: %s", i, err) continue } if expected, got := len(scenario.out), len(out); expected != got { t.Errorf( "%d. expected %d MetricFamilies, got %d", i, expected, got, ) } for _, expected := range scenario.out { got, ok := out[expected.GetName()] if !ok { t.Errorf( "%d. expected MetricFamily %q, found none", i, expected.GetName(), ) continue } if expected.String() != got.String() { t.Errorf( "%d. expected MetricFamily %s, got %s", i, expected, got, ) } } } } func TestParse(t *testing.T) { testParse(t) } func BenchmarkParse(b *testing.B) { for i := 0; i < b.N; i++ { testParse(b) } } func testParseError(t testing.TB) { var scenarios = []struct { in string err string }{ // 0: No new-line at end of input. { in: `bla 3.14`, err: "EOF", }, // 1: Invalid escape sequence in label value. { in: `metric{label="\t"} 3.14`, err: "text format parsing error in line 1: invalid escape sequence", }, // 2: Newline in label value. { in: ` metric{label="new line"} 3.14 `, err: `text format parsing error in line 2: label value "new" contains unescaped new-line`, }, // 3: { in: `metric{@="bla"} 3.14`, err: "text format parsing error in line 1: invalid label name for metric", }, // 4: { in: `metric{__name__="bla"} 3.14`, err: `text format parsing error in line 1: label name "__name__" is reserved`, }, // 5: { in: `metric{label+="bla"} 3.14`, err: "text format parsing error in line 1: expected '=' after label name", }, // 6: { in: `metric{label=bla} 3.14`, err: "text format parsing error in line 1: expected '\"' at start of label value", }, // 7: { in: ` # TYPE metric summary metric{quantile="bla"} 3.14 `, err: "text format parsing error in line 3: expected float as value for 'quantile' label", }, // 8: { in: `metric{label="bla"+} 3.14`, err: "text format parsing error in line 1: unexpected end of label value", }, // 9: { in: `metric{label="bla"} 3.14 2.72 `, err: "text format parsing error in line 1: expected integer as timestamp", }, // 10: { in: `metric{label="bla"} 3.14 2 3 `, err: "text format parsing error in line 1: spurious string after timestamp", }, // 11: { in: `metric{label="bla"} blubb `, err: "text format parsing error in line 1: expected float as value", }, // 12: { in: ` # HELP metric one # HELP metric two `, err: "text format parsing error in line 3: second HELP line for metric name", }, // 13: { in: ` # TYPE metric counter # TYPE metric untyped `, err: `text format parsing error in line 3: second TYPE line for metric name "metric", or TYPE reported after samples`, }, // 14: { in: ` metric 4.12 # TYPE metric counter `, err: `text format parsing error in line 3: second TYPE line for metric name "metric", or TYPE reported after samples`, }, // 14: { in: ` # TYPE metric bla `, err: "text format parsing error in line 2: unknown metric type", }, // 15: { in: ` # TYPE met-ric `, err: "text format parsing error in line 2: invalid metric name in comment", }, // 16: { in: `@invalidmetric{label="bla"} 3.14 2`, err: "text format parsing error in line 1: invalid metric name", }, // 17: { in: `{label="bla"} 3.14 2`, err: "text format parsing error in line 1: invalid metric name", }, // 18: { in: ` # TYPE metric histogram metric_bucket{le="bla"} 3.14 `, err: "text format parsing error in line 3: expected float as value for 'le' label", }, } for i, scenario := range scenarios { _, err := parser.TextToMetricFamilies(strings.NewReader(scenario.in)) if err == nil { t.Errorf("%d. expected error, got nil", i) continue } if expected, got := scenario.err, err.Error(); strings.Index(got, expected) != 0 { t.Errorf( "%d. expected error starting with %q, got %q", i, expected, got, ) } } } func TestParseError(t *testing.T) { testParseError(t) } func BenchmarkParseError(b *testing.B) { for i := 0; i < b.N; i++ { testParseError(b) } } golang-prometheus-client-0.7.0+ds/text/proto.go000066400000000000000000000030661256572052500215300ustar00rootroot00000000000000// Copyright 2014 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package text import ( "fmt" "io" "github.com/golang/protobuf/proto" "github.com/matttproud/golang_protobuf_extensions/pbutil" dto "github.com/prometheus/client_model/go" ) // WriteProtoDelimited writes the MetricFamily to the writer in delimited // protobuf format and returns the number of bytes written and any error // encountered. func WriteProtoDelimited(w io.Writer, p *dto.MetricFamily) (int, error) { return pbutil.WriteDelimited(w, p) } // WriteProtoText writes the MetricFamily to the writer in text format and // returns the number of bytes written and any error encountered. func WriteProtoText(w io.Writer, p *dto.MetricFamily) (int, error) { return fmt.Fprintf(w, "%s\n", proto.MarshalTextString(p)) } // WriteProtoCompactText writes the MetricFamily to the writer in compact text // format and returns the number of bytes written and any error encountered. func WriteProtoCompactText(w io.Writer, p *dto.MetricFamily) (int, error) { return fmt.Fprintf(w, "%s\n", p) } golang-prometheus-client-0.7.0+ds/text/testdata/000077500000000000000000000000001256572052500216425ustar00rootroot00000000000000golang-prometheus-client-0.7.0+ds/text/testdata/protobuf000066400000000000000000000200631256572052500234260ustar00rootroot00000000000000 "http_request_duration_microseconds+The HTTP request latencies in microseconds."W handler/"G ? ? Gz?"]  handler/alerts"G ? ? Gz?"b  handler /api/metrics"G ? ? Gz?"`  handler /api/query"G ? ? Gz?"f  handler/api/query_range"G ? ? Gz?"b  handler /api/targets"G ? ? Gz?"`  handler /consoles/"G ? ? Gz?"\  handler/graph"G ? ? Gz?"[  handler/heap"G ? ? Gz?"^  handler/static/"G ? ? Gz?"`  handler prometheus"G;[?@ ?zǯ@ ?/$@ Gz?= ף@ http_request_size_bytes The HTTP request sizes in bytes."W handler/"G ? ? Gz?"]  handler/alerts"G ? ? Gz?"b  handler /api/metrics"G ? ? Gz?"`  handler /api/query"G ? ? Gz?"f  handler/api/query_range"G ? ? Gz?"b  handler /api/targets"G ? ? Gz?"`  handler /consoles/"G ? ? Gz?"\  handler/graph"G ? ? Gz?"[  handler/heap"G ? ? Gz?"^  handler/static/"G ? ? Gz?"`  handler prometheus"G;@@ ?0r@ ?0r@ Gz?0r@| http_requests_total#Total number of HTTP requests made."> code200  handler prometheus methodget M@ http_response_size_bytes!The HTTP response sizes in bytes."W handler/"G ? ? Gz?"]  handler/alerts"G ? ? Gz?"b  handler /api/metrics"G ? ? Gz?"`  handler /api/query"G ? ? Gz?"f  handler/api/query_range"G ? ? Gz?"b  handler /api/targets"G ? ? Gz?"`  handler /consoles/"G ? ? Gz?"\  handler/graph"G ? ? Gz?"[  handler/heap"G ? ? Gz?"^  handler/static/"G ? ? Gz?"`  handler prometheus"G;@ ?4@ ?@ Gz? @\ process_cpu_seconds_total0Total user and system CPU time spent in seconds."  p= ף?O process_goroutines*Number of goroutines that currently exist."  Q@J process_max_fds(Maximum number of open file descriptors."  @C process_open_fds Number of open file descriptors."  =@N process_resident_memory_bytesResident memory size in bytes."  KAc process_start_time_seconds6Start time of the process since unix epoch in seconds."  = -1AL process_virtual_memory_bytesVirtual memory size in bytes."  A_ 'prometheus_dns_sd_lookup_failures_total%The number of DNS-SD lookup failures."  O prometheus_dns_sd_lookups_totalThe number of DNS-SD lookups."  @ *prometheus_evaluator_duration_milliseconds,The duration for all evaluations to execute."q"o "@ {Gz? ? ? ?? Gz?? 9prometheus_local_storage_checkpoint_duration_millisecondsWThe duration (in milliseconds) it took to checkpoint in-memory metrics and head chunks."   (prometheus_local_storage_chunk_ops_total3The total number of chunk operations by their type."  typecreate @"  typepersist [@" typepin {@"  type transcode k@" typeunpin {@ @ ? ףp=bk@ Gz?{Gr@j /prometheus_local_storage_persist_queue_capacity(The total capacity of the persist queue."  @z -prometheus_local_storage_persist_queue_length:The current number of chunks waiting in the persist queue."   )prometheus_local_storage_series_ops_total4The total number of series operations by their type."  typecreate @"*  typemaintenance_in_memory @ -prometheus_notifications_latency_millisecondsXLatency quantiles for sending alert notifications (not including dropped notifications)."I"G ? ? Gz?h 'prometheus_notifications_queue_capacity.The capacity of the alert notifications queue."  Y@g %prometheus_notifications_queue_length/The number of alert notifications in the queue."   0prometheus_rule_evaluation_duration_milliseconds#The duration for a rule to execute."`  rule_typealerting"G7(@ ? ? Gz?@"a  rule_type recording"G7.@ ? ? Gz?@i )prometheus_rule_evaluation_failures_total-The total number of rule evaluation failures."  ` !prometheus_samples_queue_capacity,Capacity of the queue for unwritten samples."  @ prometheus_samples_queue_lengthCurrent number of items in the queue for unwritten samples. Each item comprises all samples exposed by one target as one metric family (i.e. metrics of the same name)."   )prometheus_target_interval_length_seconds!Actual intervals between scrapes."  interval15s"oM@ {Gz?,@ ?,@ ?.@ ?.@ Gz?.@"  interval1s"o:<@ {Gz? ? ? ?? Gz??golang-prometheus-client-0.7.0+ds/text/testdata/protobuf.gz000066400000000000000000000040051256572052500240430ustar00rootroot00000000000000TprotobufYM=[`7&^MĻ̚XD$7!$Ux9HKDB%Jrŗ$J$$rA>D50Ih簻SUޫz?lfOarx)##']sƑǭ-(X4r}ʱjG"/̪7jkϚxowcvn(֚DV!xG/'~ -_&`;Fg4tЍ]Ku8#R chwa{HhW!vd|¿+7\@ctŐ =޽ WcvYh5߽eӗ`5C~͗^opur P&;3<3Z/[ sА`;2pGĂ͋p2w 8$mM֚h{z[5h3}'K 4pE,wr&}l&}ls{?AC ե;n:j Sd7]ʁ\kX!NB#!igm5w4wc\#bMAdRX{~]d9ua9p((wqS8ڥE,|8fD `b˝1y7}U-|9M}{ .ڧpuDϟk@@VAjRF I:qA_xg"%i :T "s8^NC ($*kVN=ub}VG!Qȭk%I[~p'%l 8n*,O4NMlO⯼,??py~.ϿZG_(](]'.uu؏Ĉ=jo*{'SlOc,df6T]ΘXՃSi\ɒZ:ZHx2PviX!L}/3,In݈>/vQ"W~t"8޽pnNN-ݚZݼ R3!nsvsoUid>V3~E]gofl>H xIH+02ԆXohJouւiy:UV#’`qת,nAsY @$ q}чi]HvDE;D+UШ+z9 ,RO]^G[9I`o5)wu 0īNIa bXbCS&8٬˂ -໅{>(IFws:ɰD嬁oA]dmYdJ@vFp W[:JfĸAI7AQͳ-_0GX9mXwy~ 7.qsΑ߂# aHXsIQJP<]Y3E{[m)ad=s^)%M--L_2$BSƫt߷ϭC*Ew  TjHylK#cc`at@m^'hDy>Q\;Y~XE|xƃ*ܾ\8U`CTX4:<' qh*gmy ZÊѰk cvݺ"Y)P o1,Km'+M'2hm)Qѩa=Fa7 tYgh}"uUT&aY9xft,iM@T3+Y !~kݘL MՏ/hhh>E8v0a]]+  OC„gF P+uYXUe\#9 n6(} B*Ry$ : }zNC' 383}!MpkHܲOׁ #6ܥ HD3 golang-prometheus-client-0.7.0+ds/text/testdata/text000066400000000000000000000510131256572052500225510ustar00rootroot00000000000000# HELP http_request_duration_microseconds The HTTP request latencies in microseconds. # TYPE http_request_duration_microseconds summary http_request_duration_microseconds{handler="/",quantile="0.5"} 0 http_request_duration_microseconds{handler="/",quantile="0.9"} 0 http_request_duration_microseconds{handler="/",quantile="0.99"} 0 http_request_duration_microseconds_sum{handler="/"} 0 http_request_duration_microseconds_count{handler="/"} 0 http_request_duration_microseconds{handler="/alerts",quantile="0.5"} 0 http_request_duration_microseconds{handler="/alerts",quantile="0.9"} 0 http_request_duration_microseconds{handler="/alerts",quantile="0.99"} 0 http_request_duration_microseconds_sum{handler="/alerts"} 0 http_request_duration_microseconds_count{handler="/alerts"} 0 http_request_duration_microseconds{handler="/api/metrics",quantile="0.5"} 0 http_request_duration_microseconds{handler="/api/metrics",quantile="0.9"} 0 http_request_duration_microseconds{handler="/api/metrics",quantile="0.99"} 0 http_request_duration_microseconds_sum{handler="/api/metrics"} 0 http_request_duration_microseconds_count{handler="/api/metrics"} 0 http_request_duration_microseconds{handler="/api/query",quantile="0.5"} 0 http_request_duration_microseconds{handler="/api/query",quantile="0.9"} 0 http_request_duration_microseconds{handler="/api/query",quantile="0.99"} 0 http_request_duration_microseconds_sum{handler="/api/query"} 0 http_request_duration_microseconds_count{handler="/api/query"} 0 http_request_duration_microseconds{handler="/api/query_range",quantile="0.5"} 0 http_request_duration_microseconds{handler="/api/query_range",quantile="0.9"} 0 http_request_duration_microseconds{handler="/api/query_range",quantile="0.99"} 0 http_request_duration_microseconds_sum{handler="/api/query_range"} 0 http_request_duration_microseconds_count{handler="/api/query_range"} 0 http_request_duration_microseconds{handler="/api/targets",quantile="0.5"} 0 http_request_duration_microseconds{handler="/api/targets",quantile="0.9"} 0 http_request_duration_microseconds{handler="/api/targets",quantile="0.99"} 0 http_request_duration_microseconds_sum{handler="/api/targets"} 0 http_request_duration_microseconds_count{handler="/api/targets"} 0 http_request_duration_microseconds{handler="/consoles/",quantile="0.5"} 0 http_request_duration_microseconds{handler="/consoles/",quantile="0.9"} 0 http_request_duration_microseconds{handler="/consoles/",quantile="0.99"} 0 http_request_duration_microseconds_sum{handler="/consoles/"} 0 http_request_duration_microseconds_count{handler="/consoles/"} 0 http_request_duration_microseconds{handler="/graph",quantile="0.5"} 0 http_request_duration_microseconds{handler="/graph",quantile="0.9"} 0 http_request_duration_microseconds{handler="/graph",quantile="0.99"} 0 http_request_duration_microseconds_sum{handler="/graph"} 0 http_request_duration_microseconds_count{handler="/graph"} 0 http_request_duration_microseconds{handler="/heap",quantile="0.5"} 0 http_request_duration_microseconds{handler="/heap",quantile="0.9"} 0 http_request_duration_microseconds{handler="/heap",quantile="0.99"} 0 http_request_duration_microseconds_sum{handler="/heap"} 0 http_request_duration_microseconds_count{handler="/heap"} 0 http_request_duration_microseconds{handler="/static/",quantile="0.5"} 0 http_request_duration_microseconds{handler="/static/",quantile="0.9"} 0 http_request_duration_microseconds{handler="/static/",quantile="0.99"} 0 http_request_duration_microseconds_sum{handler="/static/"} 0 http_request_duration_microseconds_count{handler="/static/"} 0 http_request_duration_microseconds{handler="prometheus",quantile="0.5"} 1307.275 http_request_duration_microseconds{handler="prometheus",quantile="0.9"} 1858.632 http_request_duration_microseconds{handler="prometheus",quantile="0.99"} 3087.384 http_request_duration_microseconds_sum{handler="prometheus"} 179886.5000000001 http_request_duration_microseconds_count{handler="prometheus"} 119 # HELP http_request_size_bytes The HTTP request sizes in bytes. # TYPE http_request_size_bytes summary http_request_size_bytes{handler="/",quantile="0.5"} 0 http_request_size_bytes{handler="/",quantile="0.9"} 0 http_request_size_bytes{handler="/",quantile="0.99"} 0 http_request_size_bytes_sum{handler="/"} 0 http_request_size_bytes_count{handler="/"} 0 http_request_size_bytes{handler="/alerts",quantile="0.5"} 0 http_request_size_bytes{handler="/alerts",quantile="0.9"} 0 http_request_size_bytes{handler="/alerts",quantile="0.99"} 0 http_request_size_bytes_sum{handler="/alerts"} 0 http_request_size_bytes_count{handler="/alerts"} 0 http_request_size_bytes{handler="/api/metrics",quantile="0.5"} 0 http_request_size_bytes{handler="/api/metrics",quantile="0.9"} 0 http_request_size_bytes{handler="/api/metrics",quantile="0.99"} 0 http_request_size_bytes_sum{handler="/api/metrics"} 0 http_request_size_bytes_count{handler="/api/metrics"} 0 http_request_size_bytes{handler="/api/query",quantile="0.5"} 0 http_request_size_bytes{handler="/api/query",quantile="0.9"} 0 http_request_size_bytes{handler="/api/query",quantile="0.99"} 0 http_request_size_bytes_sum{handler="/api/query"} 0 http_request_size_bytes_count{handler="/api/query"} 0 http_request_size_bytes{handler="/api/query_range",quantile="0.5"} 0 http_request_size_bytes{handler="/api/query_range",quantile="0.9"} 0 http_request_size_bytes{handler="/api/query_range",quantile="0.99"} 0 http_request_size_bytes_sum{handler="/api/query_range"} 0 http_request_size_bytes_count{handler="/api/query_range"} 0 http_request_size_bytes{handler="/api/targets",quantile="0.5"} 0 http_request_size_bytes{handler="/api/targets",quantile="0.9"} 0 http_request_size_bytes{handler="/api/targets",quantile="0.99"} 0 http_request_size_bytes_sum{handler="/api/targets"} 0 http_request_size_bytes_count{handler="/api/targets"} 0 http_request_size_bytes{handler="/consoles/",quantile="0.5"} 0 http_request_size_bytes{handler="/consoles/",quantile="0.9"} 0 http_request_size_bytes{handler="/consoles/",quantile="0.99"} 0 http_request_size_bytes_sum{handler="/consoles/"} 0 http_request_size_bytes_count{handler="/consoles/"} 0 http_request_size_bytes{handler="/graph",quantile="0.5"} 0 http_request_size_bytes{handler="/graph",quantile="0.9"} 0 http_request_size_bytes{handler="/graph",quantile="0.99"} 0 http_request_size_bytes_sum{handler="/graph"} 0 http_request_size_bytes_count{handler="/graph"} 0 http_request_size_bytes{handler="/heap",quantile="0.5"} 0 http_request_size_bytes{handler="/heap",quantile="0.9"} 0 http_request_size_bytes{handler="/heap",quantile="0.99"} 0 http_request_size_bytes_sum{handler="/heap"} 0 http_request_size_bytes_count{handler="/heap"} 0 http_request_size_bytes{handler="/static/",quantile="0.5"} 0 http_request_size_bytes{handler="/static/",quantile="0.9"} 0 http_request_size_bytes{handler="/static/",quantile="0.99"} 0 http_request_size_bytes_sum{handler="/static/"} 0 http_request_size_bytes_count{handler="/static/"} 0 http_request_size_bytes{handler="prometheus",quantile="0.5"} 291 http_request_size_bytes{handler="prometheus",quantile="0.9"} 291 http_request_size_bytes{handler="prometheus",quantile="0.99"} 291 http_request_size_bytes_sum{handler="prometheus"} 34488 http_request_size_bytes_count{handler="prometheus"} 119 # HELP http_requests_total Total number of HTTP requests made. # TYPE http_requests_total counter http_requests_total{code="200",handler="prometheus",method="get"} 119 # HELP http_response_size_bytes The HTTP response sizes in bytes. # TYPE http_response_size_bytes summary http_response_size_bytes{handler="/",quantile="0.5"} 0 http_response_size_bytes{handler="/",quantile="0.9"} 0 http_response_size_bytes{handler="/",quantile="0.99"} 0 http_response_size_bytes_sum{handler="/"} 0 http_response_size_bytes_count{handler="/"} 0 http_response_size_bytes{handler="/alerts",quantile="0.5"} 0 http_response_size_bytes{handler="/alerts",quantile="0.9"} 0 http_response_size_bytes{handler="/alerts",quantile="0.99"} 0 http_response_size_bytes_sum{handler="/alerts"} 0 http_response_size_bytes_count{handler="/alerts"} 0 http_response_size_bytes{handler="/api/metrics",quantile="0.5"} 0 http_response_size_bytes{handler="/api/metrics",quantile="0.9"} 0 http_response_size_bytes{handler="/api/metrics",quantile="0.99"} 0 http_response_size_bytes_sum{handler="/api/metrics"} 0 http_response_size_bytes_count{handler="/api/metrics"} 0 http_response_size_bytes{handler="/api/query",quantile="0.5"} 0 http_response_size_bytes{handler="/api/query",quantile="0.9"} 0 http_response_size_bytes{handler="/api/query",quantile="0.99"} 0 http_response_size_bytes_sum{handler="/api/query"} 0 http_response_size_bytes_count{handler="/api/query"} 0 http_response_size_bytes{handler="/api/query_range",quantile="0.5"} 0 http_response_size_bytes{handler="/api/query_range",quantile="0.9"} 0 http_response_size_bytes{handler="/api/query_range",quantile="0.99"} 0 http_response_size_bytes_sum{handler="/api/query_range"} 0 http_response_size_bytes_count{handler="/api/query_range"} 0 http_response_size_bytes{handler="/api/targets",quantile="0.5"} 0 http_response_size_bytes{handler="/api/targets",quantile="0.9"} 0 http_response_size_bytes{handler="/api/targets",quantile="0.99"} 0 http_response_size_bytes_sum{handler="/api/targets"} 0 http_response_size_bytes_count{handler="/api/targets"} 0 http_response_size_bytes{handler="/consoles/",quantile="0.5"} 0 http_response_size_bytes{handler="/consoles/",quantile="0.9"} 0 http_response_size_bytes{handler="/consoles/",quantile="0.99"} 0 http_response_size_bytes_sum{handler="/consoles/"} 0 http_response_size_bytes_count{handler="/consoles/"} 0 http_response_size_bytes{handler="/graph",quantile="0.5"} 0 http_response_size_bytes{handler="/graph",quantile="0.9"} 0 http_response_size_bytes{handler="/graph",quantile="0.99"} 0 http_response_size_bytes_sum{handler="/graph"} 0 http_response_size_bytes_count{handler="/graph"} 0 http_response_size_bytes{handler="/heap",quantile="0.5"} 0 http_response_size_bytes{handler="/heap",quantile="0.9"} 0 http_response_size_bytes{handler="/heap",quantile="0.99"} 0 http_response_size_bytes_sum{handler="/heap"} 0 http_response_size_bytes_count{handler="/heap"} 0 http_response_size_bytes{handler="/static/",quantile="0.5"} 0 http_response_size_bytes{handler="/static/",quantile="0.9"} 0 http_response_size_bytes{handler="/static/",quantile="0.99"} 0 http_response_size_bytes_sum{handler="/static/"} 0 http_response_size_bytes_count{handler="/static/"} 0 http_response_size_bytes{handler="prometheus",quantile="0.5"} 2049 http_response_size_bytes{handler="prometheus",quantile="0.9"} 2058 http_response_size_bytes{handler="prometheus",quantile="0.99"} 2064 http_response_size_bytes_sum{handler="prometheus"} 247001 http_response_size_bytes_count{handler="prometheus"} 119 # HELP process_cpu_seconds_total Total user and system CPU time spent in seconds. # TYPE process_cpu_seconds_total counter process_cpu_seconds_total 0.55 # HELP go_goroutines Number of goroutines that currently exist. # TYPE go_goroutines gauge go_goroutines 70 # HELP process_max_fds Maximum number of open file descriptors. # TYPE process_max_fds gauge process_max_fds 8192 # HELP process_open_fds Number of open file descriptors. # TYPE process_open_fds gauge process_open_fds 29 # HELP process_resident_memory_bytes Resident memory size in bytes. # TYPE process_resident_memory_bytes gauge process_resident_memory_bytes 5.3870592e+07 # HELP process_start_time_seconds Start time of the process since unix epoch in seconds. # TYPE process_start_time_seconds gauge process_start_time_seconds 1.42236894836e+09 # HELP process_virtual_memory_bytes Virtual memory size in bytes. # TYPE process_virtual_memory_bytes gauge process_virtual_memory_bytes 5.41478912e+08 # HELP prometheus_dns_sd_lookup_failures_total The number of DNS-SD lookup failures. # TYPE prometheus_dns_sd_lookup_failures_total counter prometheus_dns_sd_lookup_failures_total 0 # HELP prometheus_dns_sd_lookups_total The number of DNS-SD lookups. # TYPE prometheus_dns_sd_lookups_total counter prometheus_dns_sd_lookups_total 7 # HELP prometheus_evaluator_duration_milliseconds The duration for all evaluations to execute. # TYPE prometheus_evaluator_duration_milliseconds summary prometheus_evaluator_duration_milliseconds{quantile="0.01"} 0 prometheus_evaluator_duration_milliseconds{quantile="0.05"} 0 prometheus_evaluator_duration_milliseconds{quantile="0.5"} 0 prometheus_evaluator_duration_milliseconds{quantile="0.9"} 1 prometheus_evaluator_duration_milliseconds{quantile="0.99"} 1 prometheus_evaluator_duration_milliseconds_sum 12 prometheus_evaluator_duration_milliseconds_count 23 # HELP prometheus_local_storage_checkpoint_duration_milliseconds The duration (in milliseconds) it took to checkpoint in-memory metrics and head chunks. # TYPE prometheus_local_storage_checkpoint_duration_milliseconds gauge prometheus_local_storage_checkpoint_duration_milliseconds 0 # HELP prometheus_local_storage_chunk_ops_total The total number of chunk operations by their type. # TYPE prometheus_local_storage_chunk_ops_total counter prometheus_local_storage_chunk_ops_total{type="create"} 598 prometheus_local_storage_chunk_ops_total{type="persist"} 174 prometheus_local_storage_chunk_ops_total{type="pin"} 920 prometheus_local_storage_chunk_ops_total{type="transcode"} 415 prometheus_local_storage_chunk_ops_total{type="unpin"} 920 # HELP prometheus_local_storage_indexing_batch_latency_milliseconds Quantiles for batch indexing latencies in milliseconds. # TYPE prometheus_local_storage_indexing_batch_latency_milliseconds summary prometheus_local_storage_indexing_batch_latency_milliseconds{quantile="0.5"} 0 prometheus_local_storage_indexing_batch_latency_milliseconds{quantile="0.9"} 0 prometheus_local_storage_indexing_batch_latency_milliseconds{quantile="0.99"} 0 prometheus_local_storage_indexing_batch_latency_milliseconds_sum 0 prometheus_local_storage_indexing_batch_latency_milliseconds_count 1 # HELP prometheus_local_storage_indexing_batch_sizes Quantiles for indexing batch sizes (number of metrics per batch). # TYPE prometheus_local_storage_indexing_batch_sizes summary prometheus_local_storage_indexing_batch_sizes{quantile="0.5"} 2 prometheus_local_storage_indexing_batch_sizes{quantile="0.9"} 2 prometheus_local_storage_indexing_batch_sizes{quantile="0.99"} 2 prometheus_local_storage_indexing_batch_sizes_sum 2 prometheus_local_storage_indexing_batch_sizes_count 1 # HELP prometheus_local_storage_indexing_queue_capacity The capacity of the indexing queue. # TYPE prometheus_local_storage_indexing_queue_capacity gauge prometheus_local_storage_indexing_queue_capacity 16384 # HELP prometheus_local_storage_indexing_queue_length The number of metrics waiting to be indexed. # TYPE prometheus_local_storage_indexing_queue_length gauge prometheus_local_storage_indexing_queue_length 0 # HELP prometheus_local_storage_ingested_samples_total The total number of samples ingested. # TYPE prometheus_local_storage_ingested_samples_total counter prometheus_local_storage_ingested_samples_total 30473 # HELP prometheus_local_storage_invalid_preload_requests_total The total number of preload requests referring to a non-existent series. This is an indication of outdated label indexes. # TYPE prometheus_local_storage_invalid_preload_requests_total counter prometheus_local_storage_invalid_preload_requests_total 0 # HELP prometheus_local_storage_memory_chunkdescs The current number of chunk descriptors in memory. # TYPE prometheus_local_storage_memory_chunkdescs gauge prometheus_local_storage_memory_chunkdescs 1059 # HELP prometheus_local_storage_memory_chunks The current number of chunks in memory, excluding cloned chunks (i.e. chunks without a descriptor). # TYPE prometheus_local_storage_memory_chunks gauge prometheus_local_storage_memory_chunks 1020 # HELP prometheus_local_storage_memory_series The current number of series in memory. # TYPE prometheus_local_storage_memory_series gauge prometheus_local_storage_memory_series 424 # HELP prometheus_local_storage_persist_latency_microseconds A summary of latencies for persisting each chunk. # TYPE prometheus_local_storage_persist_latency_microseconds summary prometheus_local_storage_persist_latency_microseconds{quantile="0.5"} 30.377 prometheus_local_storage_persist_latency_microseconds{quantile="0.9"} 203.539 prometheus_local_storage_persist_latency_microseconds{quantile="0.99"} 2626.463 prometheus_local_storage_persist_latency_microseconds_sum 20424.415 prometheus_local_storage_persist_latency_microseconds_count 174 # HELP prometheus_local_storage_persist_queue_capacity The total capacity of the persist queue. # TYPE prometheus_local_storage_persist_queue_capacity gauge prometheus_local_storage_persist_queue_capacity 1024 # HELP prometheus_local_storage_persist_queue_length The current number of chunks waiting in the persist queue. # TYPE prometheus_local_storage_persist_queue_length gauge prometheus_local_storage_persist_queue_length 0 # HELP prometheus_local_storage_series_ops_total The total number of series operations by their type. # TYPE prometheus_local_storage_series_ops_total counter prometheus_local_storage_series_ops_total{type="create"} 2 prometheus_local_storage_series_ops_total{type="maintenance_in_memory"} 11 # HELP prometheus_notifications_latency_milliseconds Latency quantiles for sending alert notifications (not including dropped notifications). # TYPE prometheus_notifications_latency_milliseconds summary prometheus_notifications_latency_milliseconds{quantile="0.5"} 0 prometheus_notifications_latency_milliseconds{quantile="0.9"} 0 prometheus_notifications_latency_milliseconds{quantile="0.99"} 0 prometheus_notifications_latency_milliseconds_sum 0 prometheus_notifications_latency_milliseconds_count 0 # HELP prometheus_notifications_queue_capacity The capacity of the alert notifications queue. # TYPE prometheus_notifications_queue_capacity gauge prometheus_notifications_queue_capacity 100 # HELP prometheus_notifications_queue_length The number of alert notifications in the queue. # TYPE prometheus_notifications_queue_length gauge prometheus_notifications_queue_length 0 # HELP prometheus_rule_evaluation_duration_milliseconds The duration for a rule to execute. # TYPE prometheus_rule_evaluation_duration_milliseconds summary prometheus_rule_evaluation_duration_milliseconds{rule_type="alerting",quantile="0.5"} 0 prometheus_rule_evaluation_duration_milliseconds{rule_type="alerting",quantile="0.9"} 0 prometheus_rule_evaluation_duration_milliseconds{rule_type="alerting",quantile="0.99"} 2 prometheus_rule_evaluation_duration_milliseconds_sum{rule_type="alerting"} 12 prometheus_rule_evaluation_duration_milliseconds_count{rule_type="alerting"} 115 prometheus_rule_evaluation_duration_milliseconds{rule_type="recording",quantile="0.5"} 0 prometheus_rule_evaluation_duration_milliseconds{rule_type="recording",quantile="0.9"} 0 prometheus_rule_evaluation_duration_milliseconds{rule_type="recording",quantile="0.99"} 3 prometheus_rule_evaluation_duration_milliseconds_sum{rule_type="recording"} 15 prometheus_rule_evaluation_duration_milliseconds_count{rule_type="recording"} 115 # HELP prometheus_rule_evaluation_failures_total The total number of rule evaluation failures. # TYPE prometheus_rule_evaluation_failures_total counter prometheus_rule_evaluation_failures_total 0 # HELP prometheus_samples_queue_capacity Capacity of the queue for unwritten samples. # TYPE prometheus_samples_queue_capacity gauge prometheus_samples_queue_capacity 4096 # HELP prometheus_samples_queue_length Current number of items in the queue for unwritten samples. Each item comprises all samples exposed by one target as one metric family (i.e. metrics of the same name). # TYPE prometheus_samples_queue_length gauge prometheus_samples_queue_length 0 # HELP prometheus_target_interval_length_seconds Actual intervals between scrapes. # TYPE prometheus_target_interval_length_seconds summary prometheus_target_interval_length_seconds{interval="15s",quantile="0.01"} 14 prometheus_target_interval_length_seconds{interval="15s",quantile="0.05"} 14 prometheus_target_interval_length_seconds{interval="15s",quantile="0.5"} 15 prometheus_target_interval_length_seconds{interval="15s",quantile="0.9"} 15 prometheus_target_interval_length_seconds{interval="15s",quantile="0.99"} 15 prometheus_target_interval_length_seconds_sum{interval="15s"} 175 prometheus_target_interval_length_seconds_count{interval="15s"} 12 prometheus_target_interval_length_seconds{interval="1s",quantile="0.01"} 0 prometheus_target_interval_length_seconds{interval="1s",quantile="0.05"} 0 prometheus_target_interval_length_seconds{interval="1s",quantile="0.5"} 0 prometheus_target_interval_length_seconds{interval="1s",quantile="0.9"} 1 prometheus_target_interval_length_seconds{interval="1s",quantile="0.99"} 1 prometheus_target_interval_length_seconds_sum{interval="1s"} 55 prometheus_target_interval_length_seconds_count{interval="1s"} 117 golang-prometheus-client-0.7.0+ds/text/testdata/text.gz000066400000000000000000000050431256572052500231720ustar00rootroot00000000000000*Ttext[]8}_yM<`|[f,Iý"1!vY(3f BUuSXPrY*i3ee@).e'ljlL1CrL"H-؄@ 0;ۉ["otF4Gz;ō3|9| 3|Icqs=yq$12)X^%hGW>󊛟 ?z40i4 C-Ԙ˫*sCPV5df _f&0aQRg E椂ڮכ6;v$I0EJ~`&jn*:qɥt)R4)5=͋ty(ͼixMm4x43EFSc/[6﷚֐m"vYݿhk0n5sw{S4;ӠRm'0a?{?ظ_f L>RpqP"slAZ'Jr.<8۽J2ޒ8(JaJ5+j 2L=Ay4sxI .jxSYwonNϡGuYm5Gƛo&vj5û㧈qPHHL TujՖpWP(1$,}׏)'0GfaW>E!RYڧ\$z槢؎gFG(AyQkܗ(r5o #k ;_d#,j',hu϶vlJã [f*2L[Uez >'9q+V *Z5E Zr+щXW58DZ5o'%qKT@M/)b񋷋ښK;!K }W*iGaC"]2a3'XGvwXe&0]F`"p[8<%̅JKmK!e"r|HJ4HIՀ+KY)hfi xuXM>.%_ ;jDeXQCn{|$^y7$FŞ˿%)H}BzЄɒ#]TS h{&CA{q-CR