Browse code

vendor: bump prometheus/client_golang v0.9.4, docker/go-metrics v0.0.1

bump docker/go-metrics v0.0.1:

full diff: https://github.com/docker/go-metrics/compare/d466d4f6fd960e01820085bd7e1a24426ee7ef18...v0.0.1

- docker/go-metrics#16 fix the compilation error against prometheus/client-golang master
- fixes docker/go-metrics#12 No longer builds against Prom master
- docker/go-metrics#18 metrics: address compile error correctly
- fixes docker/go-metrics#12 No longer builds against Prom master
- docker/go-metrics#15 Add functions that instruments http handler using promhttp
- docker/go-metrics#20 Rename LICENSE.code → LICENSE
- docker/go-metrics#22 Support Go Modules

bump prometheus/client_golang v0.9.4:

full diff: https://github.com/prometheus/client_golang/compare/c5b7fccd204277076155f10851dad72b76a49317...v0.9.4

version v0.9.0 is the minimum required version to work with go-metrics v0.0.1,
as it depends on `prometheus.Observer`:

vendor/github.com/docker/go-metrics/timer.go:39:4: undefined: prometheus.Observer

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>

Sebastiaan van Stijn authored on 2020/01/09 02:44:14
Showing 37 changed files
... ...
@@ -143,7 +143,7 @@ github.com/coreos/pkg                               3ac0863d7acf3bc44daf49afef89
143 143
 code.cloudfoundry.org/clock                         02e53af36e6c978af692887ed449b74026d76fec
144 144
 
145 145
 # prometheus
146
-github.com/prometheus/client_golang                 c5b7fccd204277076155f10851dad72b76a49317 # v0.8.0
146
+github.com/prometheus/client_golang                 2641b987480bca71fb39738eb8c8b0d577cb1d76 # v0.9.4
147 147
 github.com/beorn7/perks                             37c8de3658fcb183f997c4e13e8337516ab753e6 # v1.0.1
148 148
 github.com/prometheus/client_model                  d1d2010b5beead3fa1c5f271a5cf626e40b3ad6e # v0.1.0
149 149
 github.com/prometheus/common                        287d3e634a1e550c9e463dd7e5a75a422c614505 # v0.7.0
... ...
@@ -159,7 +159,7 @@ github.com/inconshreveable/mousetrap                76626ae9c91c4f2a10f34cad8ce8
159 159
 github.com/morikuni/aec                             39771216ff4c63d11f5e604076f9c45e8be1067b
160 160
 
161 161
 # metrics
162
-github.com/docker/go-metrics                        d466d4f6fd960e01820085bd7e1a24426ee7ef18
162
+github.com/docker/go-metrics                        b619b3592b65de4f087d9f16863a7e6ff905973c # v0.0.1
163 163
 
164 164
 github.com/opencontainers/selinux                   3a1f366feb7aecbf7a0e71ac4cea88b31597de9e # v1.2.2
165 165
 
166 166
new file mode 100644
... ...
@@ -0,0 +1,191 @@
0
+
1
+                                 Apache License
2
+                           Version 2.0, January 2004
3
+                        https://www.apache.org/licenses/
4
+
5
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+   1. Definitions.
8
+
9
+      "License" shall mean the terms and conditions for use, reproduction,
10
+      and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+      "Licensor" shall mean the copyright owner or entity authorized by
13
+      the copyright owner that is granting the License.
14
+
15
+      "Legal Entity" shall mean the union of the acting entity and all
16
+      other entities that control, are controlled by, or are under common
17
+      control with that entity. For the purposes of this definition,
18
+      "control" means (i) the power, direct or indirect, to cause the
19
+      direction or management of such entity, whether by contract or
20
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+      outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+      "You" (or "Your") shall mean an individual or Legal Entity
24
+      exercising permissions granted by this License.
25
+
26
+      "Source" form shall mean the preferred form for making modifications,
27
+      including but not limited to software source code, documentation
28
+      source, and configuration files.
29
+
30
+      "Object" form shall mean any form resulting from mechanical
31
+      transformation or translation of a Source form, including but
32
+      not limited to compiled object code, generated documentation,
33
+      and conversions to other media types.
34
+
35
+      "Work" shall mean the work of authorship, whether in Source or
36
+      Object form, made available under the License, as indicated by a
37
+      copyright notice that is included in or attached to the work
38
+      (an example is provided in the Appendix below).
39
+
40
+      "Derivative Works" shall mean any work, whether in Source or Object
41
+      form, that is based on (or derived from) the Work and for which the
42
+      editorial revisions, annotations, elaborations, or other modifications
43
+      represent, as a whole, an original work of authorship. For the purposes
44
+      of this License, Derivative Works shall not include works that remain
45
+      separable from, or merely link (or bind by name) to the interfaces of,
46
+      the Work and Derivative Works thereof.
47
+
48
+      "Contribution" shall mean any work of authorship, including
49
+      the original version of the Work and any modifications or additions
50
+      to that Work or Derivative Works thereof, that is intentionally
51
+      submitted to Licensor for inclusion in the Work by the copyright owner
52
+      or by an individual or Legal Entity authorized to submit on behalf of
53
+      the copyright owner. For the purposes of this definition, "submitted"
54
+      means any form of electronic, verbal, or written communication sent
55
+      to the Licensor or its representatives, including but not limited to
56
+      communication on electronic mailing lists, source code control systems,
57
+      and issue tracking systems that are managed by, or on behalf of, the
58
+      Licensor for the purpose of discussing and improving the Work, but
59
+      excluding communication that is conspicuously marked or otherwise
60
+      designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+      "Contributor" shall mean Licensor and any individual or Legal Entity
63
+      on behalf of whom a Contribution has been received by Licensor and
64
+      subsequently incorporated within the Work.
65
+
66
+   2. Grant of Copyright License. Subject to the terms and conditions of
67
+      this License, each Contributor hereby grants to You a perpetual,
68
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+      copyright license to reproduce, prepare Derivative Works of,
70
+      publicly display, publicly perform, sublicense, and distribute the
71
+      Work and such Derivative Works in Source or Object form.
72
+
73
+   3. Grant of Patent License. Subject to the terms and conditions of
74
+      this License, each Contributor hereby grants to You a perpetual,
75
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+      (except as stated in this section) patent license to make, have made,
77
+      use, offer to sell, sell, import, and otherwise transfer the Work,
78
+      where such license applies only to those patent claims licensable
79
+      by such Contributor that are necessarily infringed by their
80
+      Contribution(s) alone or by combination of their Contribution(s)
81
+      with the Work to which such Contribution(s) was submitted. If You
82
+      institute patent litigation against any entity (including a
83
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+      or a Contribution incorporated within the Work constitutes direct
85
+      or contributory patent infringement, then any patent licenses
86
+      granted to You under this License for that Work shall terminate
87
+      as of the date such litigation is filed.
88
+
89
+   4. Redistribution. You may reproduce and distribute copies of the
90
+      Work or Derivative Works thereof in any medium, with or without
91
+      modifications, and in Source or Object form, provided that You
92
+      meet the following conditions:
93
+
94
+      (a) You must give any other recipients of the Work or
95
+          Derivative Works a copy of this License; and
96
+
97
+      (b) You must cause any modified files to carry prominent notices
98
+          stating that You changed the files; and
99
+
100
+      (c) You must retain, in the Source form of any Derivative Works
101
+          that You distribute, all copyright, patent, trademark, and
102
+          attribution notices from the Source form of the Work,
103
+          excluding those notices that do not pertain to any part of
104
+          the Derivative Works; and
105
+
106
+      (d) If the Work includes a "NOTICE" text file as part of its
107
+          distribution, then any Derivative Works that You distribute must
108
+          include a readable copy of the attribution notices contained
109
+          within such NOTICE file, excluding those notices that do not
110
+          pertain to any part of the Derivative Works, in at least one
111
+          of the following places: within a NOTICE text file distributed
112
+          as part of the Derivative Works; within the Source form or
113
+          documentation, if provided along with the Derivative Works; or,
114
+          within a display generated by the Derivative Works, if and
115
+          wherever such third-party notices normally appear. The contents
116
+          of the NOTICE file are for informational purposes only and
117
+          do not modify the License. You may add Your own attribution
118
+          notices within Derivative Works that You distribute, alongside
119
+          or as an addendum to the NOTICE text from the Work, provided
120
+          that such additional attribution notices cannot be construed
121
+          as modifying the License.
122
+
123
+      You may add Your own copyright statement to Your modifications and
124
+      may provide additional or different license terms and conditions
125
+      for use, reproduction, or distribution of Your modifications, or
126
+      for any such Derivative Works as a whole, provided Your use,
127
+      reproduction, and distribution of the Work otherwise complies with
128
+      the conditions stated in this License.
129
+
130
+   5. Submission of Contributions. Unless You explicitly state otherwise,
131
+      any Contribution intentionally submitted for inclusion in the Work
132
+      by You to the Licensor shall be under the terms and conditions of
133
+      this License, without any additional terms or conditions.
134
+      Notwithstanding the above, nothing herein shall supersede or modify
135
+      the terms of any separate license agreement you may have executed
136
+      with Licensor regarding such Contributions.
137
+
138
+   6. Trademarks. This License does not grant permission to use the trade
139
+      names, trademarks, service marks, or product names of the Licensor,
140
+      except as required for reasonable and customary use in describing the
141
+      origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+   7. Disclaimer of Warranty. Unless required by applicable law or
144
+      agreed to in writing, Licensor provides the Work (and each
145
+      Contributor provides its Contributions) on an "AS IS" BASIS,
146
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+      implied, including, without limitation, any warranties or conditions
148
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+      PARTICULAR PURPOSE. You are solely responsible for determining the
150
+      appropriateness of using or redistributing the Work and assume any
151
+      risks associated with Your exercise of permissions under this License.
152
+
153
+   8. Limitation of Liability. In no event and under no legal theory,
154
+      whether in tort (including negligence), contract, or otherwise,
155
+      unless required by applicable law (such as deliberate and grossly
156
+      negligent acts) or agreed to in writing, shall any Contributor be
157
+      liable to You for damages, including any direct, indirect, special,
158
+      incidental, or consequential damages of any character arising as a
159
+      result of this License or out of the use or inability to use the
160
+      Work (including but not limited to damages for loss of goodwill,
161
+      work stoppage, computer failure or malfunction, or any and all
162
+      other commercial damages or losses), even if such Contributor
163
+      has been advised of the possibility of such damages.
164
+
165
+   9. Accepting Warranty or Additional Liability. While redistributing
166
+      the Work or Derivative Works thereof, You may choose to offer,
167
+      and charge a fee for, acceptance of support, warranty, indemnity,
168
+      or other liability obligations and/or rights consistent with this
169
+      License. However, in accepting such obligations, You may act only
170
+      on Your own behalf and on Your sole responsibility, not on behalf
171
+      of any other Contributor, and only if You agree to indemnify,
172
+      defend, and hold each Contributor harmless for any liability
173
+      incurred by, or claims asserted against, such Contributor by reason
174
+      of your accepting any such warranty or additional liability.
175
+
176
+   END OF TERMS AND CONDITIONS
177
+
178
+   Copyright 2013-2016 Docker, Inc.
179
+
180
+   Licensed under the Apache License, Version 2.0 (the "License");
181
+   you may not use this file except in compliance with the License.
182
+   You may obtain a copy of the License at
183
+
184
+       https://www.apache.org/licenses/LICENSE-2.0
185
+
186
+   Unless required by applicable law or agreed to in writing, software
187
+   distributed under the License is distributed on an "AS IS" BASIS,
188
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
189
+   See the License for the specific language governing permissions and
190
+   limitations under the License.
0 191
deleted file mode 100644
... ...
@@ -1,191 +0,0 @@
1
-
2
-                                 Apache License
3
-                           Version 2.0, January 2004
4
-                        https://www.apache.org/licenses/
5
-
6
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
-
8
-   1. Definitions.
9
-
10
-      "License" shall mean the terms and conditions for use, reproduction,
11
-      and distribution as defined by Sections 1 through 9 of this document.
12
-
13
-      "Licensor" shall mean the copyright owner or entity authorized by
14
-      the copyright owner that is granting the License.
15
-
16
-      "Legal Entity" shall mean the union of the acting entity and all
17
-      other entities that control, are controlled by, or are under common
18
-      control with that entity. For the purposes of this definition,
19
-      "control" means (i) the power, direct or indirect, to cause the
20
-      direction or management of such entity, whether by contract or
21
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
-      outstanding shares, or (iii) beneficial ownership of such entity.
23
-
24
-      "You" (or "Your") shall mean an individual or Legal Entity
25
-      exercising permissions granted by this License.
26
-
27
-      "Source" form shall mean the preferred form for making modifications,
28
-      including but not limited to software source code, documentation
29
-      source, and configuration files.
30
-
31
-      "Object" form shall mean any form resulting from mechanical
32
-      transformation or translation of a Source form, including but
33
-      not limited to compiled object code, generated documentation,
34
-      and conversions to other media types.
35
-
36
-      "Work" shall mean the work of authorship, whether in Source or
37
-      Object form, made available under the License, as indicated by a
38
-      copyright notice that is included in or attached to the work
39
-      (an example is provided in the Appendix below).
40
-
41
-      "Derivative Works" shall mean any work, whether in Source or Object
42
-      form, that is based on (or derived from) the Work and for which the
43
-      editorial revisions, annotations, elaborations, or other modifications
44
-      represent, as a whole, an original work of authorship. For the purposes
45
-      of this License, Derivative Works shall not include works that remain
46
-      separable from, or merely link (or bind by name) to the interfaces of,
47
-      the Work and Derivative Works thereof.
48
-
49
-      "Contribution" shall mean any work of authorship, including
50
-      the original version of the Work and any modifications or additions
51
-      to that Work or Derivative Works thereof, that is intentionally
52
-      submitted to Licensor for inclusion in the Work by the copyright owner
53
-      or by an individual or Legal Entity authorized to submit on behalf of
54
-      the copyright owner. For the purposes of this definition, "submitted"
55
-      means any form of electronic, verbal, or written communication sent
56
-      to the Licensor or its representatives, including but not limited to
57
-      communication on electronic mailing lists, source code control systems,
58
-      and issue tracking systems that are managed by, or on behalf of, the
59
-      Licensor for the purpose of discussing and improving the Work, but
60
-      excluding communication that is conspicuously marked or otherwise
61
-      designated in writing by the copyright owner as "Not a Contribution."
62
-
63
-      "Contributor" shall mean Licensor and any individual or Legal Entity
64
-      on behalf of whom a Contribution has been received by Licensor and
65
-      subsequently incorporated within the Work.
66
-
67
-   2. Grant of Copyright License. Subject to the terms and conditions of
68
-      this License, each Contributor hereby grants to You a perpetual,
69
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
-      copyright license to reproduce, prepare Derivative Works of,
71
-      publicly display, publicly perform, sublicense, and distribute the
72
-      Work and such Derivative Works in Source or Object form.
73
-
74
-   3. Grant of Patent License. Subject to the terms and conditions of
75
-      this License, each Contributor hereby grants to You a perpetual,
76
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
-      (except as stated in this section) patent license to make, have made,
78
-      use, offer to sell, sell, import, and otherwise transfer the Work,
79
-      where such license applies only to those patent claims licensable
80
-      by such Contributor that are necessarily infringed by their
81
-      Contribution(s) alone or by combination of their Contribution(s)
82
-      with the Work to which such Contribution(s) was submitted. If You
83
-      institute patent litigation against any entity (including a
84
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
85
-      or a Contribution incorporated within the Work constitutes direct
86
-      or contributory patent infringement, then any patent licenses
87
-      granted to You under this License for that Work shall terminate
88
-      as of the date such litigation is filed.
89
-
90
-   4. Redistribution. You may reproduce and distribute copies of the
91
-      Work or Derivative Works thereof in any medium, with or without
92
-      modifications, and in Source or Object form, provided that You
93
-      meet the following conditions:
94
-
95
-      (a) You must give any other recipients of the Work or
96
-          Derivative Works a copy of this License; and
97
-
98
-      (b) You must cause any modified files to carry prominent notices
99
-          stating that You changed the files; and
100
-
101
-      (c) You must retain, in the Source form of any Derivative Works
102
-          that You distribute, all copyright, patent, trademark, and
103
-          attribution notices from the Source form of the Work,
104
-          excluding those notices that do not pertain to any part of
105
-          the Derivative Works; and
106
-
107
-      (d) If the Work includes a "NOTICE" text file as part of its
108
-          distribution, then any Derivative Works that You distribute must
109
-          include a readable copy of the attribution notices contained
110
-          within such NOTICE file, excluding those notices that do not
111
-          pertain to any part of the Derivative Works, in at least one
112
-          of the following places: within a NOTICE text file distributed
113
-          as part of the Derivative Works; within the Source form or
114
-          documentation, if provided along with the Derivative Works; or,
115
-          within a display generated by the Derivative Works, if and
116
-          wherever such third-party notices normally appear. The contents
117
-          of the NOTICE file are for informational purposes only and
118
-          do not modify the License. You may add Your own attribution
119
-          notices within Derivative Works that You distribute, alongside
120
-          or as an addendum to the NOTICE text from the Work, provided
121
-          that such additional attribution notices cannot be construed
122
-          as modifying the License.
123
-
124
-      You may add Your own copyright statement to Your modifications and
125
-      may provide additional or different license terms and conditions
126
-      for use, reproduction, or distribution of Your modifications, or
127
-      for any such Derivative Works as a whole, provided Your use,
128
-      reproduction, and distribution of the Work otherwise complies with
129
-      the conditions stated in this License.
130
-
131
-   5. Submission of Contributions. Unless You explicitly state otherwise,
132
-      any Contribution intentionally submitted for inclusion in the Work
133
-      by You to the Licensor shall be under the terms and conditions of
134
-      this License, without any additional terms or conditions.
135
-      Notwithstanding the above, nothing herein shall supersede or modify
136
-      the terms of any separate license agreement you may have executed
137
-      with Licensor regarding such Contributions.
138
-
139
-   6. Trademarks. This License does not grant permission to use the trade
140
-      names, trademarks, service marks, or product names of the Licensor,
141
-      except as required for reasonable and customary use in describing the
142
-      origin of the Work and reproducing the content of the NOTICE file.
143
-
144
-   7. Disclaimer of Warranty. Unless required by applicable law or
145
-      agreed to in writing, Licensor provides the Work (and each
146
-      Contributor provides its Contributions) on an "AS IS" BASIS,
147
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
-      implied, including, without limitation, any warranties or conditions
149
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
-      PARTICULAR PURPOSE. You are solely responsible for determining the
151
-      appropriateness of using or redistributing the Work and assume any
152
-      risks associated with Your exercise of permissions under this License.
153
-
154
-   8. Limitation of Liability. In no event and under no legal theory,
155
-      whether in tort (including negligence), contract, or otherwise,
156
-      unless required by applicable law (such as deliberate and grossly
157
-      negligent acts) or agreed to in writing, shall any Contributor be
158
-      liable to You for damages, including any direct, indirect, special,
159
-      incidental, or consequential damages of any character arising as a
160
-      result of this License or out of the use or inability to use the
161
-      Work (including but not limited to damages for loss of goodwill,
162
-      work stoppage, computer failure or malfunction, or any and all
163
-      other commercial damages or losses), even if such Contributor
164
-      has been advised of the possibility of such damages.
165
-
166
-   9. Accepting Warranty or Additional Liability. While redistributing
167
-      the Work or Derivative Works thereof, You may choose to offer,
168
-      and charge a fee for, acceptance of support, warranty, indemnity,
169
-      or other liability obligations and/or rights consistent with this
170
-      License. However, in accepting such obligations, You may act only
171
-      on Your own behalf and on Your sole responsibility, not on behalf
172
-      of any other Contributor, and only if You agree to indemnify,
173
-      defend, and hold each Contributor harmless for any liability
174
-      incurred by, or claims asserted against, such Contributor by reason
175
-      of your accepting any such warranty or additional liability.
176
-
177
-   END OF TERMS AND CONDITIONS
178
-
179
-   Copyright 2013-2016 Docker, Inc.
180
-
181
-   Licensed under the Apache License, Version 2.0 (the "License");
182
-   you may not use this file except in compliance with the License.
183
-   You may obtain a copy of the License at
184
-
185
-       https://www.apache.org/licenses/LICENSE-2.0
186
-
187
-   Unless required by applicable law or agreed to in writing, software
188
-   distributed under the License is distributed on an "AS IS" BASIS,
189
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
190
-   See the License for the specific language governing permissions and
191
-   limitations under the License.
... ...
@@ -68,9 +68,21 @@ If you need to use a unit but it is not defined in the package please open a PR
68 68
 
69 69
 Package documentation can be found [here](https://godoc.org/github.com/docker/go-metrics).
70 70
 
71
+## HTTP Metrics
72
+
73
+To instrument a http handler, you can wrap the code like this:
74
+
75
+```go
76
+namespace := metrics.NewNamespace("docker_distribution", "http", metrics.Labels{"handler": "your_http_handler_name"})
77
+httpMetrics := namespace.NewDefaultHttpMetrics()
78
+metrics.Register(namespace)
79
+instrumentedHandler = metrics.InstrumentHandler(httpMetrics, unInstrumentedHandler)
80
+```
81
+Note: The `handler` label must be provided when a new namespace is created.
82
+
71 83
 ## Additional Metrics
72 84
 
73
-Additional metrics are also defined here that are not avaliable in the prometheus client.
85
+Additional metrics are also defined here that are not available in the prometheus client.
74 86
 If you need a custom metrics and it is generic enough to be used by multiple projects, define it here.
75 87
 
76 88
 
77 89
new file mode 100644
... ...
@@ -0,0 +1,5 @@
0
+module github.com/docker/go-metrics
1
+
2
+go 1.11
3
+
4
+require github.com/prometheus/client_golang v1.1.0
... ...
@@ -4,10 +4,71 @@ import (
4 4
 	"net/http"
5 5
 
6 6
 	"github.com/prometheus/client_golang/prometheus"
7
+	"github.com/prometheus/client_golang/prometheus/promhttp"
8
+)
9
+
10
+// HTTPHandlerOpts describes a set of configurable options of http metrics
11
+type HTTPHandlerOpts struct {
12
+	DurationBuckets     []float64
13
+	RequestSizeBuckets  []float64
14
+	ResponseSizeBuckets []float64
15
+}
16
+
17
+const (
18
+	InstrumentHandlerResponseSize = iota
19
+	InstrumentHandlerRequestSize
20
+	InstrumentHandlerDuration
21
+	InstrumentHandlerCounter
22
+	InstrumentHandlerInFlight
23
+)
24
+
25
+type HTTPMetric struct {
26
+	prometheus.Collector
27
+	handlerType int
28
+}
29
+
30
+var (
31
+	defaultDurationBuckets     = []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10, 25, 60}
32
+	defaultRequestSizeBuckets  = prometheus.ExponentialBuckets(1024, 2, 22) //1K to 4G
33
+	defaultResponseSizeBuckets = defaultRequestSizeBuckets
7 34
 )
8 35
 
9 36
 // Handler returns the global http.Handler that provides the prometheus
10
-// metrics format on GET requests
37
+// metrics format on GET requests. This handler is no longer instrumented.
11 38
 func Handler() http.Handler {
12
-	return prometheus.Handler()
39
+	return promhttp.Handler()
40
+}
41
+
42
+func InstrumentHandler(metrics []*HTTPMetric, handler http.Handler) http.HandlerFunc {
43
+	return InstrumentHandlerFunc(metrics, handler.ServeHTTP)
44
+}
45
+
46
+func InstrumentHandlerFunc(metrics []*HTTPMetric, handlerFunc http.HandlerFunc) http.HandlerFunc {
47
+	var handler http.Handler
48
+	handler = http.HandlerFunc(handlerFunc)
49
+	for _, metric := range metrics {
50
+		switch metric.handlerType {
51
+		case InstrumentHandlerResponseSize:
52
+			if collector, ok := metric.Collector.(prometheus.ObserverVec); ok {
53
+				handler = promhttp.InstrumentHandlerResponseSize(collector, handler)
54
+			}
55
+		case InstrumentHandlerRequestSize:
56
+			if collector, ok := metric.Collector.(prometheus.ObserverVec); ok {
57
+				handler = promhttp.InstrumentHandlerRequestSize(collector, handler)
58
+			}
59
+		case InstrumentHandlerDuration:
60
+			if collector, ok := metric.Collector.(prometheus.ObserverVec); ok {
61
+				handler = promhttp.InstrumentHandlerDuration(collector, handler)
62
+			}
63
+		case InstrumentHandlerCounter:
64
+			if collector, ok := metric.Collector.(*prometheus.CounterVec); ok {
65
+				handler = promhttp.InstrumentHandlerCounter(collector, handler)
66
+			}
67
+		case InstrumentHandlerInFlight:
68
+			if collector, ok := metric.Collector.(prometheus.Gauge); ok {
69
+				handler = promhttp.InstrumentHandlerInFlight(collector, handler)
70
+			}
71
+		}
72
+	}
73
+	return handler.ServeHTTP
13 74
 }
... ...
@@ -179,3 +179,137 @@ func makeName(name string, unit Unit) string {
179 179
 
180 180
 	return fmt.Sprintf("%s_%s", name, unit)
181 181
 }
182
+
183
+func (n *Namespace) NewDefaultHttpMetrics(handlerName string) []*HTTPMetric {
184
+	return n.NewHttpMetricsWithOpts(handlerName, HTTPHandlerOpts{
185
+		DurationBuckets:     defaultDurationBuckets,
186
+		RequestSizeBuckets:  defaultResponseSizeBuckets,
187
+		ResponseSizeBuckets: defaultResponseSizeBuckets,
188
+	})
189
+}
190
+
191
+func (n *Namespace) NewHttpMetrics(handlerName string, durationBuckets, requestSizeBuckets, responseSizeBuckets []float64) []*HTTPMetric {
192
+	return n.NewHttpMetricsWithOpts(handlerName, HTTPHandlerOpts{
193
+		DurationBuckets:     durationBuckets,
194
+		RequestSizeBuckets:  requestSizeBuckets,
195
+		ResponseSizeBuckets: responseSizeBuckets,
196
+	})
197
+}
198
+
199
+func (n *Namespace) NewHttpMetricsWithOpts(handlerName string, opts HTTPHandlerOpts) []*HTTPMetric {
200
+	var httpMetrics []*HTTPMetric
201
+	inFlightMetric := n.NewInFlightGaugeMetric(handlerName)
202
+	requestTotalMetric := n.NewRequestTotalMetric(handlerName)
203
+	requestDurationMetric := n.NewRequestDurationMetric(handlerName, opts.DurationBuckets)
204
+	requestSizeMetric := n.NewRequestSizeMetric(handlerName, opts.RequestSizeBuckets)
205
+	responseSizeMetric := n.NewResponseSizeMetric(handlerName, opts.ResponseSizeBuckets)
206
+	httpMetrics = append(httpMetrics, inFlightMetric, requestDurationMetric, requestTotalMetric, requestSizeMetric, responseSizeMetric)
207
+	return httpMetrics
208
+}
209
+
210
+func (n *Namespace) NewInFlightGaugeMetric(handlerName string) *HTTPMetric {
211
+	labels := prometheus.Labels(n.labels)
212
+	labels["handler"] = handlerName
213
+	metric := prometheus.NewGauge(prometheus.GaugeOpts{
214
+		Namespace:   n.name,
215
+		Subsystem:   n.subsystem,
216
+		Name:        "in_flight_requests",
217
+		Help:        "The in-flight HTTP requests",
218
+		ConstLabels: prometheus.Labels(labels),
219
+	})
220
+	httpMetric := &HTTPMetric{
221
+		Collector:   metric,
222
+		handlerType: InstrumentHandlerInFlight,
223
+	}
224
+	n.Add(httpMetric)
225
+	return httpMetric
226
+}
227
+
228
+func (n *Namespace) NewRequestTotalMetric(handlerName string) *HTTPMetric {
229
+	labels := prometheus.Labels(n.labels)
230
+	labels["handler"] = handlerName
231
+	metric := prometheus.NewCounterVec(
232
+		prometheus.CounterOpts{
233
+			Namespace:   n.name,
234
+			Subsystem:   n.subsystem,
235
+			Name:        "requests_total",
236
+			Help:        "Total number of HTTP requests made.",
237
+			ConstLabels: prometheus.Labels(labels),
238
+		},
239
+		[]string{"code", "method"},
240
+	)
241
+	httpMetric := &HTTPMetric{
242
+		Collector:   metric,
243
+		handlerType: InstrumentHandlerCounter,
244
+	}
245
+	n.Add(httpMetric)
246
+	return httpMetric
247
+}
248
+func (n *Namespace) NewRequestDurationMetric(handlerName string, buckets []float64) *HTTPMetric {
249
+	if len(buckets) == 0 {
250
+		panic("DurationBuckets must be provided")
251
+	}
252
+	labels := prometheus.Labels(n.labels)
253
+	labels["handler"] = handlerName
254
+	opts := prometheus.HistogramOpts{
255
+		Namespace:   n.name,
256
+		Subsystem:   n.subsystem,
257
+		Name:        "request_duration_seconds",
258
+		Help:        "The HTTP request latencies in seconds.",
259
+		Buckets:     buckets,
260
+		ConstLabels: prometheus.Labels(labels),
261
+	}
262
+	metric := prometheus.NewHistogramVec(opts, []string{"method"})
263
+	httpMetric := &HTTPMetric{
264
+		Collector:   metric,
265
+		handlerType: InstrumentHandlerDuration,
266
+	}
267
+	n.Add(httpMetric)
268
+	return httpMetric
269
+}
270
+
271
+func (n *Namespace) NewRequestSizeMetric(handlerName string, buckets []float64) *HTTPMetric {
272
+	if len(buckets) == 0 {
273
+		panic("RequestSizeBuckets must be provided")
274
+	}
275
+	labels := prometheus.Labels(n.labels)
276
+	labels["handler"] = handlerName
277
+	opts := prometheus.HistogramOpts{
278
+		Namespace:   n.name,
279
+		Subsystem:   n.subsystem,
280
+		Name:        "request_size_bytes",
281
+		Help:        "The HTTP request sizes in bytes.",
282
+		Buckets:     buckets,
283
+		ConstLabels: prometheus.Labels(labels),
284
+	}
285
+	metric := prometheus.NewHistogramVec(opts, []string{})
286
+	httpMetric := &HTTPMetric{
287
+		Collector:   metric,
288
+		handlerType: InstrumentHandlerRequestSize,
289
+	}
290
+	n.Add(httpMetric)
291
+	return httpMetric
292
+}
293
+
294
+func (n *Namespace) NewResponseSizeMetric(handlerName string, buckets []float64) *HTTPMetric {
295
+	if len(buckets) == 0 {
296
+		panic("ResponseSizeBuckets must be provided")
297
+	}
298
+	labels := prometheus.Labels(n.labels)
299
+	labels["handler"] = handlerName
300
+	opts := prometheus.HistogramOpts{
301
+		Namespace:   n.name,
302
+		Subsystem:   n.subsystem,
303
+		Name:        "response_size_bytes",
304
+		Help:        "The HTTP response sizes in bytes.",
305
+		Buckets:     buckets,
306
+		ConstLabels: prometheus.Labels(labels),
307
+	}
308
+	metrics := prometheus.NewHistogramVec(opts, []string{})
309
+	httpMetric := &HTTPMetric{
310
+		Collector:   metrics,
311
+		handlerType: InstrumentHandlerResponseSize,
312
+	}
313
+	n.Add(httpMetric)
314
+	return httpMetric
315
+}
... ...
@@ -28,15 +28,27 @@ type Timer interface {
28 28
 
29 29
 // LabeledTimer is a timer that must have label values populated before use.
30 30
 type LabeledTimer interface {
31
-	WithValues(labels ...string) Timer
31
+	WithValues(labels ...string) *labeledTimerObserver
32 32
 }
33 33
 
34 34
 type labeledTimer struct {
35 35
 	m *prometheus.HistogramVec
36 36
 }
37 37
 
38
-func (lt *labeledTimer) WithValues(labels ...string) Timer {
39
-	return &timer{m: lt.m.WithLabelValues(labels...)}
38
+type labeledTimerObserver struct {
39
+	m prometheus.Observer
40
+}
41
+
42
+func (lbo *labeledTimerObserver) Update(duration time.Duration) {
43
+	lbo.m.Observe(duration.Seconds())
44
+}
45
+
46
+func (lbo *labeledTimerObserver) UpdateSince(since time.Time) {
47
+	lbo.m.Observe(time.Since(since).Seconds())
48
+}
49
+
50
+func (lt *labeledTimer) WithValues(labels ...string) *labeledTimerObserver {
51
+	return &labeledTimerObserver{m: lt.m.WithLabelValues(labels...)}
40 52
 }
41 53
 
42 54
 func (lt *labeledTimer) Describe(c chan<- *prometheus.Desc) {
... ...
@@ -48,7 +60,7 @@ func (lt *labeledTimer) Collect(c chan<- prometheus.Metric) {
48 48
 }
49 49
 
50 50
 type timer struct {
51
-	m prometheus.Histogram
51
+	m prometheus.Observer
52 52
 }
53 53
 
54 54
 func (t *timer) Update(duration time.Duration) {
... ...
@@ -60,9 +72,14 @@ func (t *timer) UpdateSince(since time.Time) {
60 60
 }
61 61
 
62 62
 func (t *timer) Describe(c chan<- *prometheus.Desc) {
63
-	t.m.Describe(c)
63
+	c <- t.m.(prometheus.Metric).Desc()
64 64
 }
65 65
 
66 66
 func (t *timer) Collect(c chan<- prometheus.Metric) {
67
-	t.m.Collect(c)
67
+	// Are there any observers that don't implement Collector? It is really
68
+	// unclear what the point of the upstream change was, but we'll let this
69
+	// panic if we get an observer that doesn't implement collector. In this
70
+	// case, we should almost always see metricVec objects, so this should
71
+	// never panic.
72
+	t.m.(prometheus.Collector).Collect(c)
68 73
 }
... ...
@@ -1,12 +1,52 @@
1 1
 # Prometheus Go client library
2 2
 
3 3
 [![Build Status](https://travis-ci.org/prometheus/client_golang.svg?branch=master)](https://travis-ci.org/prometheus/client_golang)
4
+[![Go Report Card](https://goreportcard.com/badge/github.com/prometheus/client_golang)](https://goreportcard.com/report/github.com/prometheus/client_golang)
5
+[![go-doc](https://godoc.org/github.com/prometheus/client_golang?status.svg)](https://godoc.org/github.com/prometheus/client_golang)
4 6
 
5 7
 This is the [Go](http://golang.org) client library for
6 8
 [Prometheus](http://prometheus.io). It has two separate parts, one for
7 9
 instrumenting application code, and one for creating clients that talk to the
8 10
 Prometheus HTTP API.
9 11
 
12
+__This library requires Go1.9 or later.__
13
+
14
+## Important note about releases, versioning, tagging, and stability
15
+
16
+In this repository, we used to mostly ignore the many coming and going
17
+dependency management tools for Go and instead wait for a tool that most of the
18
+community would converge on. Our bet is that this tool has arrived now in the
19
+form of [Go
20
+Modules](https://github.com/golang/go/wiki/Modules#how-to-upgrade-and-downgrade-dependencies).
21
+
22
+To make full use of what Go Modules are offering, the previous versioning
23
+roadmap for this repository had to be changed. In particular, Go Modules
24
+finally provide a way for incompatible versions of the same package to coexist
25
+in the same binary. For that, however, the versions must be tagged with
26
+different major versions of 1 or greater (following [Semantic
27
+Versioning](https://semver.org/)). Thus, we decided to abandon the original
28
+plan of introducing a lot of breaking changes _before_ releasing v1 of this
29
+repository, mostly driven by the widespread use this repository already has and
30
+the relatively stable state it is in.
31
+
32
+To leverage the mechanism Go Modules offers for a transition between major
33
+version, the current plan is the following:
34
+
35
+- The v0.9.x series of releases will see a small number of bugfix releases to
36
+  deal with a few remaining minor issues (#543, #542, #539).
37
+- After that, all features currently marked as _deprecated_ will be removed,
38
+  and the result will be released as v1.0.0.
39
+- The planned breaking changes previously gathered as part of the v0.10
40
+  milestone will now go into the v2 milestone. The v2 development happens in a
41
+  [separate branch](https://github.com/prometheus/client_golang/tree/dev-v2)
42
+  for the time being. v2 releases off that branch will happen once sufficient
43
+  stability is reached. v1 and v2 will coexist for a while to enable a
44
+  convenient transition.
45
+- The API client in prometheus/client_golang/api/… is still considered
46
+  experimental. While it will be tagged alongside the rest of the code
47
+  according to the plan above, we cannot strictly guarantee semver semantics
48
+  for it.
49
+
10 50
 ## Instrumenting applications
11 51
 
12 52
 [![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)
... ...
@@ -14,8 +54,8 @@ Prometheus HTTP API.
14 14
 The
15 15
 [`prometheus` directory](https://github.com/prometheus/client_golang/tree/master/prometheus)
16 16
 contains the instrumentation library. See the
17
-[best practices section](http://prometheus.io/docs/practices/naming/) of the
18
-Prometheus documentation to learn more about instrumenting applications.
17
+[guide](https://prometheus.io/docs/guides/go-application/) on the Prometheus
18
+website to learn more about instrumenting applications.
19 19
 
20 20
 The
21 21
 [`examples` directory](https://github.com/prometheus/client_golang/tree/master/examples)
... ...
@@ -23,13 +63,14 @@ contains simple examples of instrumented code.
23 23
 
24 24
 ## Client for the Prometheus HTTP API
25 25
 
26
-[![code-coverage](http://gocover.io/_badge/github.com/prometheus/client_golang/api/prometheus)](http://gocover.io/github.com/prometheus/client_golang/api/prometheus) [![go-doc](https://godoc.org/github.com/prometheus/client_golang/api/prometheus?status.svg)](https://godoc.org/github.com/prometheus/client_golang/api/prometheus)
26
+[![code-coverage](http://gocover.io/_badge/github.com/prometheus/client_golang/api/prometheus/v1)](http://gocover.io/github.com/prometheus/client_golang/api/prometheus/v1) [![go-doc](https://godoc.org/github.com/prometheus/client_golang/api/prometheus?status.svg)](https://godoc.org/github.com/prometheus/client_golang/api)
27 27
 
28 28
 The
29 29
 [`api/prometheus` directory](https://github.com/prometheus/client_golang/tree/master/api/prometheus)
30 30
 contains the client for the
31 31
 [Prometheus HTTP API](http://prometheus.io/docs/querying/api/). It allows you
32
-to write Go applications that query time series data from a Prometheus server.
32
+to write Go applications that query time series data from a Prometheus
33
+server. It is still in alpha stage.
33 34
 
34 35
 ## Where is `model`, `extraction`, and `text`?
35 36
 
36 37
new file mode 100644
... ...
@@ -0,0 +1,13 @@
0
+module github.com/prometheus/client_golang
1
+
2
+require (
3
+	github.com/beorn7/perks v1.0.0
4
+	github.com/golang/protobuf v1.3.1
5
+	github.com/json-iterator/go v1.1.6
6
+	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
7
+	github.com/modern-go/reflect2 v1.0.1 // indirect
8
+	github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90
9
+	github.com/prometheus/common v0.4.1
10
+	github.com/prometheus/procfs v0.0.2
11
+	github.com/stretchr/testify v1.3.0 // indirect
12
+)
0 13
new file mode 100644
... ...
@@ -0,0 +1,29 @@
0
+// Copyright 2019 The Prometheus Authors
1
+// Licensed under the Apache License, Version 2.0 (the "License");
2
+// you may not use this file except in compliance with the License.
3
+// You may obtain a copy of the License at
4
+//
5
+// http://www.apache.org/licenses/LICENSE-2.0
6
+//
7
+// Unless required by applicable law or agreed to in writing, software
8
+// distributed under the License is distributed on an "AS IS" BASIS,
9
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+// See the License for the specific language governing permissions and
11
+// limitations under the License.
12
+
13
+// +build go1.12
14
+
15
+package prometheus
16
+
17
+import "runtime/debug"
18
+
19
+// readBuildInfo is a wrapper around debug.ReadBuildInfo for Go 1.12+.
20
+func readBuildInfo() (path, version, sum string) {
21
+	path, version, sum = "unknown", "unknown", "unknown"
22
+	if bi, ok := debug.ReadBuildInfo(); ok {
23
+		path = bi.Main.Path
24
+		version = bi.Main.Version
25
+		sum = bi.Main.Sum
26
+	}
27
+	return
28
+}
0 29
new file mode 100644
... ...
@@ -0,0 +1,22 @@
0
+// Copyright 2019 The Prometheus Authors
1
+// Licensed under the Apache License, Version 2.0 (the "License");
2
+// you may not use this file except in compliance with the License.
3
+// You may obtain a copy of the License at
4
+//
5
+// http://www.apache.org/licenses/LICENSE-2.0
6
+//
7
+// Unless required by applicable law or agreed to in writing, software
8
+// distributed under the License is distributed on an "AS IS" BASIS,
9
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+// See the License for the specific language governing permissions and
11
+// limitations under the License.
12
+
13
+// +build !go1.12
14
+
15
+package prometheus
16
+
17
+// readBuildInfo is a wrapper around debug.ReadBuildInfo for Go versions before
18
+// 1.12. Remove this whole file once the minimum supported Go version is 1.12.
19
+func readBuildInfo() (path, version, sum string) {
20
+	return "unknown", "unknown", "unknown"
21
+}
... ...
@@ -29,27 +29,72 @@ type Collector interface {
29 29
 	// collected by this Collector to the provided channel and returns once
30 30
 	// the last descriptor has been sent. The sent descriptors fulfill the
31 31
 	// consistency and uniqueness requirements described in the Desc
32
-	// documentation. (It is valid if one and the same Collector sends
33
-	// duplicate descriptors. Those duplicates are simply ignored. However,
34
-	// two different Collectors must not send duplicate descriptors.) This
35
-	// method idempotently sends the same descriptors throughout the
36
-	// lifetime of the Collector. If a Collector encounters an error while
37
-	// executing this method, it must send an invalid descriptor (created
38
-	// with NewInvalidDesc) to signal the error to the registry.
32
+	// documentation.
33
+	//
34
+	// It is valid if one and the same Collector sends duplicate
35
+	// descriptors. Those duplicates are simply ignored. However, two
36
+	// different Collectors must not send duplicate descriptors.
37
+	//
38
+	// Sending no descriptor at all marks the Collector as “unchecked”,
39
+	// i.e. no checks will be performed at registration time, and the
40
+	// Collector may yield any Metric it sees fit in its Collect method.
41
+	//
42
+	// This method idempotently sends the same descriptors throughout the
43
+	// lifetime of the Collector. It may be called concurrently and
44
+	// therefore must be implemented in a concurrency safe way.
45
+	//
46
+	// If a Collector encounters an error while executing this method, it
47
+	// must send an invalid descriptor (created with NewInvalidDesc) to
48
+	// signal the error to the registry.
39 49
 	Describe(chan<- *Desc)
40 50
 	// Collect is called by the Prometheus registry when collecting
41 51
 	// metrics. The implementation sends each collected metric via the
42 52
 	// provided channel and returns once the last metric has been sent. The
43
-	// descriptor of each sent metric is one of those returned by
44
-	// Describe. Returned metrics that share the same descriptor must differ
45
-	// in their variable label values. This method may be called
46
-	// concurrently and must therefore be implemented in a concurrency safe
47
-	// way. Blocking occurs at the expense of total performance of rendering
48
-	// all registered metrics. Ideally, Collector implementations support
49
-	// concurrent readers.
53
+	// descriptor of each sent metric is one of those returned by Describe
54
+	// (unless the Collector is unchecked, see above). Returned metrics that
55
+	// share the same descriptor must differ in their variable label
56
+	// values.
57
+	//
58
+	// This method may be called concurrently and must therefore be
59
+	// implemented in a concurrency safe way. Blocking occurs at the expense
60
+	// of total performance of rendering all registered metrics. Ideally,
61
+	// Collector implementations support concurrent readers.
50 62
 	Collect(chan<- Metric)
51 63
 }
52 64
 
65
+// DescribeByCollect is a helper to implement the Describe method of a custom
66
+// Collector. It collects the metrics from the provided Collector and sends
67
+// their descriptors to the provided channel.
68
+//
69
+// If a Collector collects the same metrics throughout its lifetime, its
70
+// Describe method can simply be implemented as:
71
+//
72
+//   func (c customCollector) Describe(ch chan<- *Desc) {
73
+//   	DescribeByCollect(c, ch)
74
+//   }
75
+//
76
+// However, this will not work if the metrics collected change dynamically over
77
+// the lifetime of the Collector in a way that their combined set of descriptors
78
+// changes as well. The shortcut implementation will then violate the contract
79
+// of the Describe method. If a Collector sometimes collects no metrics at all
80
+// (for example vectors like CounterVec, GaugeVec, etc., which only collect
81
+// metrics after a metric with a fully specified label set has been accessed),
82
+// it might even get registered as an unchecked Collector (cf. the Register
83
+// method of the Registerer interface). Hence, only use this shortcut
84
+// implementation of Describe if you are certain to fulfill the contract.
85
+//
86
+// The Collector example demonstrates a use of DescribeByCollect.
87
+func DescribeByCollect(c Collector, descs chan<- *Desc) {
88
+	metrics := make(chan Metric)
89
+	go func() {
90
+		c.Collect(metrics)
91
+		close(metrics)
92
+	}()
93
+	for m := range metrics {
94
+		descs <- m.Desc()
95
+	}
96
+}
97
+
53 98
 // selfCollector implements Collector for a single Metric so that the Metric
54 99
 // collects itself. Add it as an anonymous field to a struct that implements
55 100
 // Metric, and call init with the Metric itself as an argument.
... ...
@@ -15,6 +15,10 @@ package prometheus
15 15
 
16 16
 import (
17 17
 	"errors"
18
+	"math"
19
+	"sync/atomic"
20
+
21
+	dto "github.com/prometheus/client_model/go"
18 22
 )
19 23
 
20 24
 // Counter is a Metric that represents a single numerical value that only ever
... ...
@@ -30,16 +34,8 @@ type Counter interface {
30 30
 	Metric
31 31
 	Collector
32 32
 
33
-	// Set is used to set the Counter to an arbitrary value. It is only used
34
-	// if you have to transfer a value from an external counter into this
35
-	// Prometheus metric. Do not use it for regular handling of a
36
-	// Prometheus counter (as it can be used to break the contract of
37
-	// monotonically increasing values).
38
-	//
39
-	// Deprecated: Use NewConstMetric to create a counter for an external
40
-	// value. A Counter should never be set.
41
-	Set(float64)
42
-	// Inc increments the counter by 1.
33
+	// Inc increments the counter by 1. Use Add to increment it by arbitrary
34
+	// non-negative values.
43 35
 	Inc()
44 36
 	// Add adds the given value to the counter. It panics if the value is <
45 37
 	// 0.
... ...
@@ -50,6 +46,14 @@ type Counter interface {
50 50
 type CounterOpts Opts
51 51
 
52 52
 // NewCounter creates a new Counter based on the provided CounterOpts.
53
+//
54
+// The returned implementation tracks the counter value in two separate
55
+// variables, a float64 and a uint64. The latter is used to track calls of the
56
+// Inc method and calls of the Add method with a value that can be represented
57
+// as a uint64. This allows atomic increments of the counter with optimal
58
+// performance. (It is common to have an Inc call in very hot execution paths.)
59
+// Both internal tracking values are added up in the Write method. This has to
60
+// be taken into account when it comes to precision and overflow behavior.
53 61
 func NewCounter(opts CounterOpts) Counter {
54 62
 	desc := NewDesc(
55 63
 		BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
... ...
@@ -57,20 +61,58 @@ func NewCounter(opts CounterOpts) Counter {
57 57
 		nil,
58 58
 		opts.ConstLabels,
59 59
 	)
60
-	result := &counter{value: value{desc: desc, valType: CounterValue, labelPairs: desc.constLabelPairs}}
60
+	result := &counter{desc: desc, labelPairs: desc.constLabelPairs}
61 61
 	result.init(result) // Init self-collection.
62 62
 	return result
63 63
 }
64 64
 
65 65
 type counter struct {
66
-	value
66
+	// valBits contains the bits of the represented float64 value, while
67
+	// valInt stores values that are exact integers. Both have to go first
68
+	// in the struct to guarantee alignment for atomic operations.
69
+	// http://golang.org/pkg/sync/atomic/#pkg-note-BUG
70
+	valBits uint64
71
+	valInt  uint64
72
+
73
+	selfCollector
74
+	desc *Desc
75
+
76
+	labelPairs []*dto.LabelPair
77
+}
78
+
79
+func (c *counter) Desc() *Desc {
80
+	return c.desc
67 81
 }
68 82
 
69 83
 func (c *counter) Add(v float64) {
70 84
 	if v < 0 {
71 85
 		panic(errors.New("counter cannot decrease in value"))
72 86
 	}
73
-	c.value.Add(v)
87
+	ival := uint64(v)
88
+	if float64(ival) == v {
89
+		atomic.AddUint64(&c.valInt, ival)
90
+		return
91
+	}
92
+
93
+	for {
94
+		oldBits := atomic.LoadUint64(&c.valBits)
95
+		newBits := math.Float64bits(math.Float64frombits(oldBits) + v)
96
+		if atomic.CompareAndSwapUint64(&c.valBits, oldBits, newBits) {
97
+			return
98
+		}
99
+	}
100
+}
101
+
102
+func (c *counter) Inc() {
103
+	atomic.AddUint64(&c.valInt, 1)
104
+}
105
+
106
+func (c *counter) Write(out *dto.Metric) error {
107
+	fval := math.Float64frombits(atomic.LoadUint64(&c.valBits))
108
+	ival := atomic.LoadUint64(&c.valInt)
109
+	val := fval + float64(ival)
110
+
111
+	return populateMetric(CounterValue, val, c.labelPairs, out)
74 112
 }
75 113
 
76 114
 // CounterVec is a Collector that bundles a set of Counters that all share the
... ...
@@ -78,16 +120,12 @@ func (c *counter) Add(v float64) {
78 78
 // if you want to count the same thing partitioned by various dimensions
79 79
 // (e.g. number of HTTP requests, partitioned by response code and
80 80
 // method). Create instances with NewCounterVec.
81
-//
82
-// CounterVec embeds MetricVec. See there for a full list of methods with
83
-// detailed documentation.
84 81
 type CounterVec struct {
85
-	*MetricVec
82
+	*metricVec
86 83
 }
87 84
 
88 85
 // NewCounterVec creates a new CounterVec based on the provided CounterOpts and
89
-// partitioned by the given label names. At least one label name must be
90
-// provided.
86
+// partitioned by the given label names.
91 87
 func NewCounterVec(opts CounterOpts, labelNames []string) *CounterVec {
92 88
 	desc := NewDesc(
93 89
 		BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
... ...
@@ -96,34 +134,62 @@ func NewCounterVec(opts CounterOpts, labelNames []string) *CounterVec {
96 96
 		opts.ConstLabels,
97 97
 	)
98 98
 	return &CounterVec{
99
-		MetricVec: newMetricVec(desc, func(lvs ...string) Metric {
100
-			result := &counter{value: value{
101
-				desc:       desc,
102
-				valType:    CounterValue,
103
-				labelPairs: makeLabelPairs(desc, lvs),
104
-			}}
99
+		metricVec: newMetricVec(desc, func(lvs ...string) Metric {
100
+			if len(lvs) != len(desc.variableLabels) {
101
+				panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, lvs))
102
+			}
103
+			result := &counter{desc: desc, labelPairs: makeLabelPairs(desc, lvs)}
105 104
 			result.init(result) // Init self-collection.
106 105
 			return result
107 106
 		}),
108 107
 	}
109 108
 }
110 109
 
111
-// GetMetricWithLabelValues replaces the method of the same name in
112
-// MetricVec. The difference is that this method returns a Counter and not a
113
-// Metric so that no type conversion is required.
114
-func (m *CounterVec) GetMetricWithLabelValues(lvs ...string) (Counter, error) {
115
-	metric, err := m.MetricVec.GetMetricWithLabelValues(lvs...)
110
+// GetMetricWithLabelValues returns the Counter for the given slice of label
111
+// values (same order as the VariableLabels in Desc). If that combination of
112
+// label values is accessed for the first time, a new Counter is created.
113
+//
114
+// It is possible to call this method without using the returned Counter to only
115
+// create the new Counter but leave it at its starting value 0. See also the
116
+// SummaryVec example.
117
+//
118
+// Keeping the Counter for later use is possible (and should be considered if
119
+// performance is critical), but keep in mind that Reset, DeleteLabelValues and
120
+// Delete can be used to delete the Counter from the CounterVec. In that case,
121
+// the Counter will still exist, but it will not be exported anymore, even if a
122
+// Counter with the same label values is created later.
123
+//
124
+// An error is returned if the number of label values is not the same as the
125
+// number of VariableLabels in Desc (minus any curried labels).
126
+//
127
+// Note that for more than one label value, this method is prone to mistakes
128
+// caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as
129
+// an alternative to avoid that type of mistake. For higher label numbers, the
130
+// latter has a much more readable (albeit more verbose) syntax, but it comes
131
+// with a performance overhead (for creating and processing the Labels map).
132
+// See also the GaugeVec example.
133
+func (v *CounterVec) GetMetricWithLabelValues(lvs ...string) (Counter, error) {
134
+	metric, err := v.metricVec.getMetricWithLabelValues(lvs...)
116 135
 	if metric != nil {
117 136
 		return metric.(Counter), err
118 137
 	}
119 138
 	return nil, err
120 139
 }
121 140
 
122
-// GetMetricWith replaces the method of the same name in MetricVec. The
123
-// difference is that this method returns a Counter and not a Metric so that no
124
-// type conversion is required.
125
-func (m *CounterVec) GetMetricWith(labels Labels) (Counter, error) {
126
-	metric, err := m.MetricVec.GetMetricWith(labels)
141
+// GetMetricWith returns the Counter for the given Labels map (the label names
142
+// must match those of the VariableLabels in Desc). If that label map is
143
+// accessed for the first time, a new Counter is created. Implications of
144
+// creating a Counter without using it and keeping the Counter for later use are
145
+// the same as for GetMetricWithLabelValues.
146
+//
147
+// An error is returned if the number and names of the Labels are inconsistent
148
+// with those of the VariableLabels in Desc (minus any curried labels).
149
+//
150
+// This method is used for the same purpose as
151
+// GetMetricWithLabelValues(...string). See there for pros and cons of the two
152
+// methods.
153
+func (v *CounterVec) GetMetricWith(labels Labels) (Counter, error) {
154
+	metric, err := v.metricVec.getMetricWith(labels)
127 155
 	if metric != nil {
128 156
 		return metric.(Counter), err
129 157
 	}
... ...
@@ -131,18 +197,57 @@ func (m *CounterVec) GetMetricWith(labels Labels) (Counter, error) {
131 131
 }
132 132
 
133 133
 // WithLabelValues works as GetMetricWithLabelValues, but panics where
134
-// GetMetricWithLabelValues would have returned an error. By not returning an
135
-// error, WithLabelValues allows shortcuts like
134
+// GetMetricWithLabelValues would have returned an error. Not returning an
135
+// error allows shortcuts like
136 136
 //     myVec.WithLabelValues("404", "GET").Add(42)
137
-func (m *CounterVec) WithLabelValues(lvs ...string) Counter {
138
-	return m.MetricVec.WithLabelValues(lvs...).(Counter)
137
+func (v *CounterVec) WithLabelValues(lvs ...string) Counter {
138
+	c, err := v.GetMetricWithLabelValues(lvs...)
139
+	if err != nil {
140
+		panic(err)
141
+	}
142
+	return c
139 143
 }
140 144
 
141 145
 // With works as GetMetricWith, but panics where GetMetricWithLabels would have
142
-// returned an error. By not returning an error, With allows shortcuts like
143
-//     myVec.With(Labels{"code": "404", "method": "GET"}).Add(42)
144
-func (m *CounterVec) With(labels Labels) Counter {
145
-	return m.MetricVec.With(labels).(Counter)
146
+// returned an error. Not returning an error allows shortcuts like
147
+//     myVec.With(prometheus.Labels{"code": "404", "method": "GET"}).Add(42)
148
+func (v *CounterVec) With(labels Labels) Counter {
149
+	c, err := v.GetMetricWith(labels)
150
+	if err != nil {
151
+		panic(err)
152
+	}
153
+	return c
154
+}
155
+
156
+// CurryWith returns a vector curried with the provided labels, i.e. the
157
+// returned vector has those labels pre-set for all labeled operations performed
158
+// on it. The cardinality of the curried vector is reduced accordingly. The
159
+// order of the remaining labels stays the same (just with the curried labels
160
+// taken out of the sequence – which is relevant for the
161
+// (GetMetric)WithLabelValues methods). It is possible to curry a curried
162
+// vector, but only with labels not yet used for currying before.
163
+//
164
+// The metrics contained in the CounterVec are shared between the curried and
165
+// uncurried vectors. They are just accessed differently. Curried and uncurried
166
+// vectors behave identically in terms of collection. Only one must be
167
+// registered with a given registry (usually the uncurried version). The Reset
168
+// method deletes all metrics, even if called on a curried vector.
169
+func (v *CounterVec) CurryWith(labels Labels) (*CounterVec, error) {
170
+	vec, err := v.curryWith(labels)
171
+	if vec != nil {
172
+		return &CounterVec{vec}, err
173
+	}
174
+	return nil, err
175
+}
176
+
177
+// MustCurryWith works as CurryWith but panics where CurryWith would have
178
+// returned an error.
179
+func (v *CounterVec) MustCurryWith(labels Labels) *CounterVec {
180
+	vec, err := v.CurryWith(labels)
181
+	if err != nil {
182
+		panic(err)
183
+	}
184
+	return vec
146 185
 }
147 186
 
148 187
 // CounterFunc is a Counter whose value is determined at collect time by calling a
... ...
@@ -16,33 +16,15 @@ package prometheus
16 16
 import (
17 17
 	"errors"
18 18
 	"fmt"
19
-	"regexp"
20 19
 	"sort"
21 20
 	"strings"
22 21
 
23 22
 	"github.com/golang/protobuf/proto"
23
+	"github.com/prometheus/common/model"
24 24
 
25 25
 	dto "github.com/prometheus/client_model/go"
26 26
 )
27 27
 
28
-var (
29
-	metricNameRE = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_:]*$`)
30
-	labelNameRE  = regexp.MustCompile("^[a-zA-Z_][a-zA-Z0-9_]*$")
31
-)
32
-
33
-// reservedLabelPrefix is a prefix which is not legal in user-supplied
34
-// label names.
35
-const reservedLabelPrefix = "__"
36
-
37
-// Labels represents a collection of label name -> value mappings. This type is
38
-// commonly used with the With(Labels) and GetMetricWith(Labels) methods of
39
-// metric vector Collectors, e.g.:
40
-//     myVec.With(Labels{"code": "404", "method": "GET"}).Add(42)
41
-//
42
-// The other use-case is the specification of constant label pairs in Opts or to
43
-// create a Desc.
44
-type Labels map[string]string
45
-
46 28
 // Desc is the descriptor used by every Prometheus Metric. It is essentially
47 29
 // the immutable meta-data of a Metric. The normal Metric implementations
48 30
 // included in this package manage their Desc under the hood. Users only have to
... ...
@@ -78,32 +60,27 @@ type Desc struct {
78 78
 	// Help string. Each Desc with the same fqName must have the same
79 79
 	// dimHash.
80 80
 	dimHash uint64
81
-	// err is an error that occured during construction. It is reported on
81
+	// err is an error that occurred during construction. It is reported on
82 82
 	// registration time.
83 83
 	err error
84 84
 }
85 85
 
86 86
 // NewDesc allocates and initializes a new Desc. Errors are recorded in the Desc
87 87
 // and will be reported on registration time. variableLabels and constLabels can
88
-// be nil if no such labels should be set. fqName and help must not be empty.
88
+// be nil if no such labels should be set. fqName must not be empty.
89 89
 //
90 90
 // variableLabels only contain the label names. Their label values are variable
91 91
 // and therefore not part of the Desc. (They are managed within the Metric.)
92 92
 //
93 93
 // For constLabels, the label values are constant. Therefore, they are fully
94
-// specified in the Desc. See the Opts documentation for the implications of
95
-// constant labels.
94
+// specified in the Desc. See the Collector example for a usage pattern.
96 95
 func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) *Desc {
97 96
 	d := &Desc{
98 97
 		fqName:         fqName,
99 98
 		help:           help,
100 99
 		variableLabels: variableLabels,
101 100
 	}
102
-	if help == "" {
103
-		d.err = errors.New("empty help string")
104
-		return d
105
-	}
106
-	if !metricNameRE.MatchString(fqName) {
101
+	if !model.IsValidMetricName(model.LabelValue(fqName)) {
107 102
 		d.err = fmt.Errorf("%q is not a valid metric name", fqName)
108 103
 		return d
109 104
 	}
... ...
@@ -116,7 +93,7 @@ func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) *
116 116
 	// First add only the const label names and sort them...
117 117
 	for labelName := range constLabels {
118 118
 		if !checkLabelName(labelName) {
119
-			d.err = fmt.Errorf("%q is not a valid label name", labelName)
119
+			d.err = fmt.Errorf("%q is not a valid label name for metric %q", labelName, fqName)
120 120
 			return d
121 121
 		}
122 122
 		labelNames = append(labelNames, labelName)
... ...
@@ -127,12 +104,18 @@ func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) *
127 127
 	for _, labelName := range labelNames {
128 128
 		labelValues = append(labelValues, constLabels[labelName])
129 129
 	}
130
+	// Validate the const label values. They can't have a wrong cardinality, so
131
+	// use in len(labelValues) as expectedNumberOfValues.
132
+	if err := validateLabelValues(labelValues, len(labelValues)); err != nil {
133
+		d.err = err
134
+		return d
135
+	}
130 136
 	// Now add the variable label names, but prefix them with something that
131 137
 	// cannot be in a regular label name. That prevents matching the label
132 138
 	// dimension with a different mix between preset and variable labels.
133 139
 	for _, labelName := range variableLabels {
134 140
 		if !checkLabelName(labelName) {
135
-			d.err = fmt.Errorf("%q is not a valid label name", labelName)
141
+			d.err = fmt.Errorf("%q is not a valid label name for metric %q", labelName, fqName)
136 142
 			return d
137 143
 		}
138 144
 		labelNames = append(labelNames, "$"+labelName)
... ...
@@ -142,6 +125,7 @@ func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) *
142 142
 		d.err = errors.New("duplicate label names")
143 143
 		return d
144 144
 	}
145
+
145 146
 	vh := hashNew()
146 147
 	for _, val := range labelValues {
147 148
 		vh = hashAdd(vh, val)
... ...
@@ -168,7 +152,7 @@ func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) *
168 168
 			Value: proto.String(v),
169 169
 		})
170 170
 	}
171
-	sort.Sort(LabelPairSorter(d.constLabelPairs))
171
+	sort.Sort(labelPairSorter(d.constLabelPairs))
172 172
 	return d
173 173
 }
174 174
 
... ...
@@ -198,8 +182,3 @@ func (d *Desc) String() string {
198 198
 		d.variableLabels,
199 199
 	)
200 200
 }
201
-
202
-func checkLabelName(l string) bool {
203
-	return labelNameRE.MatchString(l) &&
204
-		!strings.HasPrefix(l, reservedLabelPrefix)
205
-}
... ...
@@ -11,13 +11,15 @@
11 11
 // See the License for the specific language governing permissions and
12 12
 // limitations under the License.
13 13
 
14
-// Package prometheus provides metrics primitives to instrument code for
15
-// monitoring. It also offers a registry for metrics. Sub-packages allow to
16
-// expose the registered metrics via HTTP (package promhttp) or push them to a
17
-// Pushgateway (package push).
14
+// Package prometheus is the core instrumentation package. It provides metrics
15
+// primitives to instrument code for monitoring. It also offers a registry for
16
+// metrics. Sub-packages allow to expose the registered metrics via HTTP
17
+// (package promhttp) or push them to a Pushgateway (package push). There is
18
+// also a sub-package promauto, which provides metrics constructors with
19
+// automatic registration.
18 20
 //
19 21
 // All exported functions and methods are safe to be used concurrently unless
20
-//specified otherwise.
22
+// specified otherwise.
21 23
 //
22 24
 // A Basic Example
23 25
 //
... ...
@@ -26,6 +28,7 @@
26 26
 //    package main
27 27
 //
28 28
 //    import (
29
+//    	"log"
29 30
 //    	"net/http"
30 31
 //
31 32
 //    	"github.com/prometheus/client_golang/prometheus"
... ...
@@ -59,7 +62,7 @@
59 59
 //    	// The Handler function provides a default handler to expose metrics
60 60
 //    	// via an HTTP server. "/metrics" is the usual endpoint for that.
61 61
 //    	http.Handle("/metrics", promhttp.Handler())
62
-//    	http.ListenAndServe(":8080", nil)
62
+//    	log.Fatal(http.ListenAndServe(":8080", nil))
63 63
 //    }
64 64
 //
65 65
 //
... ...
@@ -69,9 +72,12 @@
69 69
 // Metrics
70 70
 //
71 71
 // The number of exported identifiers in this package might appear a bit
72
-// overwhelming. Hovever, in addition to the basic plumbing shown in the example
72
+// overwhelming. However, in addition to the basic plumbing shown in the example
73 73
 // above, you only need to understand the different metric types and their
74
-// vector versions for basic usage.
74
+// vector versions for basic usage. Furthermore, if you are not concerned with
75
+// fine-grained control of when and how to register metrics with the registry,
76
+// have a look at the promauto package, which will effectively allow you to
77
+// ignore registration altogether in simple cases.
75 78
 //
76 79
 // Above, you have already touched the Counter and the Gauge. There are two more
77 80
 // advanced metric types: the Summary and Histogram. A more thorough description
... ...
@@ -95,8 +101,8 @@
95 95
 // SummaryVec, HistogramVec, and UntypedVec are not.
96 96
 //
97 97
 // To create instances of Metrics and their vector versions, you need a suitable
98
-// …Opts struct, i.e. GaugeOpts, CounterOpts, SummaryOpts,
99
-// HistogramOpts, or UntypedOpts.
98
+// …Opts struct, i.e. GaugeOpts, CounterOpts, SummaryOpts, HistogramOpts, or
99
+// UntypedOpts.
100 100
 //
101 101
 // Custom Collectors and constant Metrics
102 102
 //
... ...
@@ -114,8 +120,18 @@
114 114
 // Metric instances “on the fly” using NewConstMetric, NewConstHistogram, and
115 115
 // NewConstSummary (and their respective Must… versions). That will happen in
116 116
 // the Collect method. The Describe method has to return separate Desc
117
-// instances, representative of the “throw-away” metrics to be created
118
-// later. NewDesc comes in handy to create those Desc instances.
117
+// instances, representative of the “throw-away” metrics to be created later.
118
+// NewDesc comes in handy to create those Desc instances. Alternatively, you
119
+// could return no Desc at all, which will mark the Collector “unchecked”.  No
120
+// checks are performed at registration time, but metric consistency will still
121
+// be ensured at scrape time, i.e. any inconsistencies will lead to scrape
122
+// errors. Thus, with unchecked Collectors, the responsibility to not collect
123
+// metrics that lead to inconsistencies in the total scrape result lies with the
124
+// implementer of the Collector. While this is not a desirable state, it is
125
+// sometimes necessary. The typical use case is a situation where the exact
126
+// metrics to be returned by a Collector cannot be predicted at registration
127
+// time, but the implementer has sufficient knowledge of the whole system to
128
+// guarantee metric consistency.
119 129
 //
120 130
 // The Collector example illustrates the use case. You can also look at the
121 131
 // source code of the processCollector (mirroring process metrics), the
... ...
@@ -129,34 +145,34 @@
129 129
 // Advanced Uses of the Registry
130 130
 //
131 131
 // While MustRegister is the by far most common way of registering a Collector,
132
-// sometimes you might want to handle the errors the registration might
133
-// cause. As suggested by the name, MustRegister panics if an error occurs. With
134
-// the Register function, the error is returned and can be handled.
132
+// sometimes you might want to handle the errors the registration might cause.
133
+// As suggested by the name, MustRegister panics if an error occurs. With the
134
+// Register function, the error is returned and can be handled.
135 135
 //
136 136
 // An error is returned if the registered Collector is incompatible or
137 137
 // inconsistent with already registered metrics. The registry aims for
138
-// consistency of the collected metrics according to the Prometheus data
139
-// model. Inconsistencies are ideally detected at registration time, not at
140
-// collect time. The former will usually be detected at start-up time of a
141
-// program, while the latter will only happen at scrape time, possibly not even
142
-// on the first scrape if the inconsistency only becomes relevant later. That is
143
-// the main reason why a Collector and a Metric have to describe themselves to
144
-// the registry.
138
+// consistency of the collected metrics according to the Prometheus data model.
139
+// Inconsistencies are ideally detected at registration time, not at collect
140
+// time. The former will usually be detected at start-up time of a program,
141
+// while the latter will only happen at scrape time, possibly not even on the
142
+// first scrape if the inconsistency only becomes relevant later. That is the
143
+// main reason why a Collector and a Metric have to describe themselves to the
144
+// registry.
145 145
 //
146 146
 // So far, everything we did operated on the so-called default registry, as it
147
-// can be found in the global DefaultRegistry variable. With NewRegistry, you
147
+// can be found in the global DefaultRegisterer variable. With NewRegistry, you
148 148
 // can create a custom registry, or you can even implement the Registerer or
149
-// Gatherer interfaces yourself. The methods Register and Unregister work in
150
-// the same way on a custom registry as the global functions Register and
151
-// Unregister on the default registry.
152
-//
153
-// There are a number of uses for custom registries: You can use registries
154
-// with special properties, see NewPedanticRegistry. You can avoid global state,
155
-// as it is imposed by the DefaultRegistry. You can use multiple registries at
156
-// the same time to expose different metrics in different ways. You can use
149
+// Gatherer interfaces yourself. The methods Register and Unregister work in the
150
+// same way on a custom registry as the global functions Register and Unregister
151
+// on the default registry.
152
+//
153
+// There are a number of uses for custom registries: You can use registries with
154
+// special properties, see NewPedanticRegistry. You can avoid global state, as
155
+// it is imposed by the DefaultRegisterer. You can use multiple registries at
156
+// the same time to expose different metrics in different ways.  You can use
157 157
 // separate registries for testing purposes.
158 158
 //
159
-// Also note that the DefaultRegistry comes registered with a Collector for Go
159
+// Also note that the DefaultRegisterer comes registered with a Collector for Go
160 160
 // runtime metrics (via NewGoCollector) and a Collector for process metrics (via
161 161
 // NewProcessCollector). With a custom registry, you are in control and decide
162 162
 // yourself about the Collectors to register.
... ...
@@ -166,16 +182,20 @@
166 166
 // The Registry implements the Gatherer interface. The caller of the Gather
167 167
 // method can then expose the gathered metrics in some way. Usually, the metrics
168 168
 // are served via HTTP on the /metrics endpoint. That's happening in the example
169
-// above. The tools to expose metrics via HTTP are in the promhttp
170
-// sub-package. (The top-level functions in the prometheus package are
171
-// deprecated.)
169
+// above. The tools to expose metrics via HTTP are in the promhttp sub-package.
170
+// (The top-level functions in the prometheus package are deprecated.)
172 171
 //
173 172
 // Pushing to the Pushgateway
174 173
 //
175 174
 // Function for pushing to the Pushgateway can be found in the push sub-package.
176 175
 //
176
+// Graphite Bridge
177
+//
178
+// Functions and examples to push metrics from a Gatherer to Graphite can be
179
+// found in the graphite sub-package.
180
+//
177 181
 // Other Means of Exposition
178 182
 //
179
-// More ways of exposing metrics can easily be added. Sending metrics to
180
-// Graphite would be an example that will soon be implemented.
183
+// More ways of exposing metrics can easily be added by following the approaches
184
+// of the existing implementations.
181 185
 package prometheus
... ...
@@ -1,3 +1,16 @@
1
+// Copyright 2018 The Prometheus Authors
2
+// Licensed under the Apache License, Version 2.0 (the "License");
3
+// you may not use this file except in compliance with the License.
4
+// You may obtain a copy of the License at
5
+//
6
+// http://www.apache.org/licenses/LICENSE-2.0
7
+//
8
+// Unless required by applicable law or agreed to in writing, software
9
+// distributed under the License is distributed on an "AS IS" BASIS,
10
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+// See the License for the specific language governing permissions and
12
+// limitations under the License.
13
+
1 14
 package prometheus
2 15
 
3 16
 // Inline and byte-free variant of hash/fnv's fnv64a.
... ...
@@ -13,6 +13,14 @@
13 13
 
14 14
 package prometheus
15 15
 
16
+import (
17
+	"math"
18
+	"sync/atomic"
19
+	"time"
20
+
21
+	dto "github.com/prometheus/client_model/go"
22
+)
23
+
16 24
 // Gauge is a Metric that represents a single numerical value that can
17 25
 // arbitrarily go up and down.
18 26
 //
... ...
@@ -27,29 +35,95 @@ type Gauge interface {
27 27
 
28 28
 	// Set sets the Gauge to an arbitrary value.
29 29
 	Set(float64)
30
-	// Inc increments the Gauge by 1.
30
+	// Inc increments the Gauge by 1. Use Add to increment it by arbitrary
31
+	// values.
31 32
 	Inc()
32
-	// Dec decrements the Gauge by 1.
33
+	// Dec decrements the Gauge by 1. Use Sub to decrement it by arbitrary
34
+	// values.
33 35
 	Dec()
34
-	// Add adds the given value to the Gauge. (The value can be
35
-	// negative, resulting in a decrease of the Gauge.)
36
+	// Add adds the given value to the Gauge. (The value can be negative,
37
+	// resulting in a decrease of the Gauge.)
36 38
 	Add(float64)
37 39
 	// Sub subtracts the given value from the Gauge. (The value can be
38 40
 	// negative, resulting in an increase of the Gauge.)
39 41
 	Sub(float64)
42
+
43
+	// SetToCurrentTime sets the Gauge to the current Unix time in seconds.
44
+	SetToCurrentTime()
40 45
 }
41 46
 
42 47
 // GaugeOpts is an alias for Opts. See there for doc comments.
43 48
 type GaugeOpts Opts
44 49
 
45 50
 // NewGauge creates a new Gauge based on the provided GaugeOpts.
51
+//
52
+// The returned implementation is optimized for a fast Set method. If you have a
53
+// choice for managing the value of a Gauge via Set vs. Inc/Dec/Add/Sub, pick
54
+// the former. For example, the Inc method of the returned Gauge is slower than
55
+// the Inc method of a Counter returned by NewCounter. This matches the typical
56
+// scenarios for Gauges and Counters, where the former tends to be Set-heavy and
57
+// the latter Inc-heavy.
46 58
 func NewGauge(opts GaugeOpts) Gauge {
47
-	return newValue(NewDesc(
59
+	desc := NewDesc(
48 60
 		BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
49 61
 		opts.Help,
50 62
 		nil,
51 63
 		opts.ConstLabels,
52
-	), GaugeValue, 0)
64
+	)
65
+	result := &gauge{desc: desc, labelPairs: desc.constLabelPairs}
66
+	result.init(result) // Init self-collection.
67
+	return result
68
+}
69
+
70
+type gauge struct {
71
+	// valBits contains the bits of the represented float64 value. It has
72
+	// to go first in the struct to guarantee alignment for atomic
73
+	// operations.  http://golang.org/pkg/sync/atomic/#pkg-note-BUG
74
+	valBits uint64
75
+
76
+	selfCollector
77
+
78
+	desc       *Desc
79
+	labelPairs []*dto.LabelPair
80
+}
81
+
82
+func (g *gauge) Desc() *Desc {
83
+	return g.desc
84
+}
85
+
86
+func (g *gauge) Set(val float64) {
87
+	atomic.StoreUint64(&g.valBits, math.Float64bits(val))
88
+}
89
+
90
+func (g *gauge) SetToCurrentTime() {
91
+	g.Set(float64(time.Now().UnixNano()) / 1e9)
92
+}
93
+
94
+func (g *gauge) Inc() {
95
+	g.Add(1)
96
+}
97
+
98
+func (g *gauge) Dec() {
99
+	g.Add(-1)
100
+}
101
+
102
+func (g *gauge) Add(val float64) {
103
+	for {
104
+		oldBits := atomic.LoadUint64(&g.valBits)
105
+		newBits := math.Float64bits(math.Float64frombits(oldBits) + val)
106
+		if atomic.CompareAndSwapUint64(&g.valBits, oldBits, newBits) {
107
+			return
108
+		}
109
+	}
110
+}
111
+
112
+func (g *gauge) Sub(val float64) {
113
+	g.Add(val * -1)
114
+}
115
+
116
+func (g *gauge) Write(out *dto.Metric) error {
117
+	val := math.Float64frombits(atomic.LoadUint64(&g.valBits))
118
+	return populateMetric(GaugeValue, val, g.labelPairs, out)
53 119
 }
54 120
 
55 121
 // GaugeVec is a Collector that bundles a set of Gauges that all share the same
... ...
@@ -58,12 +132,11 @@ func NewGauge(opts GaugeOpts) Gauge {
58 58
 // (e.g. number of operations queued, partitioned by user and operation
59 59
 // type). Create instances with NewGaugeVec.
60 60
 type GaugeVec struct {
61
-	*MetricVec
61
+	*metricVec
62 62
 }
63 63
 
64 64
 // NewGaugeVec creates a new GaugeVec based on the provided GaugeOpts and
65
-// partitioned by the given label names. At least one label name must be
66
-// provided.
65
+// partitioned by the given label names.
67 66
 func NewGaugeVec(opts GaugeOpts, labelNames []string) *GaugeVec {
68 67
 	desc := NewDesc(
69 68
 		BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
... ...
@@ -72,28 +145,62 @@ func NewGaugeVec(opts GaugeOpts, labelNames []string) *GaugeVec {
72 72
 		opts.ConstLabels,
73 73
 	)
74 74
 	return &GaugeVec{
75
-		MetricVec: newMetricVec(desc, func(lvs ...string) Metric {
76
-			return newValue(desc, GaugeValue, 0, lvs...)
75
+		metricVec: newMetricVec(desc, func(lvs ...string) Metric {
76
+			if len(lvs) != len(desc.variableLabels) {
77
+				panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, lvs))
78
+			}
79
+			result := &gauge{desc: desc, labelPairs: makeLabelPairs(desc, lvs)}
80
+			result.init(result) // Init self-collection.
81
+			return result
77 82
 		}),
78 83
 	}
79 84
 }
80 85
 
81
-// GetMetricWithLabelValues replaces the method of the same name in
82
-// MetricVec. The difference is that this method returns a Gauge and not a
83
-// Metric so that no type conversion is required.
84
-func (m *GaugeVec) GetMetricWithLabelValues(lvs ...string) (Gauge, error) {
85
-	metric, err := m.MetricVec.GetMetricWithLabelValues(lvs...)
86
+// GetMetricWithLabelValues returns the Gauge for the given slice of label
87
+// values (same order as the VariableLabels in Desc). If that combination of
88
+// label values is accessed for the first time, a new Gauge is created.
89
+//
90
+// It is possible to call this method without using the returned Gauge to only
91
+// create the new Gauge but leave it at its starting value 0. See also the
92
+// SummaryVec example.
93
+//
94
+// Keeping the Gauge for later use is possible (and should be considered if
95
+// performance is critical), but keep in mind that Reset, DeleteLabelValues and
96
+// Delete can be used to delete the Gauge from the GaugeVec. In that case, the
97
+// Gauge will still exist, but it will not be exported anymore, even if a
98
+// Gauge with the same label values is created later. See also the CounterVec
99
+// example.
100
+//
101
+// An error is returned if the number of label values is not the same as the
102
+// number of VariableLabels in Desc (minus any curried labels).
103
+//
104
+// Note that for more than one label value, this method is prone to mistakes
105
+// caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as
106
+// an alternative to avoid that type of mistake. For higher label numbers, the
107
+// latter has a much more readable (albeit more verbose) syntax, but it comes
108
+// with a performance overhead (for creating and processing the Labels map).
109
+func (v *GaugeVec) GetMetricWithLabelValues(lvs ...string) (Gauge, error) {
110
+	metric, err := v.metricVec.getMetricWithLabelValues(lvs...)
86 111
 	if metric != nil {
87 112
 		return metric.(Gauge), err
88 113
 	}
89 114
 	return nil, err
90 115
 }
91 116
 
92
-// GetMetricWith replaces the method of the same name in MetricVec. The
93
-// difference is that this method returns a Gauge and not a Metric so that no
94
-// type conversion is required.
95
-func (m *GaugeVec) GetMetricWith(labels Labels) (Gauge, error) {
96
-	metric, err := m.MetricVec.GetMetricWith(labels)
117
+// GetMetricWith returns the Gauge for the given Labels map (the label names
118
+// must match those of the VariableLabels in Desc). If that label map is
119
+// accessed for the first time, a new Gauge is created. Implications of
120
+// creating a Gauge without using it and keeping the Gauge for later use are
121
+// the same as for GetMetricWithLabelValues.
122
+//
123
+// An error is returned if the number and names of the Labels are inconsistent
124
+// with those of the VariableLabels in Desc (minus any curried labels).
125
+//
126
+// This method is used for the same purpose as
127
+// GetMetricWithLabelValues(...string). See there for pros and cons of the two
128
+// methods.
129
+func (v *GaugeVec) GetMetricWith(labels Labels) (Gauge, error) {
130
+	metric, err := v.metricVec.getMetricWith(labels)
97 131
 	if metric != nil {
98 132
 		return metric.(Gauge), err
99 133
 	}
... ...
@@ -101,18 +208,57 @@ func (m *GaugeVec) GetMetricWith(labels Labels) (Gauge, error) {
101 101
 }
102 102
 
103 103
 // WithLabelValues works as GetMetricWithLabelValues, but panics where
104
-// GetMetricWithLabelValues would have returned an error. By not returning an
105
-// error, WithLabelValues allows shortcuts like
104
+// GetMetricWithLabelValues would have returned an error. Not returning an
105
+// error allows shortcuts like
106 106
 //     myVec.WithLabelValues("404", "GET").Add(42)
107
-func (m *GaugeVec) WithLabelValues(lvs ...string) Gauge {
108
-	return m.MetricVec.WithLabelValues(lvs...).(Gauge)
107
+func (v *GaugeVec) WithLabelValues(lvs ...string) Gauge {
108
+	g, err := v.GetMetricWithLabelValues(lvs...)
109
+	if err != nil {
110
+		panic(err)
111
+	}
112
+	return g
109 113
 }
110 114
 
111 115
 // With works as GetMetricWith, but panics where GetMetricWithLabels would have
112
-// returned an error. By not returning an error, With allows shortcuts like
113
-//     myVec.With(Labels{"code": "404", "method": "GET"}).Add(42)
114
-func (m *GaugeVec) With(labels Labels) Gauge {
115
-	return m.MetricVec.With(labels).(Gauge)
116
+// returned an error. Not returning an error allows shortcuts like
117
+//     myVec.With(prometheus.Labels{"code": "404", "method": "GET"}).Add(42)
118
+func (v *GaugeVec) With(labels Labels) Gauge {
119
+	g, err := v.GetMetricWith(labels)
120
+	if err != nil {
121
+		panic(err)
122
+	}
123
+	return g
124
+}
125
+
126
+// CurryWith returns a vector curried with the provided labels, i.e. the
127
+// returned vector has those labels pre-set for all labeled operations performed
128
+// on it. The cardinality of the curried vector is reduced accordingly. The
129
+// order of the remaining labels stays the same (just with the curried labels
130
+// taken out of the sequence – which is relevant for the
131
+// (GetMetric)WithLabelValues methods). It is possible to curry a curried
132
+// vector, but only with labels not yet used for currying before.
133
+//
134
+// The metrics contained in the GaugeVec are shared between the curried and
135
+// uncurried vectors. They are just accessed differently. Curried and uncurried
136
+// vectors behave identically in terms of collection. Only one must be
137
+// registered with a given registry (usually the uncurried version). The Reset
138
+// method deletes all metrics, even if called on a curried vector.
139
+func (v *GaugeVec) CurryWith(labels Labels) (*GaugeVec, error) {
140
+	vec, err := v.curryWith(labels)
141
+	if vec != nil {
142
+		return &GaugeVec{vec}, err
143
+	}
144
+	return nil, err
145
+}
146
+
147
+// MustCurryWith works as CurryWith but panics where CurryWith would have
148
+// returned an error.
149
+func (v *GaugeVec) MustCurryWith(labels Labels) *GaugeVec {
150
+	vec, err := v.CurryWith(labels)
151
+	if err != nil {
152
+		panic(err)
153
+	}
154
+	return vec
116 155
 }
117 156
 
118 157
 // GaugeFunc is a Gauge whose value is determined at collect time by calling a
... ...
@@ -1,34 +1,89 @@
1
+// Copyright 2018 The Prometheus Authors
2
+// Licensed under the Apache License, Version 2.0 (the "License");
3
+// you may not use this file except in compliance with the License.
4
+// You may obtain a copy of the License at
5
+//
6
+// http://www.apache.org/licenses/LICENSE-2.0
7
+//
8
+// Unless required by applicable law or agreed to in writing, software
9
+// distributed under the License is distributed on an "AS IS" BASIS,
10
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+// See the License for the specific language governing permissions and
12
+// limitations under the License.
13
+
1 14
 package prometheus
2 15
 
3 16
 import (
4
-	"fmt"
5 17
 	"runtime"
6 18
 	"runtime/debug"
19
+	"sync"
7 20
 	"time"
8 21
 )
9 22
 
10 23
 type goCollector struct {
11
-	goroutines Gauge
12
-	gcDesc     *Desc
24
+	goroutinesDesc *Desc
25
+	threadsDesc    *Desc
26
+	gcDesc         *Desc
27
+	goInfoDesc     *Desc
13 28
 
14
-	// metrics to describe and collect
15
-	metrics memStatsMetrics
29
+	// ms... are memstats related.
30
+	msLast          *runtime.MemStats // Previously collected memstats.
31
+	msLastTimestamp time.Time
32
+	msMtx           sync.Mutex // Protects msLast and msLastTimestamp.
33
+	msMetrics       memStatsMetrics
34
+	msRead          func(*runtime.MemStats) // For mocking in tests.
35
+	msMaxWait       time.Duration           // Wait time for fresh memstats.
36
+	msMaxAge        time.Duration           // Maximum allowed age of old memstats.
16 37
 }
17 38
 
18
-// NewGoCollector returns a collector which exports metrics about the current
19
-// go process.
39
+// NewGoCollector returns a collector that exports metrics about the current Go
40
+// process. This includes memory stats. To collect those, runtime.ReadMemStats
41
+// is called. This requires to “stop the world”, which usually only happens for
42
+// garbage collection (GC). Take the following implications into account when
43
+// deciding whether to use the Go collector:
44
+//
45
+// 1. The performance impact of stopping the world is the more relevant the more
46
+// frequently metrics are collected. However, with Go1.9 or later the
47
+// stop-the-world time per metrics collection is very short (~25µs) so that the
48
+// performance impact will only matter in rare cases. However, with older Go
49
+// versions, the stop-the-world duration depends on the heap size and can be
50
+// quite significant (~1.7 ms/GiB as per
51
+// https://go-review.googlesource.com/c/go/+/34937).
52
+//
53
+// 2. During an ongoing GC, nothing else can stop the world. Therefore, if the
54
+// metrics collection happens to coincide with GC, it will only complete after
55
+// GC has finished. Usually, GC is fast enough to not cause problems. However,
56
+// with a very large heap, GC might take multiple seconds, which is enough to
57
+// cause scrape timeouts in common setups. To avoid this problem, the Go
58
+// collector will use the memstats from a previous collection if
59
+// runtime.ReadMemStats takes more than 1s. However, if there are no previously
60
+// collected memstats, or their collection is more than 5m ago, the collection
61
+// will block until runtime.ReadMemStats succeeds. (The problem might be solved
62
+// in Go1.13, see https://github.com/golang/go/issues/19812 for the related Go
63
+// issue.)
20 64
 func NewGoCollector() Collector {
21 65
 	return &goCollector{
22
-		goroutines: NewGauge(GaugeOpts{
23
-			Namespace: "go",
24
-			Name:      "goroutines",
25
-			Help:      "Number of goroutines that currently exist.",
26
-		}),
66
+		goroutinesDesc: NewDesc(
67
+			"go_goroutines",
68
+			"Number of goroutines that currently exist.",
69
+			nil, nil),
70
+		threadsDesc: NewDesc(
71
+			"go_threads",
72
+			"Number of OS threads created.",
73
+			nil, nil),
27 74
 		gcDesc: NewDesc(
28 75
 			"go_gc_duration_seconds",
29 76
 			"A summary of the GC invocation durations.",
30 77
 			nil, nil),
31
-		metrics: memStatsMetrics{
78
+		goInfoDesc: NewDesc(
79
+			"go_info",
80
+			"Information about the Go environment.",
81
+			nil, Labels{"version": runtime.Version()}),
82
+		msLast:    &runtime.MemStats{},
83
+		msRead:    runtime.ReadMemStats,
84
+		msMaxWait: time.Second,
85
+		msMaxAge:  5 * time.Minute,
86
+		msMetrics: memStatsMetrics{
32 87
 			{
33 88
 				desc: NewDesc(
34 89
 					memstatNamespace("alloc_bytes"),
... ...
@@ -48,7 +103,7 @@ func NewGoCollector() Collector {
48 48
 			}, {
49 49
 				desc: NewDesc(
50 50
 					memstatNamespace("sys_bytes"),
51
-					"Number of bytes obtained by system. Sum of all system allocations.",
51
+					"Number of bytes obtained from system.",
52 52
 					nil, nil,
53 53
 				),
54 54
 				eval:    func(ms *runtime.MemStats) float64 { return float64(ms.Sys) },
... ...
@@ -111,12 +166,12 @@ func NewGoCollector() Collector {
111 111
 				valType: GaugeValue,
112 112
 			}, {
113 113
 				desc: NewDesc(
114
-					memstatNamespace("heap_released_bytes_total"),
115
-					"Total number of heap bytes released to OS.",
114
+					memstatNamespace("heap_released_bytes"),
115
+					"Number of heap bytes released to OS.",
116 116
 					nil, nil,
117 117
 				),
118 118
 				eval:    func(ms *runtime.MemStats) float64 { return float64(ms.HeapReleased) },
119
-				valType: CounterValue,
119
+				valType: GaugeValue,
120 120
 			}, {
121 121
 				desc: NewDesc(
122 122
 					memstatNamespace("heap_objects"),
... ...
@@ -213,29 +268,53 @@ func NewGoCollector() Collector {
213 213
 				),
214 214
 				eval:    func(ms *runtime.MemStats) float64 { return float64(ms.LastGC) / 1e9 },
215 215
 				valType: GaugeValue,
216
+			}, {
217
+				desc: NewDesc(
218
+					memstatNamespace("gc_cpu_fraction"),
219
+					"The fraction of this program's available CPU time used by the GC since the program started.",
220
+					nil, nil,
221
+				),
222
+				eval:    func(ms *runtime.MemStats) float64 { return ms.GCCPUFraction },
223
+				valType: GaugeValue,
216 224
 			},
217 225
 		},
218 226
 	}
219 227
 }
220 228
 
221 229
 func memstatNamespace(s string) string {
222
-	return fmt.Sprintf("go_memstats_%s", s)
230
+	return "go_memstats_" + s
223 231
 }
224 232
 
225 233
 // Describe returns all descriptions of the collector.
226 234
 func (c *goCollector) Describe(ch chan<- *Desc) {
227
-	ch <- c.goroutines.Desc()
235
+	ch <- c.goroutinesDesc
236
+	ch <- c.threadsDesc
228 237
 	ch <- c.gcDesc
229
-
230
-	for _, i := range c.metrics {
238
+	ch <- c.goInfoDesc
239
+	for _, i := range c.msMetrics {
231 240
 		ch <- i.desc
232 241
 	}
233 242
 }
234 243
 
235 244
 // Collect returns the current state of all metrics of the collector.
236 245
 func (c *goCollector) Collect(ch chan<- Metric) {
237
-	c.goroutines.Set(float64(runtime.NumGoroutine()))
238
-	ch <- c.goroutines
246
+	var (
247
+		ms   = &runtime.MemStats{}
248
+		done = make(chan struct{})
249
+	)
250
+	// Start reading memstats first as it might take a while.
251
+	go func() {
252
+		c.msRead(ms)
253
+		c.msMtx.Lock()
254
+		c.msLast = ms
255
+		c.msLastTimestamp = time.Now()
256
+		c.msMtx.Unlock()
257
+		close(done)
258
+	}()
259
+
260
+	ch <- MustNewConstMetric(c.goroutinesDesc, GaugeValue, float64(runtime.NumGoroutine()))
261
+	n, _ := runtime.ThreadCreateProfile(nil)
262
+	ch <- MustNewConstMetric(c.threadsDesc, GaugeValue, float64(n))
239 263
 
240 264
 	var stats debug.GCStats
241 265
 	stats.PauseQuantiles = make([]time.Duration, 5)
... ...
@@ -246,11 +325,35 @@ func (c *goCollector) Collect(ch chan<- Metric) {
246 246
 		quantiles[float64(idx+1)/float64(len(stats.PauseQuantiles)-1)] = pq.Seconds()
247 247
 	}
248 248
 	quantiles[0.0] = stats.PauseQuantiles[0].Seconds()
249
-	ch <- MustNewConstSummary(c.gcDesc, uint64(stats.NumGC), float64(stats.PauseTotal.Seconds()), quantiles)
249
+	ch <- MustNewConstSummary(c.gcDesc, uint64(stats.NumGC), stats.PauseTotal.Seconds(), quantiles)
250
+
251
+	ch <- MustNewConstMetric(c.goInfoDesc, GaugeValue, 1)
250 252
 
251
-	ms := &runtime.MemStats{}
252
-	runtime.ReadMemStats(ms)
253
-	for _, i := range c.metrics {
253
+	timer := time.NewTimer(c.msMaxWait)
254
+	select {
255
+	case <-done: // Our own ReadMemStats succeeded in time. Use it.
256
+		timer.Stop() // Important for high collection frequencies to not pile up timers.
257
+		c.msCollect(ch, ms)
258
+		return
259
+	case <-timer.C: // Time out, use last memstats if possible. Continue below.
260
+	}
261
+	c.msMtx.Lock()
262
+	if time.Since(c.msLastTimestamp) < c.msMaxAge {
263
+		// Last memstats are recent enough. Collect from them under the lock.
264
+		c.msCollect(ch, c.msLast)
265
+		c.msMtx.Unlock()
266
+		return
267
+	}
268
+	// If we are here, the last memstats are too old or don't exist. We have
269
+	// to wait until our own ReadMemStats finally completes. For that to
270
+	// happen, we have to release the lock.
271
+	c.msMtx.Unlock()
272
+	<-done
273
+	c.msCollect(ch, ms)
274
+}
275
+
276
+func (c *goCollector) msCollect(ch chan<- Metric, ms *runtime.MemStats) {
277
+	for _, i := range c.msMetrics {
254 278
 		ch <- MustNewConstMetric(i.desc, i.valType, i.eval(ms))
255 279
 	}
256 280
 }
... ...
@@ -261,3 +364,33 @@ type memStatsMetrics []struct {
261 261
 	eval    func(*runtime.MemStats) float64
262 262
 	valType ValueType
263 263
 }
264
+
265
+// NewBuildInfoCollector returns a collector collecting a single metric
266
+// "go_build_info" with the constant value 1 and three labels "path", "version",
267
+// and "checksum". Their label values contain the main module path, version, and
268
+// checksum, respectively. The labels will only have meaningful values if the
269
+// binary is built with Go module support and from source code retrieved from
270
+// the source repository (rather than the local file system). This is usually
271
+// accomplished by building from outside of GOPATH, specifying the full address
272
+// of the main package, e.g. "GO111MODULE=on go run
273
+// github.com/prometheus/client_golang/examples/random". If built without Go
274
+// module support, all label values will be "unknown". If built with Go module
275
+// support but using the source code from the local file system, the "path" will
276
+// be set appropriately, but "checksum" will be empty and "version" will be
277
+// "(devel)".
278
+//
279
+// This collector uses only the build information for the main module. See
280
+// https://github.com/povilasv/prommod for an example of a collector for the
281
+// module dependencies.
282
+func NewBuildInfoCollector() Collector {
283
+	path, version, sum := readBuildInfo()
284
+	c := &selfCollector{MustNewConstMetric(
285
+		NewDesc(
286
+			"go_build_info",
287
+			"Build information about the main Go module.",
288
+			nil, Labels{"path": path, "version": version, "checksum": sum},
289
+		),
290
+		GaugeValue, 1)}
291
+	c.init(c.self)
292
+	return c
293
+}
... ...
@@ -16,7 +16,9 @@ package prometheus
16 16
 import (
17 17
 	"fmt"
18 18
 	"math"
19
+	"runtime"
19 20
 	"sort"
21
+	"sync"
20 22
 	"sync/atomic"
21 23
 
22 24
 	"github.com/golang/protobuf/proto"
... ...
@@ -108,8 +110,9 @@ func ExponentialBuckets(start, factor float64, count int) []float64 {
108 108
 }
109 109
 
110 110
 // HistogramOpts bundles the options for creating a Histogram metric. It is
111
-// mandatory to set Name and Help to a non-empty string. All other fields are
112
-// optional and can safely be left at their zero value.
111
+// mandatory to set Name to a non-empty string. All other fields are optional
112
+// and can safely be left at their zero value, although it is strongly
113
+// encouraged to set a Help string.
113 114
 type HistogramOpts struct {
114 115
 	// Namespace, Subsystem, and Name are components of the fully-qualified
115 116
 	// name of the Histogram (created by joining these components with
... ...
@@ -120,29 +123,22 @@ type HistogramOpts struct {
120 120
 	Subsystem string
121 121
 	Name      string
122 122
 
123
-	// Help provides information about this Histogram. Mandatory!
123
+	// Help provides information about this Histogram.
124 124
 	//
125 125
 	// Metrics with the same fully-qualified name must have the same Help
126 126
 	// string.
127 127
 	Help string
128 128
 
129
-	// ConstLabels are used to attach fixed labels to this
130
-	// Histogram. Histograms with the same fully-qualified name must have the
131
-	// same label names in their ConstLabels.
129
+	// ConstLabels are used to attach fixed labels to this metric. Metrics
130
+	// with the same fully-qualified name must have the same label names in
131
+	// their ConstLabels.
132 132
 	//
133
-	// Note that in most cases, labels have a value that varies during the
134
-	// lifetime of a process. Those labels are usually managed with a
135
-	// HistogramVec. ConstLabels serve only special purposes. One is for the
136
-	// special case where the value of a label does not change during the
137
-	// lifetime of a process, e.g. if the revision of the running binary is
138
-	// put into a label. Another, more advanced purpose is if more than one
139
-	// Collector needs to collect Histograms with the same fully-qualified
140
-	// name. In that case, those Summaries must differ in the values of
141
-	// their ConstLabels. See the Collector examples.
142
-	//
143
-	// If the value of a label never changes (not even between binaries),
144
-	// that label most likely should not be a label at all (but part of the
145
-	// metric name).
133
+	// ConstLabels are only used rarely. In particular, do not use them to
134
+	// attach the same labels to all your metrics. Those use cases are
135
+	// better covered by target labels set by the scraping Prometheus
136
+	// server, or by one specific metric (e.g. a build_info or a
137
+	// machine_role metric). See also
138
+	// https://prometheus.io/docs/instrumenting/writing_exporters/#target-labels,-not-static-scraped-labels
146 139
 	ConstLabels Labels
147 140
 
148 141
 	// Buckets defines the buckets into which observations are counted. Each
... ...
@@ -169,7 +165,7 @@ func NewHistogram(opts HistogramOpts) Histogram {
169 169
 
170 170
 func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogram {
171 171
 	if len(desc.variableLabels) != len(labelValues) {
172
-		panic(errInconsistentCardinality)
172
+		panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, labelValues))
173 173
 	}
174 174
 
175 175
 	for _, n := range desc.variableLabels {
... ...
@@ -191,6 +187,7 @@ func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogr
191 191
 		desc:        desc,
192 192
 		upperBounds: opts.Buckets,
193 193
 		labelPairs:  makeLabelPairs(desc, labelValues),
194
+		counts:      [2]*histogramCounts{&histogramCounts{}, &histogramCounts{}},
194 195
 	}
195 196
 	for i, upperBound := range h.upperBounds {
196 197
 		if i < len(h.upperBounds)-1 {
... ...
@@ -207,30 +204,56 @@ func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogr
207 207
 			}
208 208
 		}
209 209
 	}
210
-	// Finally we know the final length of h.upperBounds and can make counts.
211
-	h.counts = make([]uint64, len(h.upperBounds))
210
+	// Finally we know the final length of h.upperBounds and can make buckets
211
+	// for both counts:
212
+	h.counts[0].buckets = make([]uint64, len(h.upperBounds))
213
+	h.counts[1].buckets = make([]uint64, len(h.upperBounds))
212 214
 
213 215
 	h.init(h) // Init self-collection.
214 216
 	return h
215 217
 }
216 218
 
217
-type histogram struct {
219
+type histogramCounts struct {
218 220
 	// sumBits contains the bits of the float64 representing the sum of all
219 221
 	// observations. sumBits and count have to go first in the struct to
220 222
 	// guarantee alignment for atomic operations.
221 223
 	// http://golang.org/pkg/sync/atomic/#pkg-note-BUG
222 224
 	sumBits uint64
223 225
 	count   uint64
226
+	buckets []uint64
227
+}
228
+
229
+type histogram struct {
230
+	// countAndHotIdx enables lock-free writes with use of atomic updates.
231
+	// The most significant bit is the hot index [0 or 1] of the count field
232
+	// below. Observe calls update the hot one. All remaining bits count the
233
+	// number of Observe calls. Observe starts by incrementing this counter,
234
+	// and finish by incrementing the count field in the respective
235
+	// histogramCounts, as a marker for completion.
236
+	//
237
+	// Calls of the Write method (which are non-mutating reads from the
238
+	// perspective of the histogram) swap the hot–cold under the writeMtx
239
+	// lock. A cooldown is awaited (while locked) by comparing the number of
240
+	// observations with the initiation count. Once they match, then the
241
+	// last observation on the now cool one has completed. All cool fields must
242
+	// be merged into the new hot before releasing writeMtx.
243
+	//
244
+	// Fields with atomic access first! See alignment constraint:
245
+	// http://golang.org/pkg/sync/atomic/#pkg-note-BUG
246
+	countAndHotIdx uint64
224 247
 
225 248
 	selfCollector
226
-	// Note that there is no mutex required.
249
+	desc     *Desc
250
+	writeMtx sync.Mutex // Only used in the Write method.
227 251
 
228
-	desc *Desc
252
+	// Two counts, one is "hot" for lock-free observations, the other is
253
+	// "cold" for writing out a dto.Metric. It has to be an array of
254
+	// pointers to guarantee 64bit alignment of the histogramCounts, see
255
+	// http://golang.org/pkg/sync/atomic/#pkg-note-BUG.
256
+	counts [2]*histogramCounts
229 257
 
230 258
 	upperBounds []float64
231
-	counts      []uint64
232
-
233
-	labelPairs []*dto.LabelPair
259
+	labelPairs  []*dto.LabelPair
234 260
 }
235 261
 
236 262
 func (h *histogram) Desc() *Desc {
... ...
@@ -248,36 +271,84 @@ func (h *histogram) Observe(v float64) {
248 248
 	// 100 buckets: 78.1 ns/op linear - binary 54.9 ns/op
249 249
 	// 300 buckets: 154 ns/op linear - binary 61.6 ns/op
250 250
 	i := sort.SearchFloat64s(h.upperBounds, v)
251
-	if i < len(h.counts) {
252
-		atomic.AddUint64(&h.counts[i], 1)
251
+
252
+	// We increment h.countAndHotIdx so that the counter in the lower
253
+	// 63 bits gets incremented. At the same time, we get the new value
254
+	// back, which we can use to find the currently-hot counts.
255
+	n := atomic.AddUint64(&h.countAndHotIdx, 1)
256
+	hotCounts := h.counts[n>>63]
257
+
258
+	if i < len(h.upperBounds) {
259
+		atomic.AddUint64(&hotCounts.buckets[i], 1)
253 260
 	}
254
-	atomic.AddUint64(&h.count, 1)
255 261
 	for {
256
-		oldBits := atomic.LoadUint64(&h.sumBits)
262
+		oldBits := atomic.LoadUint64(&hotCounts.sumBits)
257 263
 		newBits := math.Float64bits(math.Float64frombits(oldBits) + v)
258
-		if atomic.CompareAndSwapUint64(&h.sumBits, oldBits, newBits) {
264
+		if atomic.CompareAndSwapUint64(&hotCounts.sumBits, oldBits, newBits) {
259 265
 			break
260 266
 		}
261 267
 	}
268
+	// Increment count last as we take it as a signal that the observation
269
+	// is complete.
270
+	atomic.AddUint64(&hotCounts.count, 1)
262 271
 }
263 272
 
264 273
 func (h *histogram) Write(out *dto.Metric) error {
265
-	his := &dto.Histogram{}
266
-	buckets := make([]*dto.Bucket, len(h.upperBounds))
274
+	// For simplicity, we protect this whole method by a mutex. It is not in
275
+	// the hot path, i.e. Observe is called much more often than Write. The
276
+	// complication of making Write lock-free isn't worth it, if possible at
277
+	// all.
278
+	h.writeMtx.Lock()
279
+	defer h.writeMtx.Unlock()
280
+
281
+	// Adding 1<<63 switches the hot index (from 0 to 1 or from 1 to 0)
282
+	// without touching the count bits. See the struct comments for a full
283
+	// description of the algorithm.
284
+	n := atomic.AddUint64(&h.countAndHotIdx, 1<<63)
285
+	// count is contained unchanged in the lower 63 bits.
286
+	count := n & ((1 << 63) - 1)
287
+	// The most significant bit tells us which counts is hot. The complement
288
+	// is thus the cold one.
289
+	hotCounts := h.counts[n>>63]
290
+	coldCounts := h.counts[(^n)>>63]
291
+
292
+	// Await cooldown.
293
+	for count != atomic.LoadUint64(&coldCounts.count) {
294
+		runtime.Gosched() // Let observations get work done.
295
+	}
267 296
 
268
-	his.SampleSum = proto.Float64(math.Float64frombits(atomic.LoadUint64(&h.sumBits)))
269
-	his.SampleCount = proto.Uint64(atomic.LoadUint64(&h.count))
270
-	var count uint64
297
+	his := &dto.Histogram{
298
+		Bucket:      make([]*dto.Bucket, len(h.upperBounds)),
299
+		SampleCount: proto.Uint64(count),
300
+		SampleSum:   proto.Float64(math.Float64frombits(atomic.LoadUint64(&coldCounts.sumBits))),
301
+	}
302
+	var cumCount uint64
271 303
 	for i, upperBound := range h.upperBounds {
272
-		count += atomic.LoadUint64(&h.counts[i])
273
-		buckets[i] = &dto.Bucket{
274
-			CumulativeCount: proto.Uint64(count),
304
+		cumCount += atomic.LoadUint64(&coldCounts.buckets[i])
305
+		his.Bucket[i] = &dto.Bucket{
306
+			CumulativeCount: proto.Uint64(cumCount),
275 307
 			UpperBound:      proto.Float64(upperBound),
276 308
 		}
277 309
 	}
278
-	his.Bucket = buckets
310
+
279 311
 	out.Histogram = his
280 312
 	out.Label = h.labelPairs
313
+
314
+	// Finally add all the cold counts to the new hot counts and reset the cold counts.
315
+	atomic.AddUint64(&hotCounts.count, count)
316
+	atomic.StoreUint64(&coldCounts.count, 0)
317
+	for {
318
+		oldBits := atomic.LoadUint64(&hotCounts.sumBits)
319
+		newBits := math.Float64bits(math.Float64frombits(oldBits) + his.GetSampleSum())
320
+		if atomic.CompareAndSwapUint64(&hotCounts.sumBits, oldBits, newBits) {
321
+			atomic.StoreUint64(&coldCounts.sumBits, 0)
322
+			break
323
+		}
324
+	}
325
+	for i := range h.upperBounds {
326
+		atomic.AddUint64(&hotCounts.buckets[i], atomic.LoadUint64(&coldCounts.buckets[i]))
327
+		atomic.StoreUint64(&coldCounts.buckets[i], 0)
328
+	}
281 329
 	return nil
282 330
 }
283 331
 
... ...
@@ -287,12 +358,11 @@ func (h *histogram) Write(out *dto.Metric) error {
287 287
 // (e.g. HTTP request latencies, partitioned by status code and method). Create
288 288
 // instances with NewHistogramVec.
289 289
 type HistogramVec struct {
290
-	*MetricVec
290
+	*metricVec
291 291
 }
292 292
 
293 293
 // NewHistogramVec creates a new HistogramVec based on the provided HistogramOpts and
294
-// partitioned by the given label names. At least one label name must be
295
-// provided.
294
+// partitioned by the given label names.
296 295
 func NewHistogramVec(opts HistogramOpts, labelNames []string) *HistogramVec {
297 296
 	desc := NewDesc(
298 297
 		BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
... ...
@@ -301,47 +371,116 @@ func NewHistogramVec(opts HistogramOpts, labelNames []string) *HistogramVec {
301 301
 		opts.ConstLabels,
302 302
 	)
303 303
 	return &HistogramVec{
304
-		MetricVec: newMetricVec(desc, func(lvs ...string) Metric {
304
+		metricVec: newMetricVec(desc, func(lvs ...string) Metric {
305 305
 			return newHistogram(desc, opts, lvs...)
306 306
 		}),
307 307
 	}
308 308
 }
309 309
 
310
-// GetMetricWithLabelValues replaces the method of the same name in
311
-// MetricVec. The difference is that this method returns a Histogram and not a
312
-// Metric so that no type conversion is required.
313
-func (m *HistogramVec) GetMetricWithLabelValues(lvs ...string) (Histogram, error) {
314
-	metric, err := m.MetricVec.GetMetricWithLabelValues(lvs...)
310
+// GetMetricWithLabelValues returns the Histogram for the given slice of label
311
+// values (same order as the VariableLabels in Desc). If that combination of
312
+// label values is accessed for the first time, a new Histogram is created.
313
+//
314
+// It is possible to call this method without using the returned Histogram to only
315
+// create the new Histogram but leave it at its starting value, a Histogram without
316
+// any observations.
317
+//
318
+// Keeping the Histogram for later use is possible (and should be considered if
319
+// performance is critical), but keep in mind that Reset, DeleteLabelValues and
320
+// Delete can be used to delete the Histogram from the HistogramVec. In that case, the
321
+// Histogram will still exist, but it will not be exported anymore, even if a
322
+// Histogram with the same label values is created later. See also the CounterVec
323
+// example.
324
+//
325
+// An error is returned if the number of label values is not the same as the
326
+// number of VariableLabels in Desc (minus any curried labels).
327
+//
328
+// Note that for more than one label value, this method is prone to mistakes
329
+// caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as
330
+// an alternative to avoid that type of mistake. For higher label numbers, the
331
+// latter has a much more readable (albeit more verbose) syntax, but it comes
332
+// with a performance overhead (for creating and processing the Labels map).
333
+// See also the GaugeVec example.
334
+func (v *HistogramVec) GetMetricWithLabelValues(lvs ...string) (Observer, error) {
335
+	metric, err := v.metricVec.getMetricWithLabelValues(lvs...)
315 336
 	if metric != nil {
316
-		return metric.(Histogram), err
337
+		return metric.(Observer), err
317 338
 	}
318 339
 	return nil, err
319 340
 }
320 341
 
321
-// GetMetricWith replaces the method of the same name in MetricVec. The
322
-// difference is that this method returns a Histogram and not a Metric so that no
323
-// type conversion is required.
324
-func (m *HistogramVec) GetMetricWith(labels Labels) (Histogram, error) {
325
-	metric, err := m.MetricVec.GetMetricWith(labels)
342
+// GetMetricWith returns the Histogram for the given Labels map (the label names
343
+// must match those of the VariableLabels in Desc). If that label map is
344
+// accessed for the first time, a new Histogram is created. Implications of
345
+// creating a Histogram without using it and keeping the Histogram for later use
346
+// are the same as for GetMetricWithLabelValues.
347
+//
348
+// An error is returned if the number and names of the Labels are inconsistent
349
+// with those of the VariableLabels in Desc (minus any curried labels).
350
+//
351
+// This method is used for the same purpose as
352
+// GetMetricWithLabelValues(...string). See there for pros and cons of the two
353
+// methods.
354
+func (v *HistogramVec) GetMetricWith(labels Labels) (Observer, error) {
355
+	metric, err := v.metricVec.getMetricWith(labels)
326 356
 	if metric != nil {
327
-		return metric.(Histogram), err
357
+		return metric.(Observer), err
328 358
 	}
329 359
 	return nil, err
330 360
 }
331 361
 
332 362
 // WithLabelValues works as GetMetricWithLabelValues, but panics where
333
-// GetMetricWithLabelValues would have returned an error. By not returning an
334
-// error, WithLabelValues allows shortcuts like
363
+// GetMetricWithLabelValues would have returned an error. Not returning an
364
+// error allows shortcuts like
335 365
 //     myVec.WithLabelValues("404", "GET").Observe(42.21)
336
-func (m *HistogramVec) WithLabelValues(lvs ...string) Histogram {
337
-	return m.MetricVec.WithLabelValues(lvs...).(Histogram)
366
+func (v *HistogramVec) WithLabelValues(lvs ...string) Observer {
367
+	h, err := v.GetMetricWithLabelValues(lvs...)
368
+	if err != nil {
369
+		panic(err)
370
+	}
371
+	return h
338 372
 }
339 373
 
340
-// With works as GetMetricWith, but panics where GetMetricWithLabels would have
341
-// returned an error. By not returning an error, With allows shortcuts like
342
-//     myVec.With(Labels{"code": "404", "method": "GET"}).Observe(42.21)
343
-func (m *HistogramVec) With(labels Labels) Histogram {
344
-	return m.MetricVec.With(labels).(Histogram)
374
+// With works as GetMetricWith but panics where GetMetricWithLabels would have
375
+// returned an error. Not returning an error allows shortcuts like
376
+//     myVec.With(prometheus.Labels{"code": "404", "method": "GET"}).Observe(42.21)
377
+func (v *HistogramVec) With(labels Labels) Observer {
378
+	h, err := v.GetMetricWith(labels)
379
+	if err != nil {
380
+		panic(err)
381
+	}
382
+	return h
383
+}
384
+
385
+// CurryWith returns a vector curried with the provided labels, i.e. the
386
+// returned vector has those labels pre-set for all labeled operations performed
387
+// on it. The cardinality of the curried vector is reduced accordingly. The
388
+// order of the remaining labels stays the same (just with the curried labels
389
+// taken out of the sequence – which is relevant for the
390
+// (GetMetric)WithLabelValues methods). It is possible to curry a curried
391
+// vector, but only with labels not yet used for currying before.
392
+//
393
+// The metrics contained in the HistogramVec are shared between the curried and
394
+// uncurried vectors. They are just accessed differently. Curried and uncurried
395
+// vectors behave identically in terms of collection. Only one must be
396
+// registered with a given registry (usually the uncurried version). The Reset
397
+// method deletes all metrics, even if called on a curried vector.
398
+func (v *HistogramVec) CurryWith(labels Labels) (ObserverVec, error) {
399
+	vec, err := v.curryWith(labels)
400
+	if vec != nil {
401
+		return &HistogramVec{vec}, err
402
+	}
403
+	return nil, err
404
+}
405
+
406
+// MustCurryWith works as CurryWith but panics where CurryWith would have
407
+// returned an error.
408
+func (v *HistogramVec) MustCurryWith(labels Labels) ObserverVec {
409
+	vec, err := v.CurryWith(labels)
410
+	if err != nil {
411
+		panic(err)
412
+	}
413
+	return vec
345 414
 }
346 415
 
347 416
 type constHistogram struct {
... ...
@@ -393,7 +532,7 @@ func (h *constHistogram) Write(out *dto.Metric) error {
393 393
 // bucket.
394 394
 //
395 395
 // NewConstHistogram returns an error if the length of labelValues is not
396
-// consistent with the variable labels in Desc.
396
+// consistent with the variable labels in Desc or if Desc is invalid.
397 397
 func NewConstHistogram(
398 398
 	desc *Desc,
399 399
 	count uint64,
... ...
@@ -401,8 +540,11 @@ func NewConstHistogram(
401 401
 	buckets map[float64]uint64,
402 402
 	labelValues ...string,
403 403
 ) (Metric, error) {
404
-	if len(desc.variableLabels) != len(labelValues) {
405
-		return nil, errInconsistentCardinality
404
+	if desc.err != nil {
405
+		return nil, desc.err
406
+	}
407
+	if err := validateLabelValues(labelValues, len(desc.variableLabels)); err != nil {
408
+		return nil, err
406 409
 	}
407 410
 	return &constHistogram{
408 411
 		desc:       desc,
... ...
@@ -15,9 +15,7 @@ package prometheus
15 15
 
16 16
 import (
17 17
 	"bufio"
18
-	"bytes"
19 18
 	"compress/gzip"
20
-	"fmt"
21 19
 	"io"
22 20
 	"net"
23 21
 	"net/http"
... ...
@@ -36,24 +34,14 @@ import (
36 36
 
37 37
 const (
38 38
 	contentTypeHeader     = "Content-Type"
39
-	contentLengthHeader   = "Content-Length"
40 39
 	contentEncodingHeader = "Content-Encoding"
41 40
 	acceptEncodingHeader  = "Accept-Encoding"
42 41
 )
43 42
 
44
-var bufPool sync.Pool
45
-
46
-func getBuf() *bytes.Buffer {
47
-	buf := bufPool.Get()
48
-	if buf == nil {
49
-		return &bytes.Buffer{}
50
-	}
51
-	return buf.(*bytes.Buffer)
52
-}
53
-
54
-func giveBuf(buf *bytes.Buffer) {
55
-	buf.Reset()
56
-	bufPool.Put(buf)
43
+var gzipPool = sync.Pool{
44
+	New: func() interface{} {
45
+		return gzip.NewWriter(nil)
46
+	},
57 47
 }
58 48
 
59 49
 // Handler returns an HTTP handler for the DefaultGatherer. It is
... ...
@@ -61,68 +49,50 @@ func giveBuf(buf *bytes.Buffer) {
61 61
 // name).
62 62
 //
63 63
 // Deprecated: Please note the issues described in the doc comment of
64
-// InstrumentHandler. You might want to consider using promhttp.Handler instead
65
-// (which is non instrumented).
64
+// InstrumentHandler. You might want to consider using promhttp.Handler instead.
66 65
 func Handler() http.Handler {
67 66
 	return InstrumentHandler("prometheus", UninstrumentedHandler())
68 67
 }
69 68
 
70 69
 // UninstrumentedHandler returns an HTTP handler for the DefaultGatherer.
71 70
 //
72
-// Deprecated: Use promhttp.Handler instead. See there for further documentation.
71
+// Deprecated: Use promhttp.HandlerFor(DefaultGatherer, promhttp.HandlerOpts{})
72
+// instead. See there for further documentation.
73 73
 func UninstrumentedHandler() http.Handler {
74
-	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
74
+	return http.HandlerFunc(func(rsp http.ResponseWriter, req *http.Request) {
75 75
 		mfs, err := DefaultGatherer.Gather()
76 76
 		if err != nil {
77
-			http.Error(w, "An error has occurred during metrics collection:\n\n"+err.Error(), http.StatusInternalServerError)
77
+			httpError(rsp, err)
78 78
 			return
79 79
 		}
80 80
 
81 81
 		contentType := expfmt.Negotiate(req.Header)
82
-		buf := getBuf()
83
-		defer giveBuf(buf)
84
-		writer, encoding := decorateWriter(req, buf)
85
-		enc := expfmt.NewEncoder(writer, contentType)
86
-		var lastErr error
82
+		header := rsp.Header()
83
+		header.Set(contentTypeHeader, string(contentType))
84
+
85
+		w := io.Writer(rsp)
86
+		if gzipAccepted(req.Header) {
87
+			header.Set(contentEncodingHeader, "gzip")
88
+			gz := gzipPool.Get().(*gzip.Writer)
89
+			defer gzipPool.Put(gz)
90
+
91
+			gz.Reset(w)
92
+			defer gz.Close()
93
+
94
+			w = gz
95
+		}
96
+
97
+		enc := expfmt.NewEncoder(w, contentType)
98
+
87 99
 		for _, mf := range mfs {
88 100
 			if err := enc.Encode(mf); err != nil {
89
-				lastErr = err
90
-				http.Error(w, "An error has occurred during metrics encoding:\n\n"+err.Error(), http.StatusInternalServerError)
101
+				httpError(rsp, err)
91 102
 				return
92 103
 			}
93 104
 		}
94
-		if closer, ok := writer.(io.Closer); ok {
95
-			closer.Close()
96
-		}
97
-		if lastErr != nil && buf.Len() == 0 {
98
-			http.Error(w, "No metrics encoded, last error:\n\n"+err.Error(), http.StatusInternalServerError)
99
-			return
100
-		}
101
-		header := w.Header()
102
-		header.Set(contentTypeHeader, string(contentType))
103
-		header.Set(contentLengthHeader, fmt.Sprint(buf.Len()))
104
-		if encoding != "" {
105
-			header.Set(contentEncodingHeader, encoding)
106
-		}
107
-		w.Write(buf.Bytes())
108 105
 	})
109 106
 }
110 107
 
111
-// decorateWriter wraps a writer to handle gzip compression if requested.  It
112
-// returns the decorated writer and the appropriate "Content-Encoding" header
113
-// (which is empty if no compression is enabled).
114
-func decorateWriter(request *http.Request, writer io.Writer) (io.Writer, string) {
115
-	header := request.Header.Get(acceptEncodingHeader)
116
-	parts := strings.Split(header, ",")
117
-	for _, part := range parts {
118
-		part := strings.TrimSpace(part)
119
-		if part == "gzip" || strings.HasPrefix(part, "gzip;") {
120
-			return gzip.NewWriter(writer), "gzip"
121
-		}
122
-	}
123
-	return writer, ""
124
-}
125
-
126 108
 var instLabels = []string{"method", "code"}
127 109
 
128 110
 type nower interface {
... ...
@@ -139,16 +109,6 @@ var now nower = nowFunc(func() time.Time {
139 139
 	return time.Now()
140 140
 })
141 141
 
142
-func nowSeries(t ...time.Time) nower {
143
-	return nowFunc(func() time.Time {
144
-		defer func() {
145
-			t = t[1:]
146
-		}()
147
-
148
-		return t[0]
149
-	})
150
-}
151
-
152 142
 // InstrumentHandler wraps the given HTTP handler for instrumentation. It
153 143
 // registers four metric collectors (if not already done) and reports HTTP
154 144
 // metrics to the (newly or already) registered collectors: http_requests_total
... ...
@@ -158,23 +118,16 @@ func nowSeries(t ...time.Time) nower {
158 158
 // value. http_requests_total is a metric vector partitioned by HTTP method
159 159
 // (label name "method") and HTTP status code (label name "code").
160 160
 //
161
-// Deprecated: InstrumentHandler has several issues:
162
-//
163
-// - It uses Summaries rather than Histograms. Summaries are not useful if
164
-// aggregation across multiple instances is required.
165
-//
166
-// - It uses microseconds as unit, which is deprecated and should be replaced by
167
-// seconds.
168
-//
169
-// - The size of the request is calculated in a separate goroutine. Since this
170
-// calculator requires access to the request header, it creates a race with
171
-// any writes to the header performed during request handling.
172
-// httputil.ReverseProxy is a prominent example for a handler
173
-// performing such writes.
174
-//
175
-// Upcoming versions of this package will provide ways of instrumenting HTTP
176
-// handlers that are more flexible and have fewer issues. Please prefer direct
177
-// instrumentation in the meantime.
161
+// Deprecated: InstrumentHandler has several issues. Use the tooling provided in
162
+// package promhttp instead. The issues are the following: (1) It uses Summaries
163
+// rather than Histograms. Summaries are not useful if aggregation across
164
+// multiple instances is required. (2) It uses microseconds as unit, which is
165
+// deprecated and should be replaced by seconds. (3) The size of the request is
166
+// calculated in a separate goroutine. Since this calculator requires access to
167
+// the request header, it creates a race with any writes to the header performed
168
+// during request handling.  httputil.ReverseProxy is a prominent example for a
169
+// handler performing such writes. (4) It has additional issues with HTTP/2, cf.
170
+// https://github.com/prometheus/client_golang/issues/272.
178 171
 func InstrumentHandler(handlerName string, handler http.Handler) http.HandlerFunc {
179 172
 	return InstrumentHandlerFunc(handlerName, handler.ServeHTTP)
180 173
 }
... ...
@@ -184,12 +137,13 @@ func InstrumentHandler(handlerName string, handler http.Handler) http.HandlerFun
184 184
 // issues).
185 185
 //
186 186
 // Deprecated: InstrumentHandlerFunc is deprecated for the same reasons as
187
-// InstrumentHandler is.
187
+// InstrumentHandler is. Use the tooling provided in package promhttp instead.
188 188
 func InstrumentHandlerFunc(handlerName string, handlerFunc func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
189 189
 	return InstrumentHandlerFuncWithOpts(
190 190
 		SummaryOpts{
191 191
 			Subsystem:   "http",
192 192
 			ConstLabels: Labels{"handler": handlerName},
193
+			Objectives:  map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
193 194
 		},
194 195
 		handlerFunc,
195 196
 	)
... ...
@@ -222,7 +176,7 @@ func InstrumentHandlerFunc(handlerName string, handlerFunc func(http.ResponseWri
222 222
 // SummaryOpts.
223 223
 //
224 224
 // Deprecated: InstrumentHandlerWithOpts is deprecated for the same reasons as
225
-// InstrumentHandler is.
225
+// InstrumentHandler is. Use the tooling provided in package promhttp instead.
226 226
 func InstrumentHandlerWithOpts(opts SummaryOpts, handler http.Handler) http.HandlerFunc {
227 227
 	return InstrumentHandlerFuncWithOpts(opts, handler.ServeHTTP)
228 228
 }
... ...
@@ -233,7 +187,7 @@ func InstrumentHandlerWithOpts(opts SummaryOpts, handler http.Handler) http.Hand
233 233
 // SummaryOpts are used.
234 234
 //
235 235
 // Deprecated: InstrumentHandlerFuncWithOpts is deprecated for the same reasons
236
-// as InstrumentHandler is.
236
+// as InstrumentHandler is. Use the tooling provided in package promhttp instead.
237 237
 func InstrumentHandlerFuncWithOpts(opts SummaryOpts, handlerFunc func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
238 238
 	reqCnt := NewCounterVec(
239 239
 		CounterOpts{
... ...
@@ -245,34 +199,52 @@ func InstrumentHandlerFuncWithOpts(opts SummaryOpts, handlerFunc func(http.Respo
245 245
 		},
246 246
 		instLabels,
247 247
 	)
248
+	if err := Register(reqCnt); err != nil {
249
+		if are, ok := err.(AlreadyRegisteredError); ok {
250
+			reqCnt = are.ExistingCollector.(*CounterVec)
251
+		} else {
252
+			panic(err)
253
+		}
254
+	}
248 255
 
249 256
 	opts.Name = "request_duration_microseconds"
250 257
 	opts.Help = "The HTTP request latencies in microseconds."
251 258
 	reqDur := NewSummary(opts)
259
+	if err := Register(reqDur); err != nil {
260
+		if are, ok := err.(AlreadyRegisteredError); ok {
261
+			reqDur = are.ExistingCollector.(Summary)
262
+		} else {
263
+			panic(err)
264
+		}
265
+	}
252 266
 
253 267
 	opts.Name = "request_size_bytes"
254 268
 	opts.Help = "The HTTP request sizes in bytes."
255 269
 	reqSz := NewSummary(opts)
270
+	if err := Register(reqSz); err != nil {
271
+		if are, ok := err.(AlreadyRegisteredError); ok {
272
+			reqSz = are.ExistingCollector.(Summary)
273
+		} else {
274
+			panic(err)
275
+		}
276
+	}
256 277
 
257 278
 	opts.Name = "response_size_bytes"
258 279
 	opts.Help = "The HTTP response sizes in bytes."
259 280
 	resSz := NewSummary(opts)
260
-
261
-	regReqCnt := MustRegisterOrGet(reqCnt).(*CounterVec)
262
-	regReqDur := MustRegisterOrGet(reqDur).(Summary)
263
-	regReqSz := MustRegisterOrGet(reqSz).(Summary)
264
-	regResSz := MustRegisterOrGet(resSz).(Summary)
281
+	if err := Register(resSz); err != nil {
282
+		if are, ok := err.(AlreadyRegisteredError); ok {
283
+			resSz = are.ExistingCollector.(Summary)
284
+		} else {
285
+			panic(err)
286
+		}
287
+	}
265 288
 
266 289
 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
267 290
 		now := time.Now()
268 291
 
269 292
 		delegate := &responseWriterDelegator{ResponseWriter: w}
270
-		out := make(chan int)
271
-		urlLen := 0
272
-		if r.URL != nil {
273
-			urlLen = len(r.URL.String())
274
-		}
275
-		go computeApproximateRequestSize(r, out, urlLen)
293
+		out := computeApproximateRequestSize(r)
276 294
 
277 295
 		_, cn := w.(http.CloseNotifier)
278 296
 		_, fl := w.(http.Flusher)
... ...
@@ -290,39 +262,52 @@ func InstrumentHandlerFuncWithOpts(opts SummaryOpts, handlerFunc func(http.Respo
290 290
 
291 291
 		method := sanitizeMethod(r.Method)
292 292
 		code := sanitizeCode(delegate.status)
293
-		regReqCnt.WithLabelValues(method, code).Inc()
294
-		regReqDur.Observe(elapsed)
295
-		regResSz.Observe(float64(delegate.written))
296
-		regReqSz.Observe(float64(<-out))
293
+		reqCnt.WithLabelValues(method, code).Inc()
294
+		reqDur.Observe(elapsed)
295
+		resSz.Observe(float64(delegate.written))
296
+		reqSz.Observe(float64(<-out))
297 297
 	})
298 298
 }
299 299
 
300
-func computeApproximateRequestSize(r *http.Request, out chan int, s int) {
301
-	s += len(r.Method)
302
-	s += len(r.Proto)
303
-	for name, values := range r.Header {
304
-		s += len(name)
305
-		for _, value := range values {
306
-			s += len(value)
307
-		}
300
+func computeApproximateRequestSize(r *http.Request) <-chan int {
301
+	// Get URL length in current goroutine for avoiding a race condition.
302
+	// HandlerFunc that runs in parallel may modify the URL.
303
+	s := 0
304
+	if r.URL != nil {
305
+		s += len(r.URL.String())
308 306
 	}
309
-	s += len(r.Host)
310 307
 
311
-	// N.B. r.Form and r.MultipartForm are assumed to be included in r.URL.
308
+	out := make(chan int, 1)
312 309
 
313
-	if r.ContentLength != -1 {
314
-		s += int(r.ContentLength)
315
-	}
316
-	out <- s
310
+	go func() {
311
+		s += len(r.Method)
312
+		s += len(r.Proto)
313
+		for name, values := range r.Header {
314
+			s += len(name)
315
+			for _, value := range values {
316
+				s += len(value)
317
+			}
318
+		}
319
+		s += len(r.Host)
320
+
321
+		// N.B. r.Form and r.MultipartForm are assumed to be included in r.URL.
322
+
323
+		if r.ContentLength != -1 {
324
+			s += int(r.ContentLength)
325
+		}
326
+		out <- s
327
+		close(out)
328
+	}()
329
+
330
+	return out
317 331
 }
318 332
 
319 333
 type responseWriterDelegator struct {
320 334
 	http.ResponseWriter
321 335
 
322
-	handler, method string
323
-	status          int
324
-	written         int64
325
-	wroteHeader     bool
336
+	status      int
337
+	written     int64
338
+	wroteHeader bool
326 339
 }
327 340
 
328 341
 func (r *responseWriterDelegator) WriteHeader(code int) {
... ...
@@ -345,6 +330,8 @@ type fancyResponseWriterDelegator struct {
345 345
 }
346 346
 
347 347
 func (f *fancyResponseWriterDelegator) CloseNotify() <-chan bool {
348
+	//lint:ignore SA1019 http.CloseNotifier is deprecated but we don't want to
349
+	//remove support from client_golang yet.
348 350
 	return f.ResponseWriter.(http.CloseNotifier).CloseNotify()
349 351
 }
350 352
 
... ...
@@ -488,3 +475,31 @@ func sanitizeCode(s int) string {
488 488
 		return strconv.Itoa(s)
489 489
 	}
490 490
 }
491
+
492
+// gzipAccepted returns whether the client will accept gzip-encoded content.
493
+func gzipAccepted(header http.Header) bool {
494
+	a := header.Get(acceptEncodingHeader)
495
+	parts := strings.Split(a, ",")
496
+	for _, part := range parts {
497
+		part = strings.TrimSpace(part)
498
+		if part == "gzip" || strings.HasPrefix(part, "gzip;") {
499
+			return true
500
+		}
501
+	}
502
+	return false
503
+}
504
+
505
+// httpError removes any content-encoding header and then calls http.Error with
506
+// the provided error and http.StatusInternalServerErrer. Error contents is
507
+// supposed to be uncompressed plain text. However, same as with a plain
508
+// http.Error, any header settings will be void if the header has already been
509
+// sent. The error message will still be written to the writer, but it will
510
+// probably be of limited use.
511
+func httpError(rsp http.ResponseWriter, err error) {
512
+	rsp.Header().Del(contentEncodingHeader)
513
+	http.Error(
514
+		rsp,
515
+		"An error has occurred while serving metrics:\n\n"+err.Error(),
516
+		http.StatusInternalServerError,
517
+	)
518
+}
491 519
new file mode 100644
... ...
@@ -0,0 +1,85 @@
0
+// Copyright 2018 The Prometheus Authors
1
+// Licensed under the Apache License, Version 2.0 (the "License");
2
+// you may not use this file except in compliance with the License.
3
+// You may obtain a copy of the License at
4
+//
5
+// http://www.apache.org/licenses/LICENSE-2.0
6
+//
7
+// Unless required by applicable law or agreed to in writing, software
8
+// distributed under the License is distributed on an "AS IS" BASIS,
9
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+// See the License for the specific language governing permissions and
11
+// limitations under the License.
12
+
13
+package internal
14
+
15
+import (
16
+	"sort"
17
+
18
+	dto "github.com/prometheus/client_model/go"
19
+)
20
+
21
+// metricSorter is a sortable slice of *dto.Metric.
22
+type metricSorter []*dto.Metric
23
+
24
+func (s metricSorter) Len() int {
25
+	return len(s)
26
+}
27
+
28
+func (s metricSorter) Swap(i, j int) {
29
+	s[i], s[j] = s[j], s[i]
30
+}
31
+
32
+func (s metricSorter) Less(i, j int) bool {
33
+	if len(s[i].Label) != len(s[j].Label) {
34
+		// This should not happen. The metrics are
35
+		// inconsistent. However, we have to deal with the fact, as
36
+		// people might use custom collectors or metric family injection
37
+		// to create inconsistent metrics. So let's simply compare the
38
+		// number of labels in this case. That will still yield
39
+		// reproducible sorting.
40
+		return len(s[i].Label) < len(s[j].Label)
41
+	}
42
+	for n, lp := range s[i].Label {
43
+		vi := lp.GetValue()
44
+		vj := s[j].Label[n].GetValue()
45
+		if vi != vj {
46
+			return vi < vj
47
+		}
48
+	}
49
+
50
+	// We should never arrive here. Multiple metrics with the same
51
+	// label set in the same scrape will lead to undefined ingestion
52
+	// behavior. However, as above, we have to provide stable sorting
53
+	// here, even for inconsistent metrics. So sort equal metrics
54
+	// by their timestamp, with missing timestamps (implying "now")
55
+	// coming last.
56
+	if s[i].TimestampMs == nil {
57
+		return false
58
+	}
59
+	if s[j].TimestampMs == nil {
60
+		return true
61
+	}
62
+	return s[i].GetTimestampMs() < s[j].GetTimestampMs()
63
+}
64
+
65
+// NormalizeMetricFamilies returns a MetricFamily slice with empty
66
+// MetricFamilies pruned and the remaining MetricFamilies sorted by name within
67
+// the slice, with the contained Metrics sorted within each MetricFamily.
68
+func NormalizeMetricFamilies(metricFamiliesByName map[string]*dto.MetricFamily) []*dto.MetricFamily {
69
+	for _, mf := range metricFamiliesByName {
70
+		sort.Sort(metricSorter(mf.Metric))
71
+	}
72
+	names := make([]string, 0, len(metricFamiliesByName))
73
+	for name, mf := range metricFamiliesByName {
74
+		if len(mf.Metric) > 0 {
75
+			names = append(names, name)
76
+		}
77
+	}
78
+	sort.Strings(names)
79
+	result := make([]*dto.MetricFamily, 0, len(names))
80
+	for _, name := range names {
81
+		result = append(result, metricFamiliesByName[name])
82
+	}
83
+	return result
84
+}
0 85
new file mode 100644
... ...
@@ -0,0 +1,87 @@
0
+// Copyright 2018 The Prometheus Authors
1
+// Licensed under the Apache License, Version 2.0 (the "License");
2
+// you may not use this file except in compliance with the License.
3
+// You may obtain a copy of the License at
4
+//
5
+// http://www.apache.org/licenses/LICENSE-2.0
6
+//
7
+// Unless required by applicable law or agreed to in writing, software
8
+// distributed under the License is distributed on an "AS IS" BASIS,
9
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+// See the License for the specific language governing permissions and
11
+// limitations under the License.
12
+
13
+package prometheus
14
+
15
+import (
16
+	"errors"
17
+	"fmt"
18
+	"strings"
19
+	"unicode/utf8"
20
+
21
+	"github.com/prometheus/common/model"
22
+)
23
+
24
+// Labels represents a collection of label name -> value mappings. This type is
25
+// commonly used with the With(Labels) and GetMetricWith(Labels) methods of
26
+// metric vector Collectors, e.g.:
27
+//     myVec.With(Labels{"code": "404", "method": "GET"}).Add(42)
28
+//
29
+// The other use-case is the specification of constant label pairs in Opts or to
30
+// create a Desc.
31
+type Labels map[string]string
32
+
33
+// reservedLabelPrefix is a prefix which is not legal in user-supplied
34
+// label names.
35
+const reservedLabelPrefix = "__"
36
+
37
+var errInconsistentCardinality = errors.New("inconsistent label cardinality")
38
+
39
+func makeInconsistentCardinalityError(fqName string, labels, labelValues []string) error {
40
+	return fmt.Errorf(
41
+		"%s: %q has %d variable labels named %q but %d values %q were provided",
42
+		errInconsistentCardinality, fqName,
43
+		len(labels), labels,
44
+		len(labelValues), labelValues,
45
+	)
46
+}
47
+
48
+func validateValuesInLabels(labels Labels, expectedNumberOfValues int) error {
49
+	if len(labels) != expectedNumberOfValues {
50
+		return fmt.Errorf(
51
+			"%s: expected %d label values but got %d in %#v",
52
+			errInconsistentCardinality, expectedNumberOfValues,
53
+			len(labels), labels,
54
+		)
55
+	}
56
+
57
+	for name, val := range labels {
58
+		if !utf8.ValidString(val) {
59
+			return fmt.Errorf("label %s: value %q is not valid UTF-8", name, val)
60
+		}
61
+	}
62
+
63
+	return nil
64
+}
65
+
66
+func validateLabelValues(vals []string, expectedNumberOfValues int) error {
67
+	if len(vals) != expectedNumberOfValues {
68
+		return fmt.Errorf(
69
+			"%s: expected %d label values but got %d in %#v",
70
+			errInconsistentCardinality, expectedNumberOfValues,
71
+			len(vals), vals,
72
+		)
73
+	}
74
+
75
+	for _, val := range vals {
76
+		if !utf8.ValidString(val) {
77
+			return fmt.Errorf("label value %q is not valid UTF-8", val)
78
+		}
79
+	}
80
+
81
+	return nil
82
+}
83
+
84
+func checkLabelName(l string) bool {
85
+	return model.LabelName(l).IsValid() && !strings.HasPrefix(l, reservedLabelPrefix)
86
+}
... ...
@@ -15,6 +15,9 @@ package prometheus
15 15
 
16 16
 import (
17 17
 	"strings"
18
+	"time"
19
+
20
+	"github.com/golang/protobuf/proto"
18 21
 
19 22
 	dto "github.com/prometheus/client_model/go"
20 23
 )
... ...
@@ -43,9 +46,8 @@ type Metric interface {
43 43
 	// While populating dto.Metric, it is the responsibility of the
44 44
 	// implementation to ensure validity of the Metric protobuf (like valid
45 45
 	// UTF-8 strings or syntactically valid metric and label names). It is
46
-	// recommended to sort labels lexicographically. (Implementers may find
47
-	// LabelPairSorter useful for that.) Callers of Write should still make
48
-	// sure of sorting if they depend on it.
46
+	// recommended to sort labels lexicographically. Callers of Write should
47
+	// still make sure of sorting if they depend on it.
49 48
 	Write(*dto.Metric) error
50 49
 	// TODO(beorn7): The original rationale of passing in a pre-allocated
51 50
 	// dto.Metric protobuf to save allocations has disappeared. The
... ...
@@ -57,8 +59,9 @@ type Metric interface {
57 57
 // implementation XXX has its own XXXOpts type, but in most cases, it is just be
58 58
 // an alias of this type (which might change when the requirement arises.)
59 59
 //
60
-// It is mandatory to set Name and Help to a non-empty string. All other fields
61
-// are optional and can safely be left at their zero value.
60
+// It is mandatory to set Name to a non-empty string. All other fields are
61
+// optional and can safely be left at their zero value, although it is strongly
62
+// encouraged to set a Help string.
62 63
 type Opts struct {
63 64
 	// Namespace, Subsystem, and Name are components of the fully-qualified
64 65
 	// name of the Metric (created by joining these components with
... ...
@@ -69,7 +72,7 @@ type Opts struct {
69 69
 	Subsystem string
70 70
 	Name      string
71 71
 
72
-	// Help provides information about this metric. Mandatory!
72
+	// Help provides information about this metric.
73 73
 	//
74 74
 	// Metrics with the same fully-qualified name must have the same Help
75 75
 	// string.
... ...
@@ -79,20 +82,12 @@ type Opts struct {
79 79
 	// with the same fully-qualified name must have the same label names in
80 80
 	// their ConstLabels.
81 81
 	//
82
-	// Note that in most cases, labels have a value that varies during the
83
-	// lifetime of a process. Those labels are usually managed with a metric
84
-	// vector collector (like CounterVec, GaugeVec, UntypedVec). ConstLabels
85
-	// serve only special purposes. One is for the special case where the
86
-	// value of a label does not change during the lifetime of a process,
87
-	// e.g. if the revision of the running binary is put into a
88
-	// label. Another, more advanced purpose is if more than one Collector
89
-	// needs to collect Metrics with the same fully-qualified name. In that
90
-	// case, those Metrics must differ in the values of their
91
-	// ConstLabels. See the Collector examples.
92
-	//
93
-	// If the value of a label never changes (not even between binaries),
94
-	// that label most likely should not be a label at all (but part of the
95
-	// metric name).
82
+	// ConstLabels are only used rarely. In particular, do not use them to
83
+	// attach the same labels to all your metrics. Those use cases are
84
+	// better covered by target labels set by the scraping Prometheus
85
+	// server, or by one specific metric (e.g. a build_info or a
86
+	// machine_role metric). See also
87
+	// https://prometheus.io/docs/instrumenting/writing_exporters/#target-labels,-not-static-scraped-labels
96 88
 	ConstLabels Labels
97 89
 }
98 90
 
... ...
@@ -118,37 +113,22 @@ func BuildFQName(namespace, subsystem, name string) string {
118 118
 	return name
119 119
 }
120 120
 
121
-// LabelPairSorter implements sort.Interface. It is used to sort a slice of
122
-// dto.LabelPair pointers. This is useful for implementing the Write method of
123
-// custom metrics.
124
-type LabelPairSorter []*dto.LabelPair
121
+// labelPairSorter implements sort.Interface. It is used to sort a slice of
122
+// dto.LabelPair pointers.
123
+type labelPairSorter []*dto.LabelPair
125 124
 
126
-func (s LabelPairSorter) Len() int {
125
+func (s labelPairSorter) Len() int {
127 126
 	return len(s)
128 127
 }
129 128
 
130
-func (s LabelPairSorter) Swap(i, j int) {
129
+func (s labelPairSorter) Swap(i, j int) {
131 130
 	s[i], s[j] = s[j], s[i]
132 131
 }
133 132
 
134
-func (s LabelPairSorter) Less(i, j int) bool {
133
+func (s labelPairSorter) Less(i, j int) bool {
135 134
 	return s[i].GetName() < s[j].GetName()
136 135
 }
137 136
 
138
-type hashSorter []uint64
139
-
140
-func (s hashSorter) Len() int {
141
-	return len(s)
142
-}
143
-
144
-func (s hashSorter) Swap(i, j int) {
145
-	s[i], s[j] = s[j], s[i]
146
-}
147
-
148
-func (s hashSorter) Less(i, j int) bool {
149
-	return s[i] < s[j]
150
-}
151
-
152 137
 type invalidMetric struct {
153 138
 	desc *Desc
154 139
 	err  error
... ...
@@ -164,3 +144,31 @@ func NewInvalidMetric(desc *Desc, err error) Metric {
164 164
 func (m *invalidMetric) Desc() *Desc { return m.desc }
165 165
 
166 166
 func (m *invalidMetric) Write(*dto.Metric) error { return m.err }
167
+
168
+type timestampedMetric struct {
169
+	Metric
170
+	t time.Time
171
+}
172
+
173
+func (m timestampedMetric) Write(pb *dto.Metric) error {
174
+	e := m.Metric.Write(pb)
175
+	pb.TimestampMs = proto.Int64(m.t.Unix()*1000 + int64(m.t.Nanosecond()/1000000))
176
+	return e
177
+}
178
+
179
+// NewMetricWithTimestamp returns a new Metric wrapping the provided Metric in a
180
+// way that it has an explicit timestamp set to the provided Time. This is only
181
+// useful in rare cases as the timestamp of a Prometheus metric should usually
182
+// be set by the Prometheus server during scraping. Exceptions include mirroring
183
+// metrics with given timestamps from other metric
184
+// sources.
185
+//
186
+// NewMetricWithTimestamp works best with MustNewConstMetric,
187
+// MustNewConstHistogram, and MustNewConstSummary, see example.
188
+//
189
+// Currently, the exposition formats used by Prometheus are limited to
190
+// millisecond resolution. Thus, the provided time will be rounded down to the
191
+// next full millisecond value.
192
+func NewMetricWithTimestamp(t time.Time, m Metric) Metric {
193
+	return timestampedMetric{Metric: m, t: t}
194
+}
167 195
new file mode 100644
... ...
@@ -0,0 +1,52 @@
0
+// Copyright 2017 The Prometheus Authors
1
+// Licensed under the Apache License, Version 2.0 (the "License");
2
+// you may not use this file except in compliance with the License.
3
+// You may obtain a copy of the License at
4
+//
5
+// http://www.apache.org/licenses/LICENSE-2.0
6
+//
7
+// Unless required by applicable law or agreed to in writing, software
8
+// distributed under the License is distributed on an "AS IS" BASIS,
9
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+// See the License for the specific language governing permissions and
11
+// limitations under the License.
12
+
13
+package prometheus
14
+
15
+// Observer is the interface that wraps the Observe method, which is used by
16
+// Histogram and Summary to add observations.
17
+type Observer interface {
18
+	Observe(float64)
19
+}
20
+
21
+// The ObserverFunc type is an adapter to allow the use of ordinary
22
+// functions as Observers. If f is a function with the appropriate
23
+// signature, ObserverFunc(f) is an Observer that calls f.
24
+//
25
+// This adapter is usually used in connection with the Timer type, and there are
26
+// two general use cases:
27
+//
28
+// The most common one is to use a Gauge as the Observer for a Timer.
29
+// See the "Gauge" Timer example.
30
+//
31
+// The more advanced use case is to create a function that dynamically decides
32
+// which Observer to use for observing the duration. See the "Complex" Timer
33
+// example.
34
+type ObserverFunc func(float64)
35
+
36
+// Observe calls f(value). It implements Observer.
37
+func (f ObserverFunc) Observe(value float64) {
38
+	f(value)
39
+}
40
+
41
+// ObserverVec is an interface implemented by `HistogramVec` and `SummaryVec`.
42
+type ObserverVec interface {
43
+	GetMetricWith(Labels) (Observer, error)
44
+	GetMetricWithLabelValues(lvs ...string) (Observer, error)
45
+	With(Labels) Observer
46
+	WithLabelValues(...string) Observer
47
+	CurryWith(Labels) (ObserverVec, error)
48
+	MustCurryWith(Labels) ObserverVec
49
+
50
+	Collector
51
+}
... ...
@@ -13,89 +13,139 @@
13 13
 
14 14
 package prometheus
15 15
 
16
-import "github.com/prometheus/procfs"
16
+import (
17
+	"errors"
18
+	"os"
19
+
20
+	"github.com/prometheus/procfs"
21
+)
17 22
 
18 23
 type processCollector struct {
19
-	pid             int
20 24
 	collectFn       func(chan<- Metric)
21 25
 	pidFn           func() (int, error)
22
-	cpuTotal        Counter
23
-	openFDs, maxFDs Gauge
24
-	vsize, rss      Gauge
25
-	startTime       Gauge
26
+	reportErrors    bool
27
+	cpuTotal        *Desc
28
+	openFDs, maxFDs *Desc
29
+	vsize, maxVsize *Desc
30
+	rss             *Desc
31
+	startTime       *Desc
26 32
 }
27 33
 
28
-// NewProcessCollector returns a collector which exports the current state of
29
-// process metrics including cpu, memory and file descriptor usage as well as
30
-// the process start time for the given process id under the given namespace.
31
-func NewProcessCollector(pid int, namespace string) Collector {
32
-	return NewProcessCollectorPIDFn(
33
-		func() (int, error) { return pid, nil },
34
-		namespace,
35
-	)
34
+// ProcessCollectorOpts defines the behavior of a process metrics collector
35
+// created with NewProcessCollector.
36
+type ProcessCollectorOpts struct {
37
+	// PidFn returns the PID of the process the collector collects metrics
38
+	// for. It is called upon each collection. By default, the PID of the
39
+	// current process is used, as determined on construction time by
40
+	// calling os.Getpid().
41
+	PidFn func() (int, error)
42
+	// If non-empty, each of the collected metrics is prefixed by the
43
+	// provided string and an underscore ("_").
44
+	Namespace string
45
+	// If true, any error encountered during collection is reported as an
46
+	// invalid metric (see NewInvalidMetric). Otherwise, errors are ignored
47
+	// and the collected metrics will be incomplete. (Possibly, no metrics
48
+	// will be collected at all.) While that's usually not desired, it is
49
+	// appropriate for the common "mix-in" of process metrics, where process
50
+	// metrics are nice to have, but failing to collect them should not
51
+	// disrupt the collection of the remaining metrics.
52
+	ReportErrors bool
36 53
 }
37 54
 
38
-// NewProcessCollectorPIDFn returns a collector which exports the current state
39
-// of process metrics including cpu, memory and file descriptor usage as well
40
-// as the process start time under the given namespace. The given pidFn is
41
-// called on each collect and is used to determine the process to export
42
-// metrics for.
43
-func NewProcessCollectorPIDFn(
44
-	pidFn func() (int, error),
45
-	namespace string,
46
-) Collector {
47
-	c := processCollector{
48
-		pidFn:     pidFn,
49
-		collectFn: func(chan<- Metric) {},
50
-
51
-		cpuTotal: NewCounter(CounterOpts{
52
-			Namespace: namespace,
53
-			Name:      "process_cpu_seconds_total",
54
-			Help:      "Total user and system CPU time spent in seconds.",
55
-		}),
56
-		openFDs: NewGauge(GaugeOpts{
57
-			Namespace: namespace,
58
-			Name:      "process_open_fds",
59
-			Help:      "Number of open file descriptors.",
60
-		}),
61
-		maxFDs: NewGauge(GaugeOpts{
62
-			Namespace: namespace,
63
-			Name:      "process_max_fds",
64
-			Help:      "Maximum number of open file descriptors.",
65
-		}),
66
-		vsize: NewGauge(GaugeOpts{
67
-			Namespace: namespace,
68
-			Name:      "process_virtual_memory_bytes",
69
-			Help:      "Virtual memory size in bytes.",
70
-		}),
71
-		rss: NewGauge(GaugeOpts{
72
-			Namespace: namespace,
73
-			Name:      "process_resident_memory_bytes",
74
-			Help:      "Resident memory size in bytes.",
75
-		}),
76
-		startTime: NewGauge(GaugeOpts{
77
-			Namespace: namespace,
78
-			Name:      "process_start_time_seconds",
79
-			Help:      "Start time of the process since unix epoch in seconds.",
80
-		}),
55
+// NewProcessCollector returns a collector which exports the current state of
56
+// process metrics including CPU, memory and file descriptor usage as well as
57
+// the process start time. The detailed behavior is defined by the provided
58
+// ProcessCollectorOpts. The zero value of ProcessCollectorOpts creates a
59
+// collector for the current process with an empty namespace string and no error
60
+// reporting.
61
+//
62
+// Currently, the collector depends on a Linux-style proc filesystem and
63
+// therefore only exports metrics for Linux.
64
+//
65
+// Note: An older version of this function had the following signature:
66
+//
67
+//     NewProcessCollector(pid int, namespace string) Collector
68
+//
69
+// Most commonly, it was called as
70
+//
71
+//     NewProcessCollector(os.Getpid(), "")
72
+//
73
+// The following call of the current version is equivalent to the above:
74
+//
75
+//     NewProcessCollector(ProcessCollectorOpts{})
76
+func NewProcessCollector(opts ProcessCollectorOpts) Collector {
77
+	ns := ""
78
+	if len(opts.Namespace) > 0 {
79
+		ns = opts.Namespace + "_"
80
+	}
81
+
82
+	c := &processCollector{
83
+		reportErrors: opts.ReportErrors,
84
+		cpuTotal: NewDesc(
85
+			ns+"process_cpu_seconds_total",
86
+			"Total user and system CPU time spent in seconds.",
87
+			nil, nil,
88
+		),
89
+		openFDs: NewDesc(
90
+			ns+"process_open_fds",
91
+			"Number of open file descriptors.",
92
+			nil, nil,
93
+		),
94
+		maxFDs: NewDesc(
95
+			ns+"process_max_fds",
96
+			"Maximum number of open file descriptors.",
97
+			nil, nil,
98
+		),
99
+		vsize: NewDesc(
100
+			ns+"process_virtual_memory_bytes",
101
+			"Virtual memory size in bytes.",
102
+			nil, nil,
103
+		),
104
+		maxVsize: NewDesc(
105
+			ns+"process_virtual_memory_max_bytes",
106
+			"Maximum amount of virtual memory available in bytes.",
107
+			nil, nil,
108
+		),
109
+		rss: NewDesc(
110
+			ns+"process_resident_memory_bytes",
111
+			"Resident memory size in bytes.",
112
+			nil, nil,
113
+		),
114
+		startTime: NewDesc(
115
+			ns+"process_start_time_seconds",
116
+			"Start time of the process since unix epoch in seconds.",
117
+			nil, nil,
118
+		),
119
+	}
120
+
121
+	if opts.PidFn == nil {
122
+		pid := os.Getpid()
123
+		c.pidFn = func() (int, error) { return pid, nil }
124
+	} else {
125
+		c.pidFn = opts.PidFn
81 126
 	}
82 127
 
83 128
 	// Set up process metric collection if supported by the runtime.
84
-	if _, err := procfs.NewStat(); err == nil {
129
+	if _, err := procfs.NewDefaultFS(); err == nil {
85 130
 		c.collectFn = c.processCollect
131
+	} else {
132
+		c.collectFn = func(ch chan<- Metric) {
133
+			c.reportError(ch, nil, errors.New("process metrics not supported on this platform"))
134
+		}
86 135
 	}
87 136
 
88
-	return &c
137
+	return c
89 138
 }
90 139
 
91 140
 // Describe returns all descriptions of the collector.
92 141
 func (c *processCollector) Describe(ch chan<- *Desc) {
93
-	ch <- c.cpuTotal.Desc()
94
-	ch <- c.openFDs.Desc()
95
-	ch <- c.maxFDs.Desc()
96
-	ch <- c.vsize.Desc()
97
-	ch <- c.rss.Desc()
98
-	ch <- c.startTime.Desc()
142
+	ch <- c.cpuTotal
143
+	ch <- c.openFDs
144
+	ch <- c.maxFDs
145
+	ch <- c.vsize
146
+	ch <- c.maxVsize
147
+	ch <- c.rss
148
+	ch <- c.startTime
99 149
 }
100 150
 
101 151
 // Collect returns the current state of all metrics of the collector.
... ...
@@ -103,40 +153,52 @@ func (c *processCollector) Collect(ch chan<- Metric) {
103 103
 	c.collectFn(ch)
104 104
 }
105 105
 
106
-// TODO(ts): Bring back error reporting by reverting 7faf9e7 as soon as the
107
-// client allows users to configure the error behavior.
108 106
 func (c *processCollector) processCollect(ch chan<- Metric) {
109 107
 	pid, err := c.pidFn()
110 108
 	if err != nil {
109
+		c.reportError(ch, nil, err)
111 110
 		return
112 111
 	}
113 112
 
114 113
 	p, err := procfs.NewProc(pid)
115 114
 	if err != nil {
115
+		c.reportError(ch, nil, err)
116 116
 		return
117 117
 	}
118 118
 
119
-	if stat, err := p.NewStat(); err == nil {
120
-		c.cpuTotal.Set(stat.CPUTime())
121
-		ch <- c.cpuTotal
122
-		c.vsize.Set(float64(stat.VirtualMemory()))
123
-		ch <- c.vsize
124
-		c.rss.Set(float64(stat.ResidentMemory()))
125
-		ch <- c.rss
126
-
119
+	if stat, err := p.Stat(); err == nil {
120
+		ch <- MustNewConstMetric(c.cpuTotal, CounterValue, stat.CPUTime())
121
+		ch <- MustNewConstMetric(c.vsize, GaugeValue, float64(stat.VirtualMemory()))
122
+		ch <- MustNewConstMetric(c.rss, GaugeValue, float64(stat.ResidentMemory()))
127 123
 		if startTime, err := stat.StartTime(); err == nil {
128
-			c.startTime.Set(startTime)
129
-			ch <- c.startTime
124
+			ch <- MustNewConstMetric(c.startTime, GaugeValue, startTime)
125
+		} else {
126
+			c.reportError(ch, c.startTime, err)
130 127
 		}
128
+	} else {
129
+		c.reportError(ch, nil, err)
131 130
 	}
132 131
 
133 132
 	if fds, err := p.FileDescriptorsLen(); err == nil {
134
-		c.openFDs.Set(float64(fds))
135
-		ch <- c.openFDs
133
+		ch <- MustNewConstMetric(c.openFDs, GaugeValue, float64(fds))
134
+	} else {
135
+		c.reportError(ch, c.openFDs, err)
136 136
 	}
137 137
 
138
-	if limits, err := p.NewLimits(); err == nil {
139
-		c.maxFDs.Set(float64(limits.OpenFiles))
140
-		ch <- c.maxFDs
138
+	if limits, err := p.Limits(); err == nil {
139
+		ch <- MustNewConstMetric(c.maxFDs, GaugeValue, float64(limits.OpenFiles))
140
+		ch <- MustNewConstMetric(c.maxVsize, GaugeValue, float64(limits.AddressSpace))
141
+	} else {
142
+		c.reportError(ch, nil, err)
143
+	}
144
+}
145
+
146
+func (c *processCollector) reportError(ch chan<- Metric, desc *Desc, err error) {
147
+	if !c.reportErrors {
148
+		return
149
+	}
150
+	if desc == nil {
151
+		desc = NewInvalidDesc(err)
141 152
 	}
153
+	ch <- NewInvalidMetric(desc, err)
142 154
 }
143 155
new file mode 100644
... ...
@@ -0,0 +1,357 @@
0
+// Copyright 2017 The Prometheus Authors
1
+// Licensed under the Apache License, Version 2.0 (the "License");
2
+// you may not use this file except in compliance with the License.
3
+// You may obtain a copy of the License at
4
+//
5
+// http://www.apache.org/licenses/LICENSE-2.0
6
+//
7
+// Unless required by applicable law or agreed to in writing, software
8
+// distributed under the License is distributed on an "AS IS" BASIS,
9
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+// See the License for the specific language governing permissions and
11
+// limitations under the License.
12
+
13
+package promhttp
14
+
15
+import (
16
+	"bufio"
17
+	"io"
18
+	"net"
19
+	"net/http"
20
+)
21
+
22
+const (
23
+	closeNotifier = 1 << iota
24
+	flusher
25
+	hijacker
26
+	readerFrom
27
+	pusher
28
+)
29
+
30
+type delegator interface {
31
+	http.ResponseWriter
32
+
33
+	Status() int
34
+	Written() int64
35
+}
36
+
37
+type responseWriterDelegator struct {
38
+	http.ResponseWriter
39
+
40
+	status             int
41
+	written            int64
42
+	wroteHeader        bool
43
+	observeWriteHeader func(int)
44
+}
45
+
46
+func (r *responseWriterDelegator) Status() int {
47
+	return r.status
48
+}
49
+
50
+func (r *responseWriterDelegator) Written() int64 {
51
+	return r.written
52
+}
53
+
54
+func (r *responseWriterDelegator) WriteHeader(code int) {
55
+	r.status = code
56
+	r.wroteHeader = true
57
+	r.ResponseWriter.WriteHeader(code)
58
+	if r.observeWriteHeader != nil {
59
+		r.observeWriteHeader(code)
60
+	}
61
+}
62
+
63
+func (r *responseWriterDelegator) Write(b []byte) (int, error) {
64
+	if !r.wroteHeader {
65
+		r.WriteHeader(http.StatusOK)
66
+	}
67
+	n, err := r.ResponseWriter.Write(b)
68
+	r.written += int64(n)
69
+	return n, err
70
+}
71
+
72
+type closeNotifierDelegator struct{ *responseWriterDelegator }
73
+type flusherDelegator struct{ *responseWriterDelegator }
74
+type hijackerDelegator struct{ *responseWriterDelegator }
75
+type readerFromDelegator struct{ *responseWriterDelegator }
76
+type pusherDelegator struct{ *responseWriterDelegator }
77
+
78
+func (d closeNotifierDelegator) CloseNotify() <-chan bool {
79
+	//lint:ignore SA1019 http.CloseNotifier is deprecated but we don't want to
80
+	//remove support from client_golang yet.
81
+	return d.ResponseWriter.(http.CloseNotifier).CloseNotify()
82
+}
83
+func (d flusherDelegator) Flush() {
84
+	d.ResponseWriter.(http.Flusher).Flush()
85
+}
86
+func (d hijackerDelegator) Hijack() (net.Conn, *bufio.ReadWriter, error) {
87
+	return d.ResponseWriter.(http.Hijacker).Hijack()
88
+}
89
+func (d readerFromDelegator) ReadFrom(re io.Reader) (int64, error) {
90
+	if !d.wroteHeader {
91
+		d.WriteHeader(http.StatusOK)
92
+	}
93
+	n, err := d.ResponseWriter.(io.ReaderFrom).ReadFrom(re)
94
+	d.written += n
95
+	return n, err
96
+}
97
+func (d pusherDelegator) Push(target string, opts *http.PushOptions) error {
98
+	return d.ResponseWriter.(http.Pusher).Push(target, opts)
99
+}
100
+
101
+var pickDelegator = make([]func(*responseWriterDelegator) delegator, 32)
102
+
103
+func init() {
104
+	// TODO(beorn7): Code generation would help here.
105
+	pickDelegator[0] = func(d *responseWriterDelegator) delegator { // 0
106
+		return d
107
+	}
108
+	pickDelegator[closeNotifier] = func(d *responseWriterDelegator) delegator { // 1
109
+		return closeNotifierDelegator{d}
110
+	}
111
+	pickDelegator[flusher] = func(d *responseWriterDelegator) delegator { // 2
112
+		return flusherDelegator{d}
113
+	}
114
+	pickDelegator[flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 3
115
+		return struct {
116
+			*responseWriterDelegator
117
+			http.Flusher
118
+			http.CloseNotifier
119
+		}{d, flusherDelegator{d}, closeNotifierDelegator{d}}
120
+	}
121
+	pickDelegator[hijacker] = func(d *responseWriterDelegator) delegator { // 4
122
+		return hijackerDelegator{d}
123
+	}
124
+	pickDelegator[hijacker+closeNotifier] = func(d *responseWriterDelegator) delegator { // 5
125
+		return struct {
126
+			*responseWriterDelegator
127
+			http.Hijacker
128
+			http.CloseNotifier
129
+		}{d, hijackerDelegator{d}, closeNotifierDelegator{d}}
130
+	}
131
+	pickDelegator[hijacker+flusher] = func(d *responseWriterDelegator) delegator { // 6
132
+		return struct {
133
+			*responseWriterDelegator
134
+			http.Hijacker
135
+			http.Flusher
136
+		}{d, hijackerDelegator{d}, flusherDelegator{d}}
137
+	}
138
+	pickDelegator[hijacker+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 7
139
+		return struct {
140
+			*responseWriterDelegator
141
+			http.Hijacker
142
+			http.Flusher
143
+			http.CloseNotifier
144
+		}{d, hijackerDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}}
145
+	}
146
+	pickDelegator[readerFrom] = func(d *responseWriterDelegator) delegator { // 8
147
+		return readerFromDelegator{d}
148
+	}
149
+	pickDelegator[readerFrom+closeNotifier] = func(d *responseWriterDelegator) delegator { // 9
150
+		return struct {
151
+			*responseWriterDelegator
152
+			io.ReaderFrom
153
+			http.CloseNotifier
154
+		}{d, readerFromDelegator{d}, closeNotifierDelegator{d}}
155
+	}
156
+	pickDelegator[readerFrom+flusher] = func(d *responseWriterDelegator) delegator { // 10
157
+		return struct {
158
+			*responseWriterDelegator
159
+			io.ReaderFrom
160
+			http.Flusher
161
+		}{d, readerFromDelegator{d}, flusherDelegator{d}}
162
+	}
163
+	pickDelegator[readerFrom+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 11
164
+		return struct {
165
+			*responseWriterDelegator
166
+			io.ReaderFrom
167
+			http.Flusher
168
+			http.CloseNotifier
169
+		}{d, readerFromDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}}
170
+	}
171
+	pickDelegator[readerFrom+hijacker] = func(d *responseWriterDelegator) delegator { // 12
172
+		return struct {
173
+			*responseWriterDelegator
174
+			io.ReaderFrom
175
+			http.Hijacker
176
+		}{d, readerFromDelegator{d}, hijackerDelegator{d}}
177
+	}
178
+	pickDelegator[readerFrom+hijacker+closeNotifier] = func(d *responseWriterDelegator) delegator { // 13
179
+		return struct {
180
+			*responseWriterDelegator
181
+			io.ReaderFrom
182
+			http.Hijacker
183
+			http.CloseNotifier
184
+		}{d, readerFromDelegator{d}, hijackerDelegator{d}, closeNotifierDelegator{d}}
185
+	}
186
+	pickDelegator[readerFrom+hijacker+flusher] = func(d *responseWriterDelegator) delegator { // 14
187
+		return struct {
188
+			*responseWriterDelegator
189
+			io.ReaderFrom
190
+			http.Hijacker
191
+			http.Flusher
192
+		}{d, readerFromDelegator{d}, hijackerDelegator{d}, flusherDelegator{d}}
193
+	}
194
+	pickDelegator[readerFrom+hijacker+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 15
195
+		return struct {
196
+			*responseWriterDelegator
197
+			io.ReaderFrom
198
+			http.Hijacker
199
+			http.Flusher
200
+			http.CloseNotifier
201
+		}{d, readerFromDelegator{d}, hijackerDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}}
202
+	}
203
+	pickDelegator[pusher] = func(d *responseWriterDelegator) delegator { // 16
204
+		return pusherDelegator{d}
205
+	}
206
+	pickDelegator[pusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 17
207
+		return struct {
208
+			*responseWriterDelegator
209
+			http.Pusher
210
+			http.CloseNotifier
211
+		}{d, pusherDelegator{d}, closeNotifierDelegator{d}}
212
+	}
213
+	pickDelegator[pusher+flusher] = func(d *responseWriterDelegator) delegator { // 18
214
+		return struct {
215
+			*responseWriterDelegator
216
+			http.Pusher
217
+			http.Flusher
218
+		}{d, pusherDelegator{d}, flusherDelegator{d}}
219
+	}
220
+	pickDelegator[pusher+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 19
221
+		return struct {
222
+			*responseWriterDelegator
223
+			http.Pusher
224
+			http.Flusher
225
+			http.CloseNotifier
226
+		}{d, pusherDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}}
227
+	}
228
+	pickDelegator[pusher+hijacker] = func(d *responseWriterDelegator) delegator { // 20
229
+		return struct {
230
+			*responseWriterDelegator
231
+			http.Pusher
232
+			http.Hijacker
233
+		}{d, pusherDelegator{d}, hijackerDelegator{d}}
234
+	}
235
+	pickDelegator[pusher+hijacker+closeNotifier] = func(d *responseWriterDelegator) delegator { // 21
236
+		return struct {
237
+			*responseWriterDelegator
238
+			http.Pusher
239
+			http.Hijacker
240
+			http.CloseNotifier
241
+		}{d, pusherDelegator{d}, hijackerDelegator{d}, closeNotifierDelegator{d}}
242
+	}
243
+	pickDelegator[pusher+hijacker+flusher] = func(d *responseWriterDelegator) delegator { // 22
244
+		return struct {
245
+			*responseWriterDelegator
246
+			http.Pusher
247
+			http.Hijacker
248
+			http.Flusher
249
+		}{d, pusherDelegator{d}, hijackerDelegator{d}, flusherDelegator{d}}
250
+	}
251
+	pickDelegator[pusher+hijacker+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { //23
252
+		return struct {
253
+			*responseWriterDelegator
254
+			http.Pusher
255
+			http.Hijacker
256
+			http.Flusher
257
+			http.CloseNotifier
258
+		}{d, pusherDelegator{d}, hijackerDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}}
259
+	}
260
+	pickDelegator[pusher+readerFrom] = func(d *responseWriterDelegator) delegator { // 24
261
+		return struct {
262
+			*responseWriterDelegator
263
+			http.Pusher
264
+			io.ReaderFrom
265
+		}{d, pusherDelegator{d}, readerFromDelegator{d}}
266
+	}
267
+	pickDelegator[pusher+readerFrom+closeNotifier] = func(d *responseWriterDelegator) delegator { // 25
268
+		return struct {
269
+			*responseWriterDelegator
270
+			http.Pusher
271
+			io.ReaderFrom
272
+			http.CloseNotifier
273
+		}{d, pusherDelegator{d}, readerFromDelegator{d}, closeNotifierDelegator{d}}
274
+	}
275
+	pickDelegator[pusher+readerFrom+flusher] = func(d *responseWriterDelegator) delegator { // 26
276
+		return struct {
277
+			*responseWriterDelegator
278
+			http.Pusher
279
+			io.ReaderFrom
280
+			http.Flusher
281
+		}{d, pusherDelegator{d}, readerFromDelegator{d}, flusherDelegator{d}}
282
+	}
283
+	pickDelegator[pusher+readerFrom+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 27
284
+		return struct {
285
+			*responseWriterDelegator
286
+			http.Pusher
287
+			io.ReaderFrom
288
+			http.Flusher
289
+			http.CloseNotifier
290
+		}{d, pusherDelegator{d}, readerFromDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}}
291
+	}
292
+	pickDelegator[pusher+readerFrom+hijacker] = func(d *responseWriterDelegator) delegator { // 28
293
+		return struct {
294
+			*responseWriterDelegator
295
+			http.Pusher
296
+			io.ReaderFrom
297
+			http.Hijacker
298
+		}{d, pusherDelegator{d}, readerFromDelegator{d}, hijackerDelegator{d}}
299
+	}
300
+	pickDelegator[pusher+readerFrom+hijacker+closeNotifier] = func(d *responseWriterDelegator) delegator { // 29
301
+		return struct {
302
+			*responseWriterDelegator
303
+			http.Pusher
304
+			io.ReaderFrom
305
+			http.Hijacker
306
+			http.CloseNotifier
307
+		}{d, pusherDelegator{d}, readerFromDelegator{d}, hijackerDelegator{d}, closeNotifierDelegator{d}}
308
+	}
309
+	pickDelegator[pusher+readerFrom+hijacker+flusher] = func(d *responseWriterDelegator) delegator { // 30
310
+		return struct {
311
+			*responseWriterDelegator
312
+			http.Pusher
313
+			io.ReaderFrom
314
+			http.Hijacker
315
+			http.Flusher
316
+		}{d, pusherDelegator{d}, readerFromDelegator{d}, hijackerDelegator{d}, flusherDelegator{d}}
317
+	}
318
+	pickDelegator[pusher+readerFrom+hijacker+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 31
319
+		return struct {
320
+			*responseWriterDelegator
321
+			http.Pusher
322
+			io.ReaderFrom
323
+			http.Hijacker
324
+			http.Flusher
325
+			http.CloseNotifier
326
+		}{d, pusherDelegator{d}, readerFromDelegator{d}, hijackerDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}}
327
+	}
328
+}
329
+
330
+func newDelegator(w http.ResponseWriter, observeWriteHeaderFunc func(int)) delegator {
331
+	d := &responseWriterDelegator{
332
+		ResponseWriter:     w,
333
+		observeWriteHeader: observeWriteHeaderFunc,
334
+	}
335
+
336
+	id := 0
337
+	//lint:ignore SA1019 http.CloseNotifier is deprecated but we don't want to
338
+	//remove support from client_golang yet.
339
+	if _, ok := w.(http.CloseNotifier); ok {
340
+		id += closeNotifier
341
+	}
342
+	if _, ok := w.(http.Flusher); ok {
343
+		id += flusher
344
+	}
345
+	if _, ok := w.(http.Hijacker); ok {
346
+		id += hijacker
347
+	}
348
+	if _, ok := w.(io.ReaderFrom); ok {
349
+		id += readerFrom
350
+	}
351
+	if _, ok := w.(http.Pusher); ok {
352
+		id += pusher
353
+	}
354
+
355
+	return pickDelegator[id](d)
356
+}
0 357
new file mode 100644
... ...
@@ -0,0 +1,349 @@
0
+// Copyright 2016 The Prometheus Authors
1
+// Licensed under the Apache License, Version 2.0 (the "License");
2
+// you may not use this file except in compliance with the License.
3
+// You may obtain a copy of the License at
4
+//
5
+// http://www.apache.org/licenses/LICENSE-2.0
6
+//
7
+// Unless required by applicable law or agreed to in writing, software
8
+// distributed under the License is distributed on an "AS IS" BASIS,
9
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+// See the License for the specific language governing permissions and
11
+// limitations under the License.
12
+
13
+// Package promhttp provides tooling around HTTP servers and clients.
14
+//
15
+// First, the package allows the creation of http.Handler instances to expose
16
+// Prometheus metrics via HTTP. promhttp.Handler acts on the
17
+// prometheus.DefaultGatherer. With HandlerFor, you can create a handler for a
18
+// custom registry or anything that implements the Gatherer interface. It also
19
+// allows the creation of handlers that act differently on errors or allow to
20
+// log errors.
21
+//
22
+// Second, the package provides tooling to instrument instances of http.Handler
23
+// via middleware. Middleware wrappers follow the naming scheme
24
+// InstrumentHandlerX, where X describes the intended use of the middleware.
25
+// See each function's doc comment for specific details.
26
+//
27
+// Finally, the package allows for an http.RoundTripper to be instrumented via
28
+// middleware. Middleware wrappers follow the naming scheme
29
+// InstrumentRoundTripperX, where X describes the intended use of the
30
+// middleware. See each function's doc comment for specific details.
31
+package promhttp
32
+
33
+import (
34
+	"compress/gzip"
35
+	"fmt"
36
+	"io"
37
+	"net/http"
38
+	"strings"
39
+	"sync"
40
+	"time"
41
+
42
+	"github.com/prometheus/common/expfmt"
43
+
44
+	"github.com/prometheus/client_golang/prometheus"
45
+)
46
+
47
+const (
48
+	contentTypeHeader     = "Content-Type"
49
+	contentEncodingHeader = "Content-Encoding"
50
+	acceptEncodingHeader  = "Accept-Encoding"
51
+)
52
+
53
+var gzipPool = sync.Pool{
54
+	New: func() interface{} {
55
+		return gzip.NewWriter(nil)
56
+	},
57
+}
58
+
59
+// Handler returns an http.Handler for the prometheus.DefaultGatherer, using
60
+// default HandlerOpts, i.e. it reports the first error as an HTTP error, it has
61
+// no error logging, and it applies compression if requested by the client.
62
+//
63
+// The returned http.Handler is already instrumented using the
64
+// InstrumentMetricHandler function and the prometheus.DefaultRegisterer. If you
65
+// create multiple http.Handlers by separate calls of the Handler function, the
66
+// metrics used for instrumentation will be shared between them, providing
67
+// global scrape counts.
68
+//
69
+// This function is meant to cover the bulk of basic use cases. If you are doing
70
+// anything that requires more customization (including using a non-default
71
+// Gatherer, different instrumentation, and non-default HandlerOpts), use the
72
+// HandlerFor function. See there for details.
73
+func Handler() http.Handler {
74
+	return InstrumentMetricHandler(
75
+		prometheus.DefaultRegisterer, HandlerFor(prometheus.DefaultGatherer, HandlerOpts{}),
76
+	)
77
+}
78
+
79
+// HandlerFor returns an uninstrumented http.Handler for the provided
80
+// Gatherer. The behavior of the Handler is defined by the provided
81
+// HandlerOpts. Thus, HandlerFor is useful to create http.Handlers for custom
82
+// Gatherers, with non-default HandlerOpts, and/or with custom (or no)
83
+// instrumentation. Use the InstrumentMetricHandler function to apply the same
84
+// kind of instrumentation as it is used by the Handler function.
85
+func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler {
86
+	var (
87
+		inFlightSem chan struct{}
88
+		errCnt      = prometheus.NewCounterVec(
89
+			prometheus.CounterOpts{
90
+				Name: "promhttp_metric_handler_errors_total",
91
+				Help: "Total number of internal errors encountered by the promhttp metric handler.",
92
+			},
93
+			[]string{"cause"},
94
+		)
95
+	)
96
+
97
+	if opts.MaxRequestsInFlight > 0 {
98
+		inFlightSem = make(chan struct{}, opts.MaxRequestsInFlight)
99
+	}
100
+	if opts.Registry != nil {
101
+		// Initialize all possibilites that can occur below.
102
+		errCnt.WithLabelValues("gathering")
103
+		errCnt.WithLabelValues("encoding")
104
+		if err := opts.Registry.Register(errCnt); err != nil {
105
+			if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
106
+				errCnt = are.ExistingCollector.(*prometheus.CounterVec)
107
+			} else {
108
+				panic(err)
109
+			}
110
+		}
111
+	}
112
+
113
+	h := http.HandlerFunc(func(rsp http.ResponseWriter, req *http.Request) {
114
+		if inFlightSem != nil {
115
+			select {
116
+			case inFlightSem <- struct{}{}: // All good, carry on.
117
+				defer func() { <-inFlightSem }()
118
+			default:
119
+				http.Error(rsp, fmt.Sprintf(
120
+					"Limit of concurrent requests reached (%d), try again later.", opts.MaxRequestsInFlight,
121
+				), http.StatusServiceUnavailable)
122
+				return
123
+			}
124
+		}
125
+		mfs, err := reg.Gather()
126
+		if err != nil {
127
+			if opts.ErrorLog != nil {
128
+				opts.ErrorLog.Println("error gathering metrics:", err)
129
+			}
130
+			errCnt.WithLabelValues("gathering").Inc()
131
+			switch opts.ErrorHandling {
132
+			case PanicOnError:
133
+				panic(err)
134
+			case ContinueOnError:
135
+				if len(mfs) == 0 {
136
+					// Still report the error if no metrics have been gathered.
137
+					httpError(rsp, err)
138
+					return
139
+				}
140
+			case HTTPErrorOnError:
141
+				httpError(rsp, err)
142
+				return
143
+			}
144
+		}
145
+
146
+		contentType := expfmt.Negotiate(req.Header)
147
+		header := rsp.Header()
148
+		header.Set(contentTypeHeader, string(contentType))
149
+
150
+		w := io.Writer(rsp)
151
+		if !opts.DisableCompression && gzipAccepted(req.Header) {
152
+			header.Set(contentEncodingHeader, "gzip")
153
+			gz := gzipPool.Get().(*gzip.Writer)
154
+			defer gzipPool.Put(gz)
155
+
156
+			gz.Reset(w)
157
+			defer gz.Close()
158
+
159
+			w = gz
160
+		}
161
+
162
+		enc := expfmt.NewEncoder(w, contentType)
163
+
164
+		var lastErr error
165
+		for _, mf := range mfs {
166
+			if err := enc.Encode(mf); err != nil {
167
+				lastErr = err
168
+				if opts.ErrorLog != nil {
169
+					opts.ErrorLog.Println("error encoding and sending metric family:", err)
170
+				}
171
+				errCnt.WithLabelValues("encoding").Inc()
172
+				switch opts.ErrorHandling {
173
+				case PanicOnError:
174
+					panic(err)
175
+				case ContinueOnError:
176
+					// Handled later.
177
+				case HTTPErrorOnError:
178
+					httpError(rsp, err)
179
+					return
180
+				}
181
+			}
182
+		}
183
+
184
+		if lastErr != nil {
185
+			httpError(rsp, lastErr)
186
+		}
187
+	})
188
+
189
+	if opts.Timeout <= 0 {
190
+		return h
191
+	}
192
+	return http.TimeoutHandler(h, opts.Timeout, fmt.Sprintf(
193
+		"Exceeded configured timeout of %v.\n",
194
+		opts.Timeout,
195
+	))
196
+}
197
+
198
+// InstrumentMetricHandler is usually used with an http.Handler returned by the
199
+// HandlerFor function. It instruments the provided http.Handler with two
200
+// metrics: A counter vector "promhttp_metric_handler_requests_total" to count
201
+// scrapes partitioned by HTTP status code, and a gauge
202
+// "promhttp_metric_handler_requests_in_flight" to track the number of
203
+// simultaneous scrapes. This function idempotently registers collectors for
204
+// both metrics with the provided Registerer. It panics if the registration
205
+// fails. The provided metrics are useful to see how many scrapes hit the
206
+// monitored target (which could be from different Prometheus servers or other
207
+// scrapers), and how often they overlap (which would result in more than one
208
+// scrape in flight at the same time). Note that the scrapes-in-flight gauge
209
+// will contain the scrape by which it is exposed, while the scrape counter will
210
+// only get incremented after the scrape is complete (as only then the status
211
+// code is known). For tracking scrape durations, use the
212
+// "scrape_duration_seconds" gauge created by the Prometheus server upon each
213
+// scrape.
214
+func InstrumentMetricHandler(reg prometheus.Registerer, handler http.Handler) http.Handler {
215
+	cnt := prometheus.NewCounterVec(
216
+		prometheus.CounterOpts{
217
+			Name: "promhttp_metric_handler_requests_total",
218
+			Help: "Total number of scrapes by HTTP status code.",
219
+		},
220
+		[]string{"code"},
221
+	)
222
+	// Initialize the most likely HTTP status codes.
223
+	cnt.WithLabelValues("200")
224
+	cnt.WithLabelValues("500")
225
+	cnt.WithLabelValues("503")
226
+	if err := reg.Register(cnt); err != nil {
227
+		if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
228
+			cnt = are.ExistingCollector.(*prometheus.CounterVec)
229
+		} else {
230
+			panic(err)
231
+		}
232
+	}
233
+
234
+	gge := prometheus.NewGauge(prometheus.GaugeOpts{
235
+		Name: "promhttp_metric_handler_requests_in_flight",
236
+		Help: "Current number of scrapes being served.",
237
+	})
238
+	if err := reg.Register(gge); err != nil {
239
+		if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
240
+			gge = are.ExistingCollector.(prometheus.Gauge)
241
+		} else {
242
+			panic(err)
243
+		}
244
+	}
245
+
246
+	return InstrumentHandlerCounter(cnt, InstrumentHandlerInFlight(gge, handler))
247
+}
248
+
249
+// HandlerErrorHandling defines how a Handler serving metrics will handle
250
+// errors.
251
+type HandlerErrorHandling int
252
+
253
+// These constants cause handlers serving metrics to behave as described if
254
+// errors are encountered.
255
+const (
256
+	// Serve an HTTP status code 500 upon the first error
257
+	// encountered. Report the error message in the body.
258
+	HTTPErrorOnError HandlerErrorHandling = iota
259
+	// Ignore errors and try to serve as many metrics as possible.  However,
260
+	// if no metrics can be served, serve an HTTP status code 500 and the
261
+	// last error message in the body. Only use this in deliberate "best
262
+	// effort" metrics collection scenarios. In this case, it is highly
263
+	// recommended to provide other means of detecting errors: By setting an
264
+	// ErrorLog in HandlerOpts, the errors are logged. By providing a
265
+	// Registry in HandlerOpts, the exposed metrics include an error counter
266
+	// "promhttp_metric_handler_errors_total", which can be used for
267
+	// alerts.
268
+	ContinueOnError
269
+	// Panic upon the first error encountered (useful for "crash only" apps).
270
+	PanicOnError
271
+)
272
+
273
+// Logger is the minimal interface HandlerOpts needs for logging. Note that
274
+// log.Logger from the standard library implements this interface, and it is
275
+// easy to implement by custom loggers, if they don't do so already anyway.
276
+type Logger interface {
277
+	Println(v ...interface{})
278
+}
279
+
280
+// HandlerOpts specifies options how to serve metrics via an http.Handler. The
281
+// zero value of HandlerOpts is a reasonable default.
282
+type HandlerOpts struct {
283
+	// ErrorLog specifies an optional logger for errors collecting and
284
+	// serving metrics. If nil, errors are not logged at all.
285
+	ErrorLog Logger
286
+	// ErrorHandling defines how errors are handled. Note that errors are
287
+	// logged regardless of the configured ErrorHandling provided ErrorLog
288
+	// is not nil.
289
+	ErrorHandling HandlerErrorHandling
290
+	// If Registry is not nil, it is used to register a metric
291
+	// "promhttp_metric_handler_errors_total", partitioned by "cause". A
292
+	// failed registration causes a panic. Note that this error counter is
293
+	// different from the instrumentation you get from the various
294
+	// InstrumentHandler... helpers. It counts errors that don't necessarily
295
+	// result in a non-2xx HTTP status code. There are two typical cases:
296
+	// (1) Encoding errors that only happen after streaming of the HTTP body
297
+	// has already started (and the status code 200 has been sent). This
298
+	// should only happen with custom collectors. (2) Collection errors with
299
+	// no effect on the HTTP status code because ErrorHandling is set to
300
+	// ContinueOnError.
301
+	Registry prometheus.Registerer
302
+	// If DisableCompression is true, the handler will never compress the
303
+	// response, even if requested by the client.
304
+	DisableCompression bool
305
+	// The number of concurrent HTTP requests is limited to
306
+	// MaxRequestsInFlight. Additional requests are responded to with 503
307
+	// Service Unavailable and a suitable message in the body. If
308
+	// MaxRequestsInFlight is 0 or negative, no limit is applied.
309
+	MaxRequestsInFlight int
310
+	// If handling a request takes longer than Timeout, it is responded to
311
+	// with 503 ServiceUnavailable and a suitable Message. No timeout is
312
+	// applied if Timeout is 0 or negative. Note that with the current
313
+	// implementation, reaching the timeout simply ends the HTTP requests as
314
+	// described above (and even that only if sending of the body hasn't
315
+	// started yet), while the bulk work of gathering all the metrics keeps
316
+	// running in the background (with the eventual result to be thrown
317
+	// away). Until the implementation is improved, it is recommended to
318
+	// implement a separate timeout in potentially slow Collectors.
319
+	Timeout time.Duration
320
+}
321
+
322
+// gzipAccepted returns whether the client will accept gzip-encoded content.
323
+func gzipAccepted(header http.Header) bool {
324
+	a := header.Get(acceptEncodingHeader)
325
+	parts := strings.Split(a, ",")
326
+	for _, part := range parts {
327
+		part = strings.TrimSpace(part)
328
+		if part == "gzip" || strings.HasPrefix(part, "gzip;") {
329
+			return true
330
+		}
331
+	}
332
+	return false
333
+}
334
+
335
+// httpError removes any content-encoding header and then calls http.Error with
336
+// the provided error and http.StatusInternalServerErrer. Error contents is
337
+// supposed to be uncompressed plain text. However, same as with a plain
338
+// http.Error, any header settings will be void if the header has already been
339
+// sent. The error message will still be written to the writer, but it will
340
+// probably be of limited use.
341
+func httpError(rsp http.ResponseWriter, err error) {
342
+	rsp.Header().Del(contentEncodingHeader)
343
+	http.Error(
344
+		rsp,
345
+		"An error has occurred while serving metrics:\n\n"+err.Error(),
346
+		http.StatusInternalServerError,
347
+	)
348
+}
0 349
new file mode 100644
... ...
@@ -0,0 +1,219 @@
0
+// Copyright 2017 The Prometheus Authors
1
+// Licensed under the Apache License, Version 2.0 (the "License");
2
+// you may not use this file except in compliance with the License.
3
+// You may obtain a copy of the License at
4
+//
5
+// http://www.apache.org/licenses/LICENSE-2.0
6
+//
7
+// Unless required by applicable law or agreed to in writing, software
8
+// distributed under the License is distributed on an "AS IS" BASIS,
9
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+// See the License for the specific language governing permissions and
11
+// limitations under the License.
12
+
13
+package promhttp
14
+
15
+import (
16
+	"crypto/tls"
17
+	"net/http"
18
+	"net/http/httptrace"
19
+	"time"
20
+
21
+	"github.com/prometheus/client_golang/prometheus"
22
+)
23
+
24
+// The RoundTripperFunc type is an adapter to allow the use of ordinary
25
+// functions as RoundTrippers. If f is a function with the appropriate
26
+// signature, RountTripperFunc(f) is a RoundTripper that calls f.
27
+type RoundTripperFunc func(req *http.Request) (*http.Response, error)
28
+
29
+// RoundTrip implements the RoundTripper interface.
30
+func (rt RoundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error) {
31
+	return rt(r)
32
+}
33
+
34
+// InstrumentRoundTripperInFlight is a middleware that wraps the provided
35
+// http.RoundTripper. It sets the provided prometheus.Gauge to the number of
36
+// requests currently handled by the wrapped http.RoundTripper.
37
+//
38
+// See the example for ExampleInstrumentRoundTripperDuration for example usage.
39
+func InstrumentRoundTripperInFlight(gauge prometheus.Gauge, next http.RoundTripper) RoundTripperFunc {
40
+	return RoundTripperFunc(func(r *http.Request) (*http.Response, error) {
41
+		gauge.Inc()
42
+		defer gauge.Dec()
43
+		return next.RoundTrip(r)
44
+	})
45
+}
46
+
47
+// InstrumentRoundTripperCounter is a middleware that wraps the provided
48
+// http.RoundTripper to observe the request result with the provided CounterVec.
49
+// The CounterVec must have zero, one, or two non-const non-curried labels. For
50
+// those, the only allowed label names are "code" and "method". The function
51
+// panics otherwise. Partitioning of the CounterVec happens by HTTP status code
52
+// and/or HTTP method if the respective instance label names are present in the
53
+// CounterVec. For unpartitioned counting, use a CounterVec with zero labels.
54
+//
55
+// If the wrapped RoundTripper panics or returns a non-nil error, the Counter
56
+// is not incremented.
57
+//
58
+// See the example for ExampleInstrumentRoundTripperDuration for example usage.
59
+func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.RoundTripper) RoundTripperFunc {
60
+	code, method := checkLabels(counter)
61
+
62
+	return RoundTripperFunc(func(r *http.Request) (*http.Response, error) {
63
+		resp, err := next.RoundTrip(r)
64
+		if err == nil {
65
+			counter.With(labels(code, method, r.Method, resp.StatusCode)).Inc()
66
+		}
67
+		return resp, err
68
+	})
69
+}
70
+
71
+// InstrumentRoundTripperDuration is a middleware that wraps the provided
72
+// http.RoundTripper to observe the request duration with the provided
73
+// ObserverVec.  The ObserverVec must have zero, one, or two non-const
74
+// non-curried labels. For those, the only allowed label names are "code" and
75
+// "method". The function panics otherwise. The Observe method of the Observer
76
+// in the ObserverVec is called with the request duration in
77
+// seconds. Partitioning happens by HTTP status code and/or HTTP method if the
78
+// respective instance label names are present in the ObserverVec. For
79
+// unpartitioned observations, use an ObserverVec with zero labels. Note that
80
+// partitioning of Histograms is expensive and should be used judiciously.
81
+//
82
+// If the wrapped RoundTripper panics or returns a non-nil error, no values are
83
+// reported.
84
+//
85
+// Note that this method is only guaranteed to never observe negative durations
86
+// if used with Go1.9+.
87
+func InstrumentRoundTripperDuration(obs prometheus.ObserverVec, next http.RoundTripper) RoundTripperFunc {
88
+	code, method := checkLabels(obs)
89
+
90
+	return RoundTripperFunc(func(r *http.Request) (*http.Response, error) {
91
+		start := time.Now()
92
+		resp, err := next.RoundTrip(r)
93
+		if err == nil {
94
+			obs.With(labels(code, method, r.Method, resp.StatusCode)).Observe(time.Since(start).Seconds())
95
+		}
96
+		return resp, err
97
+	})
98
+}
99
+
100
+// InstrumentTrace is used to offer flexibility in instrumenting the available
101
+// httptrace.ClientTrace hook functions. Each function is passed a float64
102
+// representing the time in seconds since the start of the http request. A user
103
+// may choose to use separately buckets Histograms, or implement custom
104
+// instance labels on a per function basis.
105
+type InstrumentTrace struct {
106
+	GotConn              func(float64)
107
+	PutIdleConn          func(float64)
108
+	GotFirstResponseByte func(float64)
109
+	Got100Continue       func(float64)
110
+	DNSStart             func(float64)
111
+	DNSDone              func(float64)
112
+	ConnectStart         func(float64)
113
+	ConnectDone          func(float64)
114
+	TLSHandshakeStart    func(float64)
115
+	TLSHandshakeDone     func(float64)
116
+	WroteHeaders         func(float64)
117
+	Wait100Continue      func(float64)
118
+	WroteRequest         func(float64)
119
+}
120
+
121
+// InstrumentRoundTripperTrace is a middleware that wraps the provided
122
+// RoundTripper and reports times to hook functions provided in the
123
+// InstrumentTrace struct. Hook functions that are not present in the provided
124
+// InstrumentTrace struct are ignored. Times reported to the hook functions are
125
+// time since the start of the request. Only with Go1.9+, those times are
126
+// guaranteed to never be negative. (Earlier Go versions are not using a
127
+// monotonic clock.) Note that partitioning of Histograms is expensive and
128
+// should be used judiciously.
129
+//
130
+// For hook functions that receive an error as an argument, no observations are
131
+// made in the event of a non-nil error value.
132
+//
133
+// See the example for ExampleInstrumentRoundTripperDuration for example usage.
134
+func InstrumentRoundTripperTrace(it *InstrumentTrace, next http.RoundTripper) RoundTripperFunc {
135
+	return RoundTripperFunc(func(r *http.Request) (*http.Response, error) {
136
+		start := time.Now()
137
+
138
+		trace := &httptrace.ClientTrace{
139
+			GotConn: func(_ httptrace.GotConnInfo) {
140
+				if it.GotConn != nil {
141
+					it.GotConn(time.Since(start).Seconds())
142
+				}
143
+			},
144
+			PutIdleConn: func(err error) {
145
+				if err != nil {
146
+					return
147
+				}
148
+				if it.PutIdleConn != nil {
149
+					it.PutIdleConn(time.Since(start).Seconds())
150
+				}
151
+			},
152
+			DNSStart: func(_ httptrace.DNSStartInfo) {
153
+				if it.DNSStart != nil {
154
+					it.DNSStart(time.Since(start).Seconds())
155
+				}
156
+			},
157
+			DNSDone: func(_ httptrace.DNSDoneInfo) {
158
+				if it.DNSDone != nil {
159
+					it.DNSDone(time.Since(start).Seconds())
160
+				}
161
+			},
162
+			ConnectStart: func(_, _ string) {
163
+				if it.ConnectStart != nil {
164
+					it.ConnectStart(time.Since(start).Seconds())
165
+				}
166
+			},
167
+			ConnectDone: func(_, _ string, err error) {
168
+				if err != nil {
169
+					return
170
+				}
171
+				if it.ConnectDone != nil {
172
+					it.ConnectDone(time.Since(start).Seconds())
173
+				}
174
+			},
175
+			GotFirstResponseByte: func() {
176
+				if it.GotFirstResponseByte != nil {
177
+					it.GotFirstResponseByte(time.Since(start).Seconds())
178
+				}
179
+			},
180
+			Got100Continue: func() {
181
+				if it.Got100Continue != nil {
182
+					it.Got100Continue(time.Since(start).Seconds())
183
+				}
184
+			},
185
+			TLSHandshakeStart: func() {
186
+				if it.TLSHandshakeStart != nil {
187
+					it.TLSHandshakeStart(time.Since(start).Seconds())
188
+				}
189
+			},
190
+			TLSHandshakeDone: func(_ tls.ConnectionState, err error) {
191
+				if err != nil {
192
+					return
193
+				}
194
+				if it.TLSHandshakeDone != nil {
195
+					it.TLSHandshakeDone(time.Since(start).Seconds())
196
+				}
197
+			},
198
+			WroteHeaders: func() {
199
+				if it.WroteHeaders != nil {
200
+					it.WroteHeaders(time.Since(start).Seconds())
201
+				}
202
+			},
203
+			Wait100Continue: func() {
204
+				if it.Wait100Continue != nil {
205
+					it.Wait100Continue(time.Since(start).Seconds())
206
+				}
207
+			},
208
+			WroteRequest: func(_ httptrace.WroteRequestInfo) {
209
+				if it.WroteRequest != nil {
210
+					it.WroteRequest(time.Since(start).Seconds())
211
+				}
212
+			},
213
+		}
214
+		r = r.WithContext(httptrace.WithClientTrace(r.Context(), trace))
215
+
216
+		return next.RoundTrip(r)
217
+	})
218
+}
0 219
new file mode 100644
... ...
@@ -0,0 +1,447 @@
0
+// Copyright 2017 The Prometheus Authors
1
+// Licensed under the Apache License, Version 2.0 (the "License");
2
+// you may not use this file except in compliance with the License.
3
+// You may obtain a copy of the License at
4
+//
5
+// http://www.apache.org/licenses/LICENSE-2.0
6
+//
7
+// Unless required by applicable law or agreed to in writing, software
8
+// distributed under the License is distributed on an "AS IS" BASIS,
9
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+// See the License for the specific language governing permissions and
11
+// limitations under the License.
12
+
13
+package promhttp
14
+
15
+import (
16
+	"errors"
17
+	"net/http"
18
+	"strconv"
19
+	"strings"
20
+	"time"
21
+
22
+	dto "github.com/prometheus/client_model/go"
23
+
24
+	"github.com/prometheus/client_golang/prometheus"
25
+)
26
+
27
+// magicString is used for the hacky label test in checkLabels. Remove once fixed.
28
+const magicString = "zZgWfBxLqvG8kc8IMv3POi2Bb0tZI3vAnBx+gBaFi9FyPzB/CzKUer1yufDa"
29
+
30
+// InstrumentHandlerInFlight is a middleware that wraps the provided
31
+// http.Handler. It sets the provided prometheus.Gauge to the number of
32
+// requests currently handled by the wrapped http.Handler.
33
+//
34
+// See the example for InstrumentHandlerDuration for example usage.
35
+func InstrumentHandlerInFlight(g prometheus.Gauge, next http.Handler) http.Handler {
36
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
37
+		g.Inc()
38
+		defer g.Dec()
39
+		next.ServeHTTP(w, r)
40
+	})
41
+}
42
+
43
+// InstrumentHandlerDuration is a middleware that wraps the provided
44
+// http.Handler to observe the request duration with the provided ObserverVec.
45
+// The ObserverVec must have zero, one, or two non-const non-curried labels. For
46
+// those, the only allowed label names are "code" and "method". The function
47
+// panics otherwise. The Observe method of the Observer in the ObserverVec is
48
+// called with the request duration in seconds. Partitioning happens by HTTP
49
+// status code and/or HTTP method if the respective instance label names are
50
+// present in the ObserverVec. For unpartitioned observations, use an
51
+// ObserverVec with zero labels. Note that partitioning of Histograms is
52
+// expensive and should be used judiciously.
53
+//
54
+// If the wrapped Handler does not set a status code, a status code of 200 is assumed.
55
+//
56
+// If the wrapped Handler panics, no values are reported.
57
+//
58
+// Note that this method is only guaranteed to never observe negative durations
59
+// if used with Go1.9+.
60
+func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler) http.HandlerFunc {
61
+	code, method := checkLabels(obs)
62
+
63
+	if code {
64
+		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
65
+			now := time.Now()
66
+			d := newDelegator(w, nil)
67
+			next.ServeHTTP(d, r)
68
+
69
+			obs.With(labels(code, method, r.Method, d.Status())).Observe(time.Since(now).Seconds())
70
+		})
71
+	}
72
+
73
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
74
+		now := time.Now()
75
+		next.ServeHTTP(w, r)
76
+		obs.With(labels(code, method, r.Method, 0)).Observe(time.Since(now).Seconds())
77
+	})
78
+}
79
+
80
+// InstrumentHandlerCounter is a middleware that wraps the provided http.Handler
81
+// to observe the request result with the provided CounterVec.  The CounterVec
82
+// must have zero, one, or two non-const non-curried labels. For those, the only
83
+// allowed label names are "code" and "method". The function panics
84
+// otherwise. Partitioning of the CounterVec happens by HTTP status code and/or
85
+// HTTP method if the respective instance label names are present in the
86
+// CounterVec. For unpartitioned counting, use a CounterVec with zero labels.
87
+//
88
+// If the wrapped Handler does not set a status code, a status code of 200 is assumed.
89
+//
90
+// If the wrapped Handler panics, the Counter is not incremented.
91
+//
92
+// See the example for InstrumentHandlerDuration for example usage.
93
+func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler) http.HandlerFunc {
94
+	code, method := checkLabels(counter)
95
+
96
+	if code {
97
+		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
98
+			d := newDelegator(w, nil)
99
+			next.ServeHTTP(d, r)
100
+			counter.With(labels(code, method, r.Method, d.Status())).Inc()
101
+		})
102
+	}
103
+
104
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
105
+		next.ServeHTTP(w, r)
106
+		counter.With(labels(code, method, r.Method, 0)).Inc()
107
+	})
108
+}
109
+
110
+// InstrumentHandlerTimeToWriteHeader is a middleware that wraps the provided
111
+// http.Handler to observe with the provided ObserverVec the request duration
112
+// until the response headers are written. The ObserverVec must have zero, one,
113
+// or two non-const non-curried labels. For those, the only allowed label names
114
+// are "code" and "method". The function panics otherwise. The Observe method of
115
+// the Observer in the ObserverVec is called with the request duration in
116
+// seconds. Partitioning happens by HTTP status code and/or HTTP method if the
117
+// respective instance label names are present in the ObserverVec. For
118
+// unpartitioned observations, use an ObserverVec with zero labels. Note that
119
+// partitioning of Histograms is expensive and should be used judiciously.
120
+//
121
+// If the wrapped Handler panics before calling WriteHeader, no value is
122
+// reported.
123
+//
124
+// Note that this method is only guaranteed to never observe negative durations
125
+// if used with Go1.9+.
126
+//
127
+// See the example for InstrumentHandlerDuration for example usage.
128
+func InstrumentHandlerTimeToWriteHeader(obs prometheus.ObserverVec, next http.Handler) http.HandlerFunc {
129
+	code, method := checkLabels(obs)
130
+
131
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
132
+		now := time.Now()
133
+		d := newDelegator(w, func(status int) {
134
+			obs.With(labels(code, method, r.Method, status)).Observe(time.Since(now).Seconds())
135
+		})
136
+		next.ServeHTTP(d, r)
137
+	})
138
+}
139
+
140
+// InstrumentHandlerRequestSize is a middleware that wraps the provided
141
+// http.Handler to observe the request size with the provided ObserverVec.  The
142
+// ObserverVec must have zero, one, or two non-const non-curried labels. For
143
+// those, the only allowed label names are "code" and "method". The function
144
+// panics otherwise. The Observe method of the Observer in the ObserverVec is
145
+// called with the request size in bytes. Partitioning happens by HTTP status
146
+// code and/or HTTP method if the respective instance label names are present in
147
+// the ObserverVec. For unpartitioned observations, use an ObserverVec with zero
148
+// labels. Note that partitioning of Histograms is expensive and should be used
149
+// judiciously.
150
+//
151
+// If the wrapped Handler does not set a status code, a status code of 200 is assumed.
152
+//
153
+// If the wrapped Handler panics, no values are reported.
154
+//
155
+// See the example for InstrumentHandlerDuration for example usage.
156
+func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler) http.HandlerFunc {
157
+	code, method := checkLabels(obs)
158
+
159
+	if code {
160
+		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
161
+			d := newDelegator(w, nil)
162
+			next.ServeHTTP(d, r)
163
+			size := computeApproximateRequestSize(r)
164
+			obs.With(labels(code, method, r.Method, d.Status())).Observe(float64(size))
165
+		})
166
+	}
167
+
168
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
169
+		next.ServeHTTP(w, r)
170
+		size := computeApproximateRequestSize(r)
171
+		obs.With(labels(code, method, r.Method, 0)).Observe(float64(size))
172
+	})
173
+}
174
+
175
+// InstrumentHandlerResponseSize is a middleware that wraps the provided
176
+// http.Handler to observe the response size with the provided ObserverVec.  The
177
+// ObserverVec must have zero, one, or two non-const non-curried labels. For
178
+// those, the only allowed label names are "code" and "method". The function
179
+// panics otherwise. The Observe method of the Observer in the ObserverVec is
180
+// called with the response size in bytes. Partitioning happens by HTTP status
181
+// code and/or HTTP method if the respective instance label names are present in
182
+// the ObserverVec. For unpartitioned observations, use an ObserverVec with zero
183
+// labels. Note that partitioning of Histograms is expensive and should be used
184
+// judiciously.
185
+//
186
+// If the wrapped Handler does not set a status code, a status code of 200 is assumed.
187
+//
188
+// If the wrapped Handler panics, no values are reported.
189
+//
190
+// See the example for InstrumentHandlerDuration for example usage.
191
+func InstrumentHandlerResponseSize(obs prometheus.ObserverVec, next http.Handler) http.Handler {
192
+	code, method := checkLabels(obs)
193
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
194
+		d := newDelegator(w, nil)
195
+		next.ServeHTTP(d, r)
196
+		obs.With(labels(code, method, r.Method, d.Status())).Observe(float64(d.Written()))
197
+	})
198
+}
199
+
200
+func checkLabels(c prometheus.Collector) (code bool, method bool) {
201
+	// TODO(beorn7): Remove this hacky way to check for instance labels
202
+	// once Descriptors can have their dimensionality queried.
203
+	var (
204
+		desc *prometheus.Desc
205
+		m    prometheus.Metric
206
+		pm   dto.Metric
207
+		lvs  []string
208
+	)
209
+
210
+	// Get the Desc from the Collector.
211
+	descc := make(chan *prometheus.Desc, 1)
212
+	c.Describe(descc)
213
+
214
+	select {
215
+	case desc = <-descc:
216
+	default:
217
+		panic("no description provided by collector")
218
+	}
219
+	select {
220
+	case <-descc:
221
+		panic("more than one description provided by collector")
222
+	default:
223
+	}
224
+
225
+	close(descc)
226
+
227
+	// Create a ConstMetric with the Desc. Since we don't know how many
228
+	// variable labels there are, try for as long as it needs.
229
+	for err := errors.New("dummy"); err != nil; lvs = append(lvs, magicString) {
230
+		m, err = prometheus.NewConstMetric(desc, prometheus.UntypedValue, 0, lvs...)
231
+	}
232
+
233
+	// Write out the metric into a proto message and look at the labels.
234
+	// If the value is not the magicString, it is a constLabel, which doesn't interest us.
235
+	// If the label is curried, it doesn't interest us.
236
+	// In all other cases, only "code" or "method" is allowed.
237
+	if err := m.Write(&pm); err != nil {
238
+		panic("error checking metric for labels")
239
+	}
240
+	for _, label := range pm.Label {
241
+		name, value := label.GetName(), label.GetValue()
242
+		if value != magicString || isLabelCurried(c, name) {
243
+			continue
244
+		}
245
+		switch name {
246
+		case "code":
247
+			code = true
248
+		case "method":
249
+			method = true
250
+		default:
251
+			panic("metric partitioned with non-supported labels")
252
+		}
253
+	}
254
+	return
255
+}
256
+
257
+func isLabelCurried(c prometheus.Collector, label string) bool {
258
+	// This is even hackier than the label test above.
259
+	// We essentially try to curry again and see if it works.
260
+	// But for that, we need to type-convert to the two
261
+	// types we use here, ObserverVec or *CounterVec.
262
+	switch v := c.(type) {
263
+	case *prometheus.CounterVec:
264
+		if _, err := v.CurryWith(prometheus.Labels{label: "dummy"}); err == nil {
265
+			return false
266
+		}
267
+	case prometheus.ObserverVec:
268
+		if _, err := v.CurryWith(prometheus.Labels{label: "dummy"}); err == nil {
269
+			return false
270
+		}
271
+	default:
272
+		panic("unsupported metric vec type")
273
+	}
274
+	return true
275
+}
276
+
277
+// emptyLabels is a one-time allocation for non-partitioned metrics to avoid
278
+// unnecessary allocations on each request.
279
+var emptyLabels = prometheus.Labels{}
280
+
281
+func labels(code, method bool, reqMethod string, status int) prometheus.Labels {
282
+	if !(code || method) {
283
+		return emptyLabels
284
+	}
285
+	labels := prometheus.Labels{}
286
+
287
+	if code {
288
+		labels["code"] = sanitizeCode(status)
289
+	}
290
+	if method {
291
+		labels["method"] = sanitizeMethod(reqMethod)
292
+	}
293
+
294
+	return labels
295
+}
296
+
297
+func computeApproximateRequestSize(r *http.Request) int {
298
+	s := 0
299
+	if r.URL != nil {
300
+		s += len(r.URL.String())
301
+	}
302
+
303
+	s += len(r.Method)
304
+	s += len(r.Proto)
305
+	for name, values := range r.Header {
306
+		s += len(name)
307
+		for _, value := range values {
308
+			s += len(value)
309
+		}
310
+	}
311
+	s += len(r.Host)
312
+
313
+	// N.B. r.Form and r.MultipartForm are assumed to be included in r.URL.
314
+
315
+	if r.ContentLength != -1 {
316
+		s += int(r.ContentLength)
317
+	}
318
+	return s
319
+}
320
+
321
+func sanitizeMethod(m string) string {
322
+	switch m {
323
+	case "GET", "get":
324
+		return "get"
325
+	case "PUT", "put":
326
+		return "put"
327
+	case "HEAD", "head":
328
+		return "head"
329
+	case "POST", "post":
330
+		return "post"
331
+	case "DELETE", "delete":
332
+		return "delete"
333
+	case "CONNECT", "connect":
334
+		return "connect"
335
+	case "OPTIONS", "options":
336
+		return "options"
337
+	case "NOTIFY", "notify":
338
+		return "notify"
339
+	default:
340
+		return strings.ToLower(m)
341
+	}
342
+}
343
+
344
+// If the wrapped http.Handler has not set a status code, i.e. the value is
345
+// currently 0, santizeCode will return 200, for consistency with behavior in
346
+// the stdlib.
347
+func sanitizeCode(s int) string {
348
+	switch s {
349
+	case 100:
350
+		return "100"
351
+	case 101:
352
+		return "101"
353
+
354
+	case 200, 0:
355
+		return "200"
356
+	case 201:
357
+		return "201"
358
+	case 202:
359
+		return "202"
360
+	case 203:
361
+		return "203"
362
+	case 204:
363
+		return "204"
364
+	case 205:
365
+		return "205"
366
+	case 206:
367
+		return "206"
368
+
369
+	case 300:
370
+		return "300"
371
+	case 301:
372
+		return "301"
373
+	case 302:
374
+		return "302"
375
+	case 304:
376
+		return "304"
377
+	case 305:
378
+		return "305"
379
+	case 307:
380
+		return "307"
381
+
382
+	case 400:
383
+		return "400"
384
+	case 401:
385
+		return "401"
386
+	case 402:
387
+		return "402"
388
+	case 403:
389
+		return "403"
390
+	case 404:
391
+		return "404"
392
+	case 405:
393
+		return "405"
394
+	case 406:
395
+		return "406"
396
+	case 407:
397
+		return "407"
398
+	case 408:
399
+		return "408"
400
+	case 409:
401
+		return "409"
402
+	case 410:
403
+		return "410"
404
+	case 411:
405
+		return "411"
406
+	case 412:
407
+		return "412"
408
+	case 413:
409
+		return "413"
410
+	case 414:
411
+		return "414"
412
+	case 415:
413
+		return "415"
414
+	case 416:
415
+		return "416"
416
+	case 417:
417
+		return "417"
418
+	case 418:
419
+		return "418"
420
+
421
+	case 500:
422
+		return "500"
423
+	case 501:
424
+		return "501"
425
+	case 502:
426
+		return "502"
427
+	case 503:
428
+		return "503"
429
+	case 504:
430
+		return "504"
431
+	case 505:
432
+		return "505"
433
+
434
+	case 428:
435
+		return "428"
436
+	case 429:
437
+		return "429"
438
+	case 431:
439
+		return "431"
440
+	case 511:
441
+		return "511"
442
+
443
+	default:
444
+		return strconv.Itoa(s)
445
+	}
446
+}
... ...
@@ -15,15 +15,22 @@ package prometheus
15 15
 
16 16
 import (
17 17
 	"bytes"
18
-	"errors"
19 18
 	"fmt"
19
+	"io/ioutil"
20 20
 	"os"
21
+	"path/filepath"
22
+	"runtime"
21 23
 	"sort"
24
+	"strings"
22 25
 	"sync"
26
+	"unicode/utf8"
23 27
 
24 28
 	"github.com/golang/protobuf/proto"
29
+	"github.com/prometheus/common/expfmt"
25 30
 
26 31
 	dto "github.com/prometheus/client_model/go"
32
+
33
+	"github.com/prometheus/client_golang/prometheus/internal"
27 34
 )
28 35
 
29 36
 const (
... ...
@@ -35,13 +42,14 @@ const (
35 35
 // DefaultRegisterer and DefaultGatherer are the implementations of the
36 36
 // Registerer and Gatherer interface a number of convenience functions in this
37 37
 // package act on. Initially, both variables point to the same Registry, which
38
-// has a process collector (see NewProcessCollector) and a Go collector (see
39
-// NewGoCollector) already registered. This approach to keep default instances
40
-// as global state mirrors the approach of other packages in the Go standard
41
-// library. Note that there are caveats. Change the variables with caution and
42
-// only if you understand the consequences. Users who want to avoid global state
43
-// altogether should not use the convenience function and act on custom
44
-// instances instead.
38
+// has a process collector (currently on Linux only, see NewProcessCollector)
39
+// and a Go collector (see NewGoCollector, in particular the note about
40
+// stop-the-world implication with Go versions older than 1.9) already
41
+// registered. This approach to keep default instances as global state mirrors
42
+// the approach of other packages in the Go standard library. Note that there
43
+// are caveats. Change the variables with caution and only if you understand the
44
+// consequences. Users who want to avoid global state altogether should not use
45
+// the convenience functions and act on custom instances instead.
45 46
 var (
46 47
 	defaultRegistry              = NewRegistry()
47 48
 	DefaultRegisterer Registerer = defaultRegistry
... ...
@@ -49,7 +57,7 @@ var (
49 49
 )
50 50
 
51 51
 func init() {
52
-	MustRegister(NewProcessCollector(os.Getpid(), ""))
52
+	MustRegister(NewProcessCollector(ProcessCollectorOpts{}))
53 53
 	MustRegister(NewGoCollector())
54 54
 }
55 55
 
... ...
@@ -65,7 +73,8 @@ func NewRegistry() *Registry {
65 65
 
66 66
 // NewPedanticRegistry returns a registry that checks during collection if each
67 67
 // collected Metric is consistent with its reported Desc, and if the Desc has
68
-// actually been registered with the registry.
68
+// actually been registered with the registry. Unchecked Collectors (those whose
69
+// Describe methed does not yield any descriptors) are excluded from the check.
69 70
 //
70 71
 // Usually, a Registry will be happy as long as the union of all collected
71 72
 // Metrics is consistent and valid even if some metrics are not consistent with
... ...
@@ -80,7 +89,7 @@ func NewPedanticRegistry() *Registry {
80 80
 
81 81
 // Registerer is the interface for the part of a registry in charge of
82 82
 // registering and unregistering. Users of custom registries should use
83
-// Registerer as type for registration purposes (rather then the Registry type
83
+// Registerer as type for registration purposes (rather than the Registry type
84 84
 // directly). In that way, they are free to use custom Registerer implementation
85 85
 // (e.g. for testing purposes).
86 86
 type Registerer interface {
... ...
@@ -95,8 +104,13 @@ type Registerer interface {
95 95
 	// returned error is an instance of AlreadyRegisteredError, which
96 96
 	// contains the previously registered Collector.
97 97
 	//
98
-	// It is in general not safe to register the same Collector multiple
99
-	// times concurrently.
98
+	// A Collector whose Describe method does not yield any Desc is treated
99
+	// as unchecked. Registration will always succeed. No check for
100
+	// re-registering (see previous paragraph) is performed. Thus, the
101
+	// caller is responsible for not double-registering the same unchecked
102
+	// Collector, and for providing a Collector that will not cause
103
+	// inconsistent metrics on collection. (This would lead to scrape
104
+	// errors.)
100 105
 	Register(Collector) error
101 106
 	// MustRegister works like Register but registers any number of
102 107
 	// Collectors and panics upon the first registration that causes an
... ...
@@ -105,7 +119,9 @@ type Registerer interface {
105 105
 	// Unregister unregisters the Collector that equals the Collector passed
106 106
 	// in as an argument.  (Two Collectors are considered equal if their
107 107
 	// Describe method yields the same set of descriptors.) The function
108
-	// returns whether a Collector was unregistered.
108
+	// returns whether a Collector was unregistered. Note that an unchecked
109
+	// Collector cannot be unregistered (as its Describe method does not
110
+	// yield any descriptor).
109 111
 	//
110 112
 	// Note that even after unregistering, it will not be possible to
111 113
 	// register a new Collector that is inconsistent with the unregistered
... ...
@@ -123,15 +139,23 @@ type Registerer interface {
123 123
 type Gatherer interface {
124 124
 	// Gather calls the Collect method of the registered Collectors and then
125 125
 	// gathers the collected metrics into a lexicographically sorted slice
126
-	// of MetricFamily protobufs. Even if an error occurs, Gather attempts
127
-	// to gather as many metrics as possible. Hence, if a non-nil error is
128
-	// returned, the returned MetricFamily slice could be nil (in case of a
129
-	// fatal error that prevented any meaningful metric collection) or
130
-	// contain a number of MetricFamily protobufs, some of which might be
131
-	// incomplete, and some might be missing altogether. The returned error
132
-	// (which might be a MultiError) explains the details. In scenarios
133
-	// where complete collection is critical, the returned MetricFamily
134
-	// protobufs should be disregarded if the returned error is non-nil.
126
+	// of uniquely named MetricFamily protobufs. Gather ensures that the
127
+	// returned slice is valid and self-consistent so that it can be used
128
+	// for valid exposition. As an exception to the strict consistency
129
+	// requirements described for metric.Desc, Gather will tolerate
130
+	// different sets of label names for metrics of the same metric family.
131
+	//
132
+	// Even if an error occurs, Gather attempts to gather as many metrics as
133
+	// possible. Hence, if a non-nil error is returned, the returned
134
+	// MetricFamily slice could be nil (in case of a fatal error that
135
+	// prevented any meaningful metric collection) or contain a number of
136
+	// MetricFamily protobufs, some of which might be incomplete, and some
137
+	// might be missing altogether. The returned error (which might be a
138
+	// MultiError) explains the details. Note that this is mostly useful for
139
+	// debugging purposes. If the gathered protobufs are to be used for
140
+	// exposition in actual monitoring, it is almost always better to not
141
+	// expose an incomplete result and instead disregard the returned
142
+	// MetricFamily protobufs in case the returned error is non-nil.
135 143
 	Gather() ([]*dto.MetricFamily, error)
136 144
 }
137 145
 
... ...
@@ -152,38 +176,6 @@ func MustRegister(cs ...Collector) {
152 152
 	DefaultRegisterer.MustRegister(cs...)
153 153
 }
154 154
 
155
-// RegisterOrGet registers the provided Collector with the DefaultRegisterer and
156
-// returns the Collector, unless an equal Collector was registered before, in
157
-// which case that Collector is returned.
158
-//
159
-// Deprecated: RegisterOrGet is merely a convenience function for the
160
-// implementation as described in the documentation for
161
-// AlreadyRegisteredError. As the use case is relatively rare, this function
162
-// will be removed in a future version of this package to clean up the
163
-// namespace.
164
-func RegisterOrGet(c Collector) (Collector, error) {
165
-	if err := Register(c); err != nil {
166
-		if are, ok := err.(AlreadyRegisteredError); ok {
167
-			return are.ExistingCollector, nil
168
-		}
169
-		return nil, err
170
-	}
171
-	return c, nil
172
-}
173
-
174
-// MustRegisterOrGet behaves like RegisterOrGet but panics instead of returning
175
-// an error.
176
-//
177
-// Deprecated: This is deprecated for the same reason RegisterOrGet is. See
178
-// there for details.
179
-func MustRegisterOrGet(c Collector) Collector {
180
-	c, err := RegisterOrGet(c)
181
-	if err != nil {
182
-		panic(err)
183
-	}
184
-	return c
185
-}
186
-
187 155
 // Unregister removes the registration of the provided Collector from the
188 156
 // DefaultRegisterer.
189 157
 //
... ...
@@ -201,25 +193,6 @@ func (gf GathererFunc) Gather() ([]*dto.MetricFamily, error) {
201 201
 	return gf()
202 202
 }
203 203
 
204
-// SetMetricFamilyInjectionHook replaces the DefaultGatherer with one that
205
-// gathers from the previous DefaultGatherers but then merges the MetricFamily
206
-// protobufs returned from the provided hook function with the MetricFamily
207
-// protobufs returned from the original DefaultGatherer.
208
-//
209
-// Deprecated: This function manipulates the DefaultGatherer variable. Consider
210
-// the implications, i.e. don't do this concurrently with any uses of the
211
-// DefaultGatherer. In the rare cases where you need to inject MetricFamily
212
-// protobufs directly, it is recommended to use a custom Registry and combine it
213
-// with a custom Gatherer using the Gatherers type (see
214
-// there). SetMetricFamilyInjectionHook only exists for compatibility reasons
215
-// with previous versions of this package.
216
-func SetMetricFamilyInjectionHook(hook func() []*dto.MetricFamily) {
217
-	DefaultGatherer = Gatherers{
218
-		DefaultGatherer,
219
-		GathererFunc(func() ([]*dto.MetricFamily, error) { return hook(), nil }),
220
-	}
221
-}
222
-
223 204
 // AlreadyRegisteredError is returned by the Register method if the Collector to
224 205
 // be registered has already been registered before, or a different Collector
225 206
 // that collects the same metrics has been registered before. Registration fails
... ...
@@ -252,6 +225,13 @@ func (errs MultiError) Error() string {
252 252
 	return buf.String()
253 253
 }
254 254
 
255
+// Append appends the provided error if it is not nil.
256
+func (errs *MultiError) Append(err error) {
257
+	if err != nil {
258
+		*errs = append(*errs, err)
259
+	}
260
+}
261
+
255 262
 // MaybeUnwrap returns nil if len(errs) is 0. It returns the first and only
256 263
 // contained error as error if len(errs is 1). In all other cases, it returns
257 264
 // the MultiError directly. This is helpful for returning a MultiError in a way
... ...
@@ -276,6 +256,7 @@ type Registry struct {
276 276
 	collectorsByID        map[uint64]Collector // ID is a hash of the descIDs.
277 277
 	descIDs               map[uint64]struct{}
278 278
 	dimHashesByName       map[string]uint64
279
+	uncheckedCollectors   []Collector
279 280
 	pedanticChecksEnabled bool
280 281
 }
281 282
 
... ...
@@ -293,8 +274,13 @@ func (r *Registry) Register(c Collector) error {
293 293
 		close(descChan)
294 294
 	}()
295 295
 	r.mtx.Lock()
296
-	defer r.mtx.Unlock()
297
-	// Coduct various tests...
296
+	defer func() {
297
+		// Drain channel in case of premature return to not leak a goroutine.
298
+		for range descChan {
299
+		}
300
+		r.mtx.Unlock()
301
+	}()
302
+	// Conduct various tests...
298 303
 	for desc := range descChan {
299 304
 
300 305
 		// Is the descriptor valid at all?
... ...
@@ -333,9 +319,10 @@ func (r *Registry) Register(c Collector) error {
333 333
 			}
334 334
 		}
335 335
 	}
336
-	// Did anything happen at all?
336
+	// A Collector yielding no Desc at all is considered unchecked.
337 337
 	if len(newDescIDs) == 0 {
338
-		return errors.New("collector has no descriptors")
338
+		r.uncheckedCollectors = append(r.uncheckedCollectors, c)
339
+		return nil
339 340
 	}
340 341
 	if existing, exists := r.collectorsByID[collectorID]; exists {
341 342
 		return AlreadyRegisteredError{
... ...
@@ -409,31 +396,25 @@ func (r *Registry) MustRegister(cs ...Collector) {
409 409
 // Gather implements Gatherer.
410 410
 func (r *Registry) Gather() ([]*dto.MetricFamily, error) {
411 411
 	var (
412
-		metricChan        = make(chan Metric, capMetricChan)
413
-		metricHashes      = map[uint64]struct{}{}
414
-		dimHashes         = map[string]uint64{}
415
-		wg                sync.WaitGroup
416
-		errs              MultiError          // The collected errors to return in the end.
417
-		registeredDescIDs map[uint64]struct{} // Only used for pedantic checks
412
+		checkedMetricChan   = make(chan Metric, capMetricChan)
413
+		uncheckedMetricChan = make(chan Metric, capMetricChan)
414
+		metricHashes        = map[uint64]struct{}{}
415
+		wg                  sync.WaitGroup
416
+		errs                MultiError          // The collected errors to return in the end.
417
+		registeredDescIDs   map[uint64]struct{} // Only used for pedantic checks
418 418
 	)
419 419
 
420 420
 	r.mtx.RLock()
421
+	goroutineBudget := len(r.collectorsByID) + len(r.uncheckedCollectors)
421 422
 	metricFamiliesByName := make(map[string]*dto.MetricFamily, len(r.dimHashesByName))
422
-
423
-	// Scatter.
424
-	// (Collectors could be complex and slow, so we call them all at once.)
425
-	wg.Add(len(r.collectorsByID))
426
-	go func() {
427
-		wg.Wait()
428
-		close(metricChan)
429
-	}()
423
+	checkedCollectors := make(chan Collector, len(r.collectorsByID))
424
+	uncheckedCollectors := make(chan Collector, len(r.uncheckedCollectors))
430 425
 	for _, collector := range r.collectorsByID {
431
-		go func(collector Collector) {
432
-			defer wg.Done()
433
-			collector.Collect(metricChan)
434
-		}(collector)
426
+		checkedCollectors <- collector
427
+	}
428
+	for _, collector := range r.uncheckedCollectors {
429
+		uncheckedCollectors <- collector
435 430
 	}
436
-
437 431
 	// In case pedantic checks are enabled, we have to copy the map before
438 432
 	// giving up the RLock.
439 433
 	if r.pedanticChecksEnabled {
... ...
@@ -442,133 +423,264 @@ func (r *Registry) Gather() ([]*dto.MetricFamily, error) {
442 442
 			registeredDescIDs[id] = struct{}{}
443 443
 		}
444 444
 	}
445
-
446 445
 	r.mtx.RUnlock()
447 446
 
448
-	// Drain metricChan in case of premature return.
447
+	wg.Add(goroutineBudget)
448
+
449
+	collectWorker := func() {
450
+		for {
451
+			select {
452
+			case collector := <-checkedCollectors:
453
+				collector.Collect(checkedMetricChan)
454
+			case collector := <-uncheckedCollectors:
455
+				collector.Collect(uncheckedMetricChan)
456
+			default:
457
+				return
458
+			}
459
+			wg.Done()
460
+		}
461
+	}
462
+
463
+	// Start the first worker now to make sure at least one is running.
464
+	go collectWorker()
465
+	goroutineBudget--
466
+
467
+	// Close checkedMetricChan and uncheckedMetricChan once all collectors
468
+	// are collected.
469
+	go func() {
470
+		wg.Wait()
471
+		close(checkedMetricChan)
472
+		close(uncheckedMetricChan)
473
+	}()
474
+
475
+	// Drain checkedMetricChan and uncheckedMetricChan in case of premature return.
449 476
 	defer func() {
450
-		for _ = range metricChan {
477
+		if checkedMetricChan != nil {
478
+			for range checkedMetricChan {
479
+			}
480
+		}
481
+		if uncheckedMetricChan != nil {
482
+			for range uncheckedMetricChan {
483
+			}
451 484
 		}
452 485
 	}()
453 486
 
454
-	// Gather.
455
-	for metric := range metricChan {
456
-		// This could be done concurrently, too, but it required locking
457
-		// of metricFamiliesByName (and of metricHashes if checks are
458
-		// enabled). Most likely not worth it.
459
-		desc := metric.Desc()
460
-		dtoMetric := &dto.Metric{}
461
-		if err := metric.Write(dtoMetric); err != nil {
462
-			errs = append(errs, fmt.Errorf(
463
-				"error collecting metric %v: %s", desc, err,
487
+	// Copy the channel references so we can nil them out later to remove
488
+	// them from the select statements below.
489
+	cmc := checkedMetricChan
490
+	umc := uncheckedMetricChan
491
+
492
+	for {
493
+		select {
494
+		case metric, ok := <-cmc:
495
+			if !ok {
496
+				cmc = nil
497
+				break
498
+			}
499
+			errs.Append(processMetric(
500
+				metric, metricFamiliesByName,
501
+				metricHashes,
502
+				registeredDescIDs,
464 503
 			))
465
-			continue
466
-		}
467
-		metricFamily, ok := metricFamiliesByName[desc.fqName]
468
-		if ok {
469
-			if metricFamily.GetHelp() != desc.help {
470
-				errs = append(errs, fmt.Errorf(
471
-					"collected metric %s %s has help %q but should have %q",
472
-					desc.fqName, dtoMetric, desc.help, metricFamily.GetHelp(),
473
-				))
474
-				continue
504
+		case metric, ok := <-umc:
505
+			if !ok {
506
+				umc = nil
507
+				break
475 508
 			}
476
-			// TODO(beorn7): Simplify switch once Desc has type.
477
-			switch metricFamily.GetType() {
478
-			case dto.MetricType_COUNTER:
479
-				if dtoMetric.Counter == nil {
480
-					errs = append(errs, fmt.Errorf(
481
-						"collected metric %s %s should be a Counter",
482
-						desc.fqName, dtoMetric,
483
-					))
484
-					continue
485
-				}
486
-			case dto.MetricType_GAUGE:
487
-				if dtoMetric.Gauge == nil {
488
-					errs = append(errs, fmt.Errorf(
489
-						"collected metric %s %s should be a Gauge",
490
-						desc.fqName, dtoMetric,
491
-					))
492
-					continue
493
-				}
494
-			case dto.MetricType_SUMMARY:
495
-				if dtoMetric.Summary == nil {
496
-					errs = append(errs, fmt.Errorf(
497
-						"collected metric %s %s should be a Summary",
498
-						desc.fqName, dtoMetric,
499
-					))
500
-					continue
501
-				}
502
-			case dto.MetricType_UNTYPED:
503
-				if dtoMetric.Untyped == nil {
504
-					errs = append(errs, fmt.Errorf(
505
-						"collected metric %s %s should be Untyped",
506
-						desc.fqName, dtoMetric,
509
+			errs.Append(processMetric(
510
+				metric, metricFamiliesByName,
511
+				metricHashes,
512
+				nil,
513
+			))
514
+		default:
515
+			if goroutineBudget <= 0 || len(checkedCollectors)+len(uncheckedCollectors) == 0 {
516
+				// All collectors are already being worked on or
517
+				// we have already as many goroutines started as
518
+				// there are collectors. Do the same as above,
519
+				// just without the default.
520
+				select {
521
+				case metric, ok := <-cmc:
522
+					if !ok {
523
+						cmc = nil
524
+						break
525
+					}
526
+					errs.Append(processMetric(
527
+						metric, metricFamiliesByName,
528
+						metricHashes,
529
+						registeredDescIDs,
507 530
 					))
508
-					continue
509
-				}
510
-			case dto.MetricType_HISTOGRAM:
511
-				if dtoMetric.Histogram == nil {
512
-					errs = append(errs, fmt.Errorf(
513
-						"collected metric %s %s should be a Histogram",
514
-						desc.fqName, dtoMetric,
531
+				case metric, ok := <-umc:
532
+					if !ok {
533
+						umc = nil
534
+						break
535
+					}
536
+					errs.Append(processMetric(
537
+						metric, metricFamiliesByName,
538
+						metricHashes,
539
+						nil,
515 540
 					))
516
-					continue
517 541
 				}
518
-			default:
519
-				panic("encountered MetricFamily with invalid type")
542
+				break
520 543
 			}
521
-		} else {
522
-			metricFamily = &dto.MetricFamily{}
523
-			metricFamily.Name = proto.String(desc.fqName)
524
-			metricFamily.Help = proto.String(desc.help)
525
-			// TODO(beorn7): Simplify switch once Desc has type.
526
-			switch {
527
-			case dtoMetric.Gauge != nil:
528
-				metricFamily.Type = dto.MetricType_GAUGE.Enum()
529
-			case dtoMetric.Counter != nil:
530
-				metricFamily.Type = dto.MetricType_COUNTER.Enum()
531
-			case dtoMetric.Summary != nil:
532
-				metricFamily.Type = dto.MetricType_SUMMARY.Enum()
533
-			case dtoMetric.Untyped != nil:
534
-				metricFamily.Type = dto.MetricType_UNTYPED.Enum()
535
-			case dtoMetric.Histogram != nil:
536
-				metricFamily.Type = dto.MetricType_HISTOGRAM.Enum()
537
-			default:
538
-				errs = append(errs, fmt.Errorf(
539
-					"empty metric collected: %s", dtoMetric,
540
-				))
541
-				continue
544
+			// Start more workers.
545
+			go collectWorker()
546
+			goroutineBudget--
547
+			runtime.Gosched()
548
+		}
549
+		// Once both checkedMetricChan and uncheckdMetricChan are closed
550
+		// and drained, the contraption above will nil out cmc and umc,
551
+		// and then we can leave the collect loop here.
552
+		if cmc == nil && umc == nil {
553
+			break
554
+		}
555
+	}
556
+	return internal.NormalizeMetricFamilies(metricFamiliesByName), errs.MaybeUnwrap()
557
+}
558
+
559
+// WriteToTextfile calls Gather on the provided Gatherer, encodes the result in the
560
+// Prometheus text format, and writes it to a temporary file. Upon success, the
561
+// temporary file is renamed to the provided filename.
562
+//
563
+// This is intended for use with the textfile collector of the node exporter.
564
+// Note that the node exporter expects the filename to be suffixed with ".prom".
565
+func WriteToTextfile(filename string, g Gatherer) error {
566
+	tmp, err := ioutil.TempFile(filepath.Dir(filename), filepath.Base(filename))
567
+	if err != nil {
568
+		return err
569
+	}
570
+	defer os.Remove(tmp.Name())
571
+
572
+	mfs, err := g.Gather()
573
+	if err != nil {
574
+		return err
575
+	}
576
+	for _, mf := range mfs {
577
+		if _, err := expfmt.MetricFamilyToText(tmp, mf); err != nil {
578
+			return err
579
+		}
580
+	}
581
+	if err := tmp.Close(); err != nil {
582
+		return err
583
+	}
584
+
585
+	if err := os.Chmod(tmp.Name(), 0644); err != nil {
586
+		return err
587
+	}
588
+	return os.Rename(tmp.Name(), filename)
589
+}
590
+
591
+// processMetric is an internal helper method only used by the Gather method.
592
+func processMetric(
593
+	metric Metric,
594
+	metricFamiliesByName map[string]*dto.MetricFamily,
595
+	metricHashes map[uint64]struct{},
596
+	registeredDescIDs map[uint64]struct{},
597
+) error {
598
+	desc := metric.Desc()
599
+	// Wrapped metrics collected by an unchecked Collector can have an
600
+	// invalid Desc.
601
+	if desc.err != nil {
602
+		return desc.err
603
+	}
604
+	dtoMetric := &dto.Metric{}
605
+	if err := metric.Write(dtoMetric); err != nil {
606
+		return fmt.Errorf("error collecting metric %v: %s", desc, err)
607
+	}
608
+	metricFamily, ok := metricFamiliesByName[desc.fqName]
609
+	if ok { // Existing name.
610
+		if metricFamily.GetHelp() != desc.help {
611
+			return fmt.Errorf(
612
+				"collected metric %s %s has help %q but should have %q",
613
+				desc.fqName, dtoMetric, desc.help, metricFamily.GetHelp(),
614
+			)
615
+		}
616
+		// TODO(beorn7): Simplify switch once Desc has type.
617
+		switch metricFamily.GetType() {
618
+		case dto.MetricType_COUNTER:
619
+			if dtoMetric.Counter == nil {
620
+				return fmt.Errorf(
621
+					"collected metric %s %s should be a Counter",
622
+					desc.fqName, dtoMetric,
623
+				)
542 624
 			}
543
-			metricFamiliesByName[desc.fqName] = metricFamily
544
-		}
545
-		if err := checkMetricConsistency(metricFamily, dtoMetric, metricHashes, dimHashes); err != nil {
546
-			errs = append(errs, err)
547
-			continue
548
-		}
549
-		if r.pedanticChecksEnabled {
550
-			// Is the desc registered at all?
551
-			if _, exist := registeredDescIDs[desc.id]; !exist {
552
-				errs = append(errs, fmt.Errorf(
553
-					"collected metric %s %s with unregistered descriptor %s",
554
-					metricFamily.GetName(), dtoMetric, desc,
555
-				))
556
-				continue
625
+		case dto.MetricType_GAUGE:
626
+			if dtoMetric.Gauge == nil {
627
+				return fmt.Errorf(
628
+					"collected metric %s %s should be a Gauge",
629
+					desc.fqName, dtoMetric,
630
+				)
557 631
 			}
558
-			if err := checkDescConsistency(metricFamily, dtoMetric, desc); err != nil {
559
-				errs = append(errs, err)
560
-				continue
632
+		case dto.MetricType_SUMMARY:
633
+			if dtoMetric.Summary == nil {
634
+				return fmt.Errorf(
635
+					"collected metric %s %s should be a Summary",
636
+					desc.fqName, dtoMetric,
637
+				)
561 638
 			}
639
+		case dto.MetricType_UNTYPED:
640
+			if dtoMetric.Untyped == nil {
641
+				return fmt.Errorf(
642
+					"collected metric %s %s should be Untyped",
643
+					desc.fqName, dtoMetric,
644
+				)
645
+			}
646
+		case dto.MetricType_HISTOGRAM:
647
+			if dtoMetric.Histogram == nil {
648
+				return fmt.Errorf(
649
+					"collected metric %s %s should be a Histogram",
650
+					desc.fqName, dtoMetric,
651
+				)
652
+			}
653
+		default:
654
+			panic("encountered MetricFamily with invalid type")
655
+		}
656
+	} else { // New name.
657
+		metricFamily = &dto.MetricFamily{}
658
+		metricFamily.Name = proto.String(desc.fqName)
659
+		metricFamily.Help = proto.String(desc.help)
660
+		// TODO(beorn7): Simplify switch once Desc has type.
661
+		switch {
662
+		case dtoMetric.Gauge != nil:
663
+			metricFamily.Type = dto.MetricType_GAUGE.Enum()
664
+		case dtoMetric.Counter != nil:
665
+			metricFamily.Type = dto.MetricType_COUNTER.Enum()
666
+		case dtoMetric.Summary != nil:
667
+			metricFamily.Type = dto.MetricType_SUMMARY.Enum()
668
+		case dtoMetric.Untyped != nil:
669
+			metricFamily.Type = dto.MetricType_UNTYPED.Enum()
670
+		case dtoMetric.Histogram != nil:
671
+			metricFamily.Type = dto.MetricType_HISTOGRAM.Enum()
672
+		default:
673
+			return fmt.Errorf("empty metric collected: %s", dtoMetric)
674
+		}
675
+		if err := checkSuffixCollisions(metricFamily, metricFamiliesByName); err != nil {
676
+			return err
562 677
 		}
563
-		metricFamily.Metric = append(metricFamily.Metric, dtoMetric)
678
+		metricFamiliesByName[desc.fqName] = metricFamily
564 679
 	}
565
-	return normalizeMetricFamilies(metricFamiliesByName), errs.MaybeUnwrap()
680
+	if err := checkMetricConsistency(metricFamily, dtoMetric, metricHashes); err != nil {
681
+		return err
682
+	}
683
+	if registeredDescIDs != nil {
684
+		// Is the desc registered at all?
685
+		if _, exist := registeredDescIDs[desc.id]; !exist {
686
+			return fmt.Errorf(
687
+				"collected metric %s %s with unregistered descriptor %s",
688
+				metricFamily.GetName(), dtoMetric, desc,
689
+			)
690
+		}
691
+		if err := checkDescConsistency(metricFamily, dtoMetric, desc); err != nil {
692
+			return err
693
+		}
694
+	}
695
+	metricFamily.Metric = append(metricFamily.Metric, dtoMetric)
696
+	return nil
566 697
 }
567 698
 
568 699
 // Gatherers is a slice of Gatherer instances that implements the Gatherer
569 700
 // interface itself. Its Gather method calls Gather on all Gatherers in the
570 701
 // slice in order and returns the merged results. Errors returned from the
571
-// Gather calles are all returned in a flattened MultiError. Duplicate and
702
+// Gather calls are all returned in a flattened MultiError. Duplicate and
572 703
 // inconsistent Metrics are skipped (first occurrence in slice order wins) and
573 704
 // reported in the returned error.
574 705
 //
... ...
@@ -588,7 +700,6 @@ func (gs Gatherers) Gather() ([]*dto.MetricFamily, error) {
588 588
 	var (
589 589
 		metricFamiliesByName = map[string]*dto.MetricFamily{}
590 590
 		metricHashes         = map[uint64]struct{}{}
591
-		dimHashes            = map[string]uint64{}
592 591
 		errs                 MultiError // The collected errors to return in the end.
593 592
 	)
594 593
 
... ...
@@ -625,10 +736,14 @@ func (gs Gatherers) Gather() ([]*dto.MetricFamily, error) {
625 625
 				existingMF.Name = mf.Name
626 626
 				existingMF.Help = mf.Help
627 627
 				existingMF.Type = mf.Type
628
+				if err := checkSuffixCollisions(existingMF, metricFamiliesByName); err != nil {
629
+					errs = append(errs, err)
630
+					continue
631
+				}
628 632
 				metricFamiliesByName[mf.GetName()] = existingMF
629 633
 			}
630 634
 			for _, m := range mf.Metric {
631
-				if err := checkMetricConsistency(existingMF, m, metricHashes, dimHashes); err != nil {
635
+				if err := checkMetricConsistency(existingMF, m, metricHashes); err != nil {
632 636
 					errs = append(errs, err)
633 637
 					continue
634 638
 				}
... ...
@@ -636,88 +751,80 @@ func (gs Gatherers) Gather() ([]*dto.MetricFamily, error) {
636 636
 			}
637 637
 		}
638 638
 	}
639
-	return normalizeMetricFamilies(metricFamiliesByName), errs.MaybeUnwrap()
640
-}
641
-
642
-// metricSorter is a sortable slice of *dto.Metric.
643
-type metricSorter []*dto.Metric
644
-
645
-func (s metricSorter) Len() int {
646
-	return len(s)
647
-}
648
-
649
-func (s metricSorter) Swap(i, j int) {
650
-	s[i], s[j] = s[j], s[i]
651
-}
652
-
653
-func (s metricSorter) Less(i, j int) bool {
654
-	if len(s[i].Label) != len(s[j].Label) {
655
-		// This should not happen. The metrics are
656
-		// inconsistent. However, we have to deal with the fact, as
657
-		// people might use custom collectors or metric family injection
658
-		// to create inconsistent metrics. So let's simply compare the
659
-		// number of labels in this case. That will still yield
660
-		// reproducible sorting.
661
-		return len(s[i].Label) < len(s[j].Label)
662
-	}
663
-	for n, lp := range s[i].Label {
664
-		vi := lp.GetValue()
665
-		vj := s[j].Label[n].GetValue()
666
-		if vi != vj {
667
-			return vi < vj
668
-		}
669
-	}
670
-
671
-	// We should never arrive here. Multiple metrics with the same
672
-	// label set in the same scrape will lead to undefined ingestion
673
-	// behavior. However, as above, we have to provide stable sorting
674
-	// here, even for inconsistent metrics. So sort equal metrics
675
-	// by their timestamp, with missing timestamps (implying "now")
676
-	// coming last.
677
-	if s[i].TimestampMs == nil {
678
-		return false
679
-	}
680
-	if s[j].TimestampMs == nil {
681
-		return true
682
-	}
683
-	return s[i].GetTimestampMs() < s[j].GetTimestampMs()
639
+	return internal.NormalizeMetricFamilies(metricFamiliesByName), errs.MaybeUnwrap()
684 640
 }
685 641
 
686
-// normalizeMetricFamilies returns a MetricFamily slice whith empty
687
-// MetricFamilies pruned and the remaining MetricFamilies sorted by name within
688
-// the slice, with the contained Metrics sorted within each MetricFamily.
689
-func normalizeMetricFamilies(metricFamiliesByName map[string]*dto.MetricFamily) []*dto.MetricFamily {
690
-	for _, mf := range metricFamiliesByName {
691
-		sort.Sort(metricSorter(mf.Metric))
642
+// checkSuffixCollisions checks for collisions with the “magic” suffixes the
643
+// Prometheus text format and the internal metric representation of the
644
+// Prometheus server add while flattening Summaries and Histograms.
645
+func checkSuffixCollisions(mf *dto.MetricFamily, mfs map[string]*dto.MetricFamily) error {
646
+	var (
647
+		newName              = mf.GetName()
648
+		newType              = mf.GetType()
649
+		newNameWithoutSuffix = ""
650
+	)
651
+	switch {
652
+	case strings.HasSuffix(newName, "_count"):
653
+		newNameWithoutSuffix = newName[:len(newName)-6]
654
+	case strings.HasSuffix(newName, "_sum"):
655
+		newNameWithoutSuffix = newName[:len(newName)-4]
656
+	case strings.HasSuffix(newName, "_bucket"):
657
+		newNameWithoutSuffix = newName[:len(newName)-7]
658
+	}
659
+	if newNameWithoutSuffix != "" {
660
+		if existingMF, ok := mfs[newNameWithoutSuffix]; ok {
661
+			switch existingMF.GetType() {
662
+			case dto.MetricType_SUMMARY:
663
+				if !strings.HasSuffix(newName, "_bucket") {
664
+					return fmt.Errorf(
665
+						"collected metric named %q collides with previously collected summary named %q",
666
+						newName, newNameWithoutSuffix,
667
+					)
668
+				}
669
+			case dto.MetricType_HISTOGRAM:
670
+				return fmt.Errorf(
671
+					"collected metric named %q collides with previously collected histogram named %q",
672
+					newName, newNameWithoutSuffix,
673
+				)
674
+			}
675
+		}
692 676
 	}
693
-	names := make([]string, 0, len(metricFamiliesByName))
694
-	for name, mf := range metricFamiliesByName {
695
-		if len(mf.Metric) > 0 {
696
-			names = append(names, name)
677
+	if newType == dto.MetricType_SUMMARY || newType == dto.MetricType_HISTOGRAM {
678
+		if _, ok := mfs[newName+"_count"]; ok {
679
+			return fmt.Errorf(
680
+				"collected histogram or summary named %q collides with previously collected metric named %q",
681
+				newName, newName+"_count",
682
+			)
683
+		}
684
+		if _, ok := mfs[newName+"_sum"]; ok {
685
+			return fmt.Errorf(
686
+				"collected histogram or summary named %q collides with previously collected metric named %q",
687
+				newName, newName+"_sum",
688
+			)
697 689
 		}
698 690
 	}
699
-	sort.Strings(names)
700
-	result := make([]*dto.MetricFamily, 0, len(names))
701
-	for _, name := range names {
702
-		result = append(result, metricFamiliesByName[name])
691
+	if newType == dto.MetricType_HISTOGRAM {
692
+		if _, ok := mfs[newName+"_bucket"]; ok {
693
+			return fmt.Errorf(
694
+				"collected histogram named %q collides with previously collected metric named %q",
695
+				newName, newName+"_bucket",
696
+			)
697
+		}
703 698
 	}
704
-	return result
699
+	return nil
705 700
 }
706 701
 
707 702
 // checkMetricConsistency checks if the provided Metric is consistent with the
708
-// provided MetricFamily. It also hashed the Metric labels and the MetricFamily
709
-// name. If the resulting hash is alread in the provided metricHashes, an error
710
-// is returned. If not, it is added to metricHashes. The provided dimHashes maps
711
-// MetricFamily names to their dimHash (hashed sorted label names). If dimHashes
712
-// doesn't yet contain a hash for the provided MetricFamily, it is
713
-// added. Otherwise, an error is returned if the existing dimHashes in not equal
714
-// the calculated dimHash.
703
+// provided MetricFamily. It also hashes the Metric labels and the MetricFamily
704
+// name. If the resulting hash is already in the provided metricHashes, an error
705
+// is returned. If not, it is added to metricHashes.
715 706
 func checkMetricConsistency(
716 707
 	metricFamily *dto.MetricFamily,
717 708
 	dtoMetric *dto.Metric,
718 709
 	metricHashes map[uint64]struct{},
719
-	dimHashes map[string]uint64,
720 710
 ) error {
711
+	name := metricFamily.GetName()
712
+
721 713
 	// Type consistency with metric family.
722 714
 	if metricFamily.GetType() == dto.MetricType_GAUGE && dtoMetric.Gauge == nil ||
723 715
 		metricFamily.GetType() == dto.MetricType_COUNTER && dtoMetric.Counter == nil ||
... ...
@@ -725,41 +832,65 @@ func checkMetricConsistency(
725 725
 		metricFamily.GetType() == dto.MetricType_HISTOGRAM && dtoMetric.Histogram == nil ||
726 726
 		metricFamily.GetType() == dto.MetricType_UNTYPED && dtoMetric.Untyped == nil {
727 727
 		return fmt.Errorf(
728
-			"collected metric %s %s is not a %s",
729
-			metricFamily.GetName(), dtoMetric, metricFamily.GetType(),
728
+			"collected metric %q { %s} is not a %s",
729
+			name, dtoMetric, metricFamily.GetType(),
730 730
 		)
731 731
 	}
732 732
 
733
-	// Is the metric unique (i.e. no other metric with the same name and the same label values)?
733
+	previousLabelName := ""
734
+	for _, labelPair := range dtoMetric.GetLabel() {
735
+		labelName := labelPair.GetName()
736
+		if labelName == previousLabelName {
737
+			return fmt.Errorf(
738
+				"collected metric %q { %s} has two or more labels with the same name: %s",
739
+				name, dtoMetric, labelName,
740
+			)
741
+		}
742
+		if !checkLabelName(labelName) {
743
+			return fmt.Errorf(
744
+				"collected metric %q { %s} has a label with an invalid name: %s",
745
+				name, dtoMetric, labelName,
746
+			)
747
+		}
748
+		if dtoMetric.Summary != nil && labelName == quantileLabel {
749
+			return fmt.Errorf(
750
+				"collected metric %q { %s} must not have an explicit %q label",
751
+				name, dtoMetric, quantileLabel,
752
+			)
753
+		}
754
+		if !utf8.ValidString(labelPair.GetValue()) {
755
+			return fmt.Errorf(
756
+				"collected metric %q { %s} has a label named %q whose value is not utf8: %#v",
757
+				name, dtoMetric, labelName, labelPair.GetValue())
758
+		}
759
+		previousLabelName = labelName
760
+	}
761
+
762
+	// Is the metric unique (i.e. no other metric with the same name and the same labels)?
734 763
 	h := hashNew()
735
-	h = hashAdd(h, metricFamily.GetName())
764
+	h = hashAdd(h, name)
736 765
 	h = hashAddByte(h, separatorByte)
737
-	dh := hashNew()
738 766
 	// Make sure label pairs are sorted. We depend on it for the consistency
739 767
 	// check.
740
-	sort.Sort(LabelPairSorter(dtoMetric.Label))
768
+	if !sort.IsSorted(labelPairSorter(dtoMetric.Label)) {
769
+		// We cannot sort dtoMetric.Label in place as it is immutable by contract.
770
+		copiedLabels := make([]*dto.LabelPair, len(dtoMetric.Label))
771
+		copy(copiedLabels, dtoMetric.Label)
772
+		sort.Sort(labelPairSorter(copiedLabels))
773
+		dtoMetric.Label = copiedLabels
774
+	}
741 775
 	for _, lp := range dtoMetric.Label {
776
+		h = hashAdd(h, lp.GetName())
777
+		h = hashAddByte(h, separatorByte)
742 778
 		h = hashAdd(h, lp.GetValue())
743 779
 		h = hashAddByte(h, separatorByte)
744
-		dh = hashAdd(dh, lp.GetName())
745
-		dh = hashAddByte(dh, separatorByte)
746 780
 	}
747 781
 	if _, exists := metricHashes[h]; exists {
748 782
 		return fmt.Errorf(
749
-			"collected metric %s %s was collected before with the same name and label values",
750
-			metricFamily.GetName(), dtoMetric,
783
+			"collected metric %q { %s} was collected before with the same name and label values",
784
+			name, dtoMetric,
751 785
 		)
752 786
 	}
753
-	if dimHash, ok := dimHashes[metricFamily.GetName()]; ok {
754
-		if dimHash != dh {
755
-			return fmt.Errorf(
756
-				"collected metric %s %s has label dimensions inconsistent with previously collected metrics in the same metric family",
757
-				metricFamily.GetName(), dtoMetric,
758
-			)
759
-		}
760
-	} else {
761
-		dimHashes[metricFamily.GetName()] = dh
762
-	}
763 787
 	metricHashes[h] = struct{}{}
764 788
 	return nil
765 789
 }
... ...
@@ -778,8 +909,8 @@ func checkDescConsistency(
778 778
 	}
779 779
 
780 780
 	// Is the desc consistent with the content of the metric?
781
-	lpsFromDesc := make([]*dto.LabelPair, 0, len(dtoMetric.Label))
782
-	lpsFromDesc = append(lpsFromDesc, desc.constLabelPairs...)
781
+	lpsFromDesc := make([]*dto.LabelPair, len(desc.constLabelPairs), len(dtoMetric.Label))
782
+	copy(lpsFromDesc, desc.constLabelPairs)
783 783
 	for _, l := range desc.variableLabels {
784 784
 		lpsFromDesc = append(lpsFromDesc, &dto.LabelPair{
785 785
 			Name: proto.String(l),
... ...
@@ -791,7 +922,7 @@ func checkDescConsistency(
791 791
 			metricFamily.GetName(), dtoMetric, desc,
792 792
 		)
793 793
 	}
794
-	sort.Sort(LabelPairSorter(lpsFromDesc))
794
+	sort.Sort(labelPairSorter(lpsFromDesc))
795 795
 	for i, lpFromDesc := range lpsFromDesc {
796 796
 		lpFromMetric := dtoMetric.Label[i]
797 797
 		if lpFromDesc.GetName() != lpFromMetric.GetName() ||
... ...
@@ -16,8 +16,10 @@ package prometheus
16 16
 import (
17 17
 	"fmt"
18 18
 	"math"
19
+	"runtime"
19 20
 	"sort"
20 21
 	"sync"
22
+	"sync/atomic"
21 23
 	"time"
22 24
 
23 25
 	"github.com/beorn7/perks/quantile"
... ...
@@ -36,7 +38,10 @@ const quantileLabel = "quantile"
36 36
 //
37 37
 // A typical use-case is the observation of request latencies. By default, a
38 38
 // Summary provides the median, the 90th and the 99th percentile of the latency
39
-// as rank estimations.
39
+// as rank estimations. However, the default behavior will change in the
40
+// upcoming v1.0.0 of the library. There will be no rank estimations at all by
41
+// default. For a sane transition, it is recommended to set the desired rank
42
+// estimations explicitly.
40 43
 //
41 44
 // Note that the rank estimations cannot be aggregated in a meaningful way with
42 45
 // the Prometheus query language (i.e. you cannot average or add them). If you
... ...
@@ -54,6 +59,9 @@ type Summary interface {
54 54
 }
55 55
 
56 56
 // DefObjectives are the default Summary quantile values.
57
+//
58
+// Deprecated: DefObjectives will not be used as the default objectives in
59
+// v1.0.0 of the library. The default Summary will have no quantiles then.
57 60
 var (
58 61
 	DefObjectives = map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}
59 62
 
... ...
@@ -75,8 +83,10 @@ const (
75 75
 )
76 76
 
77 77
 // SummaryOpts bundles the options for creating a Summary metric. It is
78
-// mandatory to set Name and Help to a non-empty string. All other fields are
79
-// optional and can safely be left at their zero value.
78
+// mandatory to set Name to a non-empty string. While all other fields are
79
+// optional and can safely be left at their zero value, it is recommended to set
80
+// a help string and to explicitly set the Objectives field to the desired value
81
+// as the default value will change in the upcoming v1.0.0 of the library.
80 82
 type SummaryOpts struct {
81 83
 	// Namespace, Subsystem, and Name are components of the fully-qualified
82 84
 	// name of the Summary (created by joining these components with
... ...
@@ -87,35 +97,40 @@ type SummaryOpts struct {
87 87
 	Subsystem string
88 88
 	Name      string
89 89
 
90
-	// Help provides information about this Summary. Mandatory!
90
+	// Help provides information about this Summary.
91 91
 	//
92 92
 	// Metrics with the same fully-qualified name must have the same Help
93 93
 	// string.
94 94
 	Help string
95 95
 
96
-	// ConstLabels are used to attach fixed labels to this
97
-	// Summary. Summaries with the same fully-qualified name must have the
98
-	// same label names in their ConstLabels.
96
+	// ConstLabels are used to attach fixed labels to this metric. Metrics
97
+	// with the same fully-qualified name must have the same label names in
98
+	// their ConstLabels.
99 99
 	//
100
-	// Note that in most cases, labels have a value that varies during the
101
-	// lifetime of a process. Those labels are usually managed with a
102
-	// SummaryVec. ConstLabels serve only special purposes. One is for the
103
-	// special case where the value of a label does not change during the
104
-	// lifetime of a process, e.g. if the revision of the running binary is
105
-	// put into a label. Another, more advanced purpose is if more than one
106
-	// Collector needs to collect Summaries with the same fully-qualified
107
-	// name. In that case, those Summaries must differ in the values of
108
-	// their ConstLabels. See the Collector examples.
100
+	// Due to the way a Summary is represented in the Prometheus text format
101
+	// and how it is handled by the Prometheus server internally, “quantile”
102
+	// is an illegal label name. Construction of a Summary or SummaryVec
103
+	// will panic if this label name is used in ConstLabels.
109 104
 	//
110
-	// If the value of a label never changes (not even between binaries),
111
-	// that label most likely should not be a label at all (but part of the
112
-	// metric name).
105
+	// ConstLabels are only used rarely. In particular, do not use them to
106
+	// attach the same labels to all your metrics. Those use cases are
107
+	// better covered by target labels set by the scraping Prometheus
108
+	// server, or by one specific metric (e.g. a build_info or a
109
+	// machine_role metric). See also
110
+	// https://prometheus.io/docs/instrumenting/writing_exporters/#target-labels,-not-static-scraped-labels
113 111
 	ConstLabels Labels
114 112
 
115 113
 	// Objectives defines the quantile rank estimates with their respective
116
-	// absolute error. If Objectives[q] = e, then the value reported
117
-	// for q will be the φ-quantile value for some φ between q-e and q+e.
118
-	// The default value is DefObjectives.
114
+	// absolute error. If Objectives[q] = e, then the value reported for q
115
+	// will be the φ-quantile value for some φ between q-e and q+e.  The
116
+	// default value is DefObjectives. It is used if Objectives is left at
117
+	// its zero value (i.e. nil). To create a Summary without Objectives,
118
+	// set it to an empty map (i.e. map[float64]float64{}).
119
+	//
120
+	// Note that the current value of DefObjectives is deprecated. It will
121
+	// be replaced by an empty map in v1.0.0 of the library. Please
122
+	// explicitly set Objectives to the desired value to avoid problems
123
+	// during the transition.
119 124
 	Objectives map[float64]float64
120 125
 
121 126
 	// MaxAge defines the duration for which an observation stays relevant
... ...
@@ -139,7 +154,7 @@ type SummaryOpts struct {
139 139
 	BufCap uint32
140 140
 }
141 141
 
142
-// Great fuck-up with the sliding-window decay algorithm... The Merge method of
142
+// Problem with the sliding-window decay algorithm... The Merge method of
143 143
 // perk/quantile is actually not working as advertised - and it might be
144 144
 // unfixable, as the underlying algorithm is apparently not capable of merging
145 145
 // summaries in the first place. To avoid using Merge, we are currently adding
... ...
@@ -169,7 +184,7 @@ func NewSummary(opts SummaryOpts) Summary {
169 169
 
170 170
 func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary {
171 171
 	if len(desc.variableLabels) != len(labelValues) {
172
-		panic(errInconsistentCardinality)
172
+		panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, labelValues))
173 173
 	}
174 174
 
175 175
 	for _, n := range desc.variableLabels {
... ...
@@ -183,7 +198,7 @@ func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary {
183 183
 		}
184 184
 	}
185 185
 
186
-	if len(opts.Objectives) == 0 {
186
+	if opts.Objectives == nil {
187 187
 		opts.Objectives = DefObjectives
188 188
 	}
189 189
 
... ...
@@ -202,6 +217,17 @@ func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary {
202 202
 		opts.BufCap = DefBufCap
203 203
 	}
204 204
 
205
+	if len(opts.Objectives) == 0 {
206
+		// Use the lock-free implementation of a Summary without objectives.
207
+		s := &noObjectivesSummary{
208
+			desc:       desc,
209
+			labelPairs: makeLabelPairs(desc, labelValues),
210
+			counts:     [2]*summaryCounts{&summaryCounts{}, &summaryCounts{}},
211
+		}
212
+		s.init(s) // Init self-collection.
213
+		return s
214
+	}
215
+
205 216
 	s := &summary{
206 217
 		desc: desc,
207 218
 
... ...
@@ -370,6 +396,116 @@ func (s *summary) swapBufs(now time.Time) {
370 370
 	}
371 371
 }
372 372
 
373
+type summaryCounts struct {
374
+	// sumBits contains the bits of the float64 representing the sum of all
375
+	// observations. sumBits and count have to go first in the struct to
376
+	// guarantee alignment for atomic operations.
377
+	// http://golang.org/pkg/sync/atomic/#pkg-note-BUG
378
+	sumBits uint64
379
+	count   uint64
380
+}
381
+
382
+type noObjectivesSummary struct {
383
+	// countAndHotIdx enables lock-free writes with use of atomic updates.
384
+	// The most significant bit is the hot index [0 or 1] of the count field
385
+	// below. Observe calls update the hot one. All remaining bits count the
386
+	// number of Observe calls. Observe starts by incrementing this counter,
387
+	// and finish by incrementing the count field in the respective
388
+	// summaryCounts, as a marker for completion.
389
+	//
390
+	// Calls of the Write method (which are non-mutating reads from the
391
+	// perspective of the summary) swap the hot–cold under the writeMtx
392
+	// lock. A cooldown is awaited (while locked) by comparing the number of
393
+	// observations with the initiation count. Once they match, then the
394
+	// last observation on the now cool one has completed. All cool fields must
395
+	// be merged into the new hot before releasing writeMtx.
396
+
397
+	// Fields with atomic access first! See alignment constraint:
398
+	// http://golang.org/pkg/sync/atomic/#pkg-note-BUG
399
+	countAndHotIdx uint64
400
+
401
+	selfCollector
402
+	desc     *Desc
403
+	writeMtx sync.Mutex // Only used in the Write method.
404
+
405
+	// Two counts, one is "hot" for lock-free observations, the other is
406
+	// "cold" for writing out a dto.Metric. It has to be an array of
407
+	// pointers to guarantee 64bit alignment of the histogramCounts, see
408
+	// http://golang.org/pkg/sync/atomic/#pkg-note-BUG.
409
+	counts [2]*summaryCounts
410
+
411
+	labelPairs []*dto.LabelPair
412
+}
413
+
414
+func (s *noObjectivesSummary) Desc() *Desc {
415
+	return s.desc
416
+}
417
+
418
+func (s *noObjectivesSummary) Observe(v float64) {
419
+	// We increment h.countAndHotIdx so that the counter in the lower
420
+	// 63 bits gets incremented. At the same time, we get the new value
421
+	// back, which we can use to find the currently-hot counts.
422
+	n := atomic.AddUint64(&s.countAndHotIdx, 1)
423
+	hotCounts := s.counts[n>>63]
424
+
425
+	for {
426
+		oldBits := atomic.LoadUint64(&hotCounts.sumBits)
427
+		newBits := math.Float64bits(math.Float64frombits(oldBits) + v)
428
+		if atomic.CompareAndSwapUint64(&hotCounts.sumBits, oldBits, newBits) {
429
+			break
430
+		}
431
+	}
432
+	// Increment count last as we take it as a signal that the observation
433
+	// is complete.
434
+	atomic.AddUint64(&hotCounts.count, 1)
435
+}
436
+
437
+func (s *noObjectivesSummary) Write(out *dto.Metric) error {
438
+	// For simplicity, we protect this whole method by a mutex. It is not in
439
+	// the hot path, i.e. Observe is called much more often than Write. The
440
+	// complication of making Write lock-free isn't worth it, if possible at
441
+	// all.
442
+	s.writeMtx.Lock()
443
+	defer s.writeMtx.Unlock()
444
+
445
+	// Adding 1<<63 switches the hot index (from 0 to 1 or from 1 to 0)
446
+	// without touching the count bits. See the struct comments for a full
447
+	// description of the algorithm.
448
+	n := atomic.AddUint64(&s.countAndHotIdx, 1<<63)
449
+	// count is contained unchanged in the lower 63 bits.
450
+	count := n & ((1 << 63) - 1)
451
+	// The most significant bit tells us which counts is hot. The complement
452
+	// is thus the cold one.
453
+	hotCounts := s.counts[n>>63]
454
+	coldCounts := s.counts[(^n)>>63]
455
+
456
+	// Await cooldown.
457
+	for count != atomic.LoadUint64(&coldCounts.count) {
458
+		runtime.Gosched() // Let observations get work done.
459
+	}
460
+
461
+	sum := &dto.Summary{
462
+		SampleCount: proto.Uint64(count),
463
+		SampleSum:   proto.Float64(math.Float64frombits(atomic.LoadUint64(&coldCounts.sumBits))),
464
+	}
465
+
466
+	out.Summary = sum
467
+	out.Label = s.labelPairs
468
+
469
+	// Finally add all the cold counts to the new hot counts and reset the cold counts.
470
+	atomic.AddUint64(&hotCounts.count, count)
471
+	atomic.StoreUint64(&coldCounts.count, 0)
472
+	for {
473
+		oldBits := atomic.LoadUint64(&hotCounts.sumBits)
474
+		newBits := math.Float64bits(math.Float64frombits(oldBits) + sum.GetSampleSum())
475
+		if atomic.CompareAndSwapUint64(&hotCounts.sumBits, oldBits, newBits) {
476
+			atomic.StoreUint64(&coldCounts.sumBits, 0)
477
+			break
478
+		}
479
+	}
480
+	return nil
481
+}
482
+
373 483
 type quantSort []*dto.Quantile
374 484
 
375 485
 func (s quantSort) Len() int {
... ...
@@ -390,13 +526,21 @@ func (s quantSort) Less(i, j int) bool {
390 390
 // (e.g. HTTP request latencies, partitioned by status code and method). Create
391 391
 // instances with NewSummaryVec.
392 392
 type SummaryVec struct {
393
-	*MetricVec
393
+	*metricVec
394 394
 }
395 395
 
396 396
 // NewSummaryVec creates a new SummaryVec based on the provided SummaryOpts and
397
-// partitioned by the given label names. At least one label name must be
398
-// provided.
397
+// partitioned by the given label names.
398
+//
399
+// Due to the way a Summary is represented in the Prometheus text format and how
400
+// it is handled by the Prometheus server internally, “quantile” is an illegal
401
+// label name. NewSummaryVec will panic if this label name is used.
399 402
 func NewSummaryVec(opts SummaryOpts, labelNames []string) *SummaryVec {
403
+	for _, ln := range labelNames {
404
+		if ln == quantileLabel {
405
+			panic(errQuantileLabelNotAllowed)
406
+		}
407
+	}
400 408
 	desc := NewDesc(
401 409
 		BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
402 410
 		opts.Help,
... ...
@@ -404,47 +548,116 @@ func NewSummaryVec(opts SummaryOpts, labelNames []string) *SummaryVec {
404 404
 		opts.ConstLabels,
405 405
 	)
406 406
 	return &SummaryVec{
407
-		MetricVec: newMetricVec(desc, func(lvs ...string) Metric {
407
+		metricVec: newMetricVec(desc, func(lvs ...string) Metric {
408 408
 			return newSummary(desc, opts, lvs...)
409 409
 		}),
410 410
 	}
411 411
 }
412 412
 
413
-// GetMetricWithLabelValues replaces the method of the same name in
414
-// MetricVec. The difference is that this method returns a Summary and not a
415
-// Metric so that no type conversion is required.
416
-func (m *SummaryVec) GetMetricWithLabelValues(lvs ...string) (Summary, error) {
417
-	metric, err := m.MetricVec.GetMetricWithLabelValues(lvs...)
413
+// GetMetricWithLabelValues returns the Summary for the given slice of label
414
+// values (same order as the VariableLabels in Desc). If that combination of
415
+// label values is accessed for the first time, a new Summary is created.
416
+//
417
+// It is possible to call this method without using the returned Summary to only
418
+// create the new Summary but leave it at its starting value, a Summary without
419
+// any observations.
420
+//
421
+// Keeping the Summary for later use is possible (and should be considered if
422
+// performance is critical), but keep in mind that Reset, DeleteLabelValues and
423
+// Delete can be used to delete the Summary from the SummaryVec. In that case,
424
+// the Summary will still exist, but it will not be exported anymore, even if a
425
+// Summary with the same label values is created later. See also the CounterVec
426
+// example.
427
+//
428
+// An error is returned if the number of label values is not the same as the
429
+// number of VariableLabels in Desc (minus any curried labels).
430
+//
431
+// Note that for more than one label value, this method is prone to mistakes
432
+// caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as
433
+// an alternative to avoid that type of mistake. For higher label numbers, the
434
+// latter has a much more readable (albeit more verbose) syntax, but it comes
435
+// with a performance overhead (for creating and processing the Labels map).
436
+// See also the GaugeVec example.
437
+func (v *SummaryVec) GetMetricWithLabelValues(lvs ...string) (Observer, error) {
438
+	metric, err := v.metricVec.getMetricWithLabelValues(lvs...)
418 439
 	if metric != nil {
419
-		return metric.(Summary), err
440
+		return metric.(Observer), err
420 441
 	}
421 442
 	return nil, err
422 443
 }
423 444
 
424
-// GetMetricWith replaces the method of the same name in MetricVec. The
425
-// difference is that this method returns a Summary and not a Metric so that no
426
-// type conversion is required.
427
-func (m *SummaryVec) GetMetricWith(labels Labels) (Summary, error) {
428
-	metric, err := m.MetricVec.GetMetricWith(labels)
445
+// GetMetricWith returns the Summary for the given Labels map (the label names
446
+// must match those of the VariableLabels in Desc). If that label map is
447
+// accessed for the first time, a new Summary is created. Implications of
448
+// creating a Summary without using it and keeping the Summary for later use are
449
+// the same as for GetMetricWithLabelValues.
450
+//
451
+// An error is returned if the number and names of the Labels are inconsistent
452
+// with those of the VariableLabels in Desc (minus any curried labels).
453
+//
454
+// This method is used for the same purpose as
455
+// GetMetricWithLabelValues(...string). See there for pros and cons of the two
456
+// methods.
457
+func (v *SummaryVec) GetMetricWith(labels Labels) (Observer, error) {
458
+	metric, err := v.metricVec.getMetricWith(labels)
429 459
 	if metric != nil {
430
-		return metric.(Summary), err
460
+		return metric.(Observer), err
431 461
 	}
432 462
 	return nil, err
433 463
 }
434 464
 
435 465
 // WithLabelValues works as GetMetricWithLabelValues, but panics where
436
-// GetMetricWithLabelValues would have returned an error. By not returning an
437
-// error, WithLabelValues allows shortcuts like
466
+// GetMetricWithLabelValues would have returned an error. Not returning an
467
+// error allows shortcuts like
438 468
 //     myVec.WithLabelValues("404", "GET").Observe(42.21)
439
-func (m *SummaryVec) WithLabelValues(lvs ...string) Summary {
440
-	return m.MetricVec.WithLabelValues(lvs...).(Summary)
469
+func (v *SummaryVec) WithLabelValues(lvs ...string) Observer {
470
+	s, err := v.GetMetricWithLabelValues(lvs...)
471
+	if err != nil {
472
+		panic(err)
473
+	}
474
+	return s
441 475
 }
442 476
 
443 477
 // With works as GetMetricWith, but panics where GetMetricWithLabels would have
444
-// returned an error. By not returning an error, With allows shortcuts like
445
-//     myVec.With(Labels{"code": "404", "method": "GET"}).Observe(42.21)
446
-func (m *SummaryVec) With(labels Labels) Summary {
447
-	return m.MetricVec.With(labels).(Summary)
478
+// returned an error. Not returning an error allows shortcuts like
479
+//     myVec.With(prometheus.Labels{"code": "404", "method": "GET"}).Observe(42.21)
480
+func (v *SummaryVec) With(labels Labels) Observer {
481
+	s, err := v.GetMetricWith(labels)
482
+	if err != nil {
483
+		panic(err)
484
+	}
485
+	return s
486
+}
487
+
488
+// CurryWith returns a vector curried with the provided labels, i.e. the
489
+// returned vector has those labels pre-set for all labeled operations performed
490
+// on it. The cardinality of the curried vector is reduced accordingly. The
491
+// order of the remaining labels stays the same (just with the curried labels
492
+// taken out of the sequence – which is relevant for the
493
+// (GetMetric)WithLabelValues methods). It is possible to curry a curried
494
+// vector, but only with labels not yet used for currying before.
495
+//
496
+// The metrics contained in the SummaryVec are shared between the curried and
497
+// uncurried vectors. They are just accessed differently. Curried and uncurried
498
+// vectors behave identically in terms of collection. Only one must be
499
+// registered with a given registry (usually the uncurried version). The Reset
500
+// method deletes all metrics, even if called on a curried vector.
501
+func (v *SummaryVec) CurryWith(labels Labels) (ObserverVec, error) {
502
+	vec, err := v.curryWith(labels)
503
+	if vec != nil {
504
+		return &SummaryVec{vec}, err
505
+	}
506
+	return nil, err
507
+}
508
+
509
+// MustCurryWith works as CurryWith but panics where CurryWith would have
510
+// returned an error.
511
+func (v *SummaryVec) MustCurryWith(labels Labels) ObserverVec {
512
+	vec, err := v.CurryWith(labels)
513
+	if err != nil {
514
+		panic(err)
515
+	}
516
+	return vec
448 517
 }
449 518
 
450 519
 type constSummary struct {
... ...
@@ -497,7 +710,7 @@ func (s *constSummary) Write(out *dto.Metric) error {
497 497
 //     map[float64]float64{0.5: 0.23, 0.99: 0.56}
498 498
 //
499 499
 // NewConstSummary returns an error if the length of labelValues is not
500
-// consistent with the variable labels in Desc.
500
+// consistent with the variable labels in Desc or if Desc is invalid.
501 501
 func NewConstSummary(
502 502
 	desc *Desc,
503 503
 	count uint64,
... ...
@@ -505,8 +718,11 @@ func NewConstSummary(
505 505
 	quantiles map[float64]float64,
506 506
 	labelValues ...string,
507 507
 ) (Metric, error) {
508
-	if len(desc.variableLabels) != len(labelValues) {
509
-		return nil, errInconsistentCardinality
508
+	if desc.err != nil {
509
+		return nil, desc.err
510
+	}
511
+	if err := validateLabelValues(labelValues, len(desc.variableLabels)); err != nil {
512
+		return nil, err
510 513
 	}
511 514
 	return &constSummary{
512 515
 		desc:       desc,
513 516
new file mode 100644
... ...
@@ -0,0 +1,54 @@
0
+// Copyright 2016 The Prometheus Authors
1
+// Licensed under the Apache License, Version 2.0 (the "License");
2
+// you may not use this file except in compliance with the License.
3
+// You may obtain a copy of the License at
4
+//
5
+// http://www.apache.org/licenses/LICENSE-2.0
6
+//
7
+// Unless required by applicable law or agreed to in writing, software
8
+// distributed under the License is distributed on an "AS IS" BASIS,
9
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+// See the License for the specific language governing permissions and
11
+// limitations under the License.
12
+
13
+package prometheus
14
+
15
+import "time"
16
+
17
+// Timer is a helper type to time functions. Use NewTimer to create new
18
+// instances.
19
+type Timer struct {
20
+	begin    time.Time
21
+	observer Observer
22
+}
23
+
24
+// NewTimer creates a new Timer. The provided Observer is used to observe a
25
+// duration in seconds. Timer is usually used to time a function call in the
26
+// following way:
27
+//    func TimeMe() {
28
+//        timer := NewTimer(myHistogram)
29
+//        defer timer.ObserveDuration()
30
+//        // Do actual work.
31
+//    }
32
+func NewTimer(o Observer) *Timer {
33
+	return &Timer{
34
+		begin:    time.Now(),
35
+		observer: o,
36
+	}
37
+}
38
+
39
+// ObserveDuration records the duration passed since the Timer was created with
40
+// NewTimer. It calls the Observe method of the Observer provided during
41
+// construction with the duration in seconds as an argument. The observed
42
+// duration is also returned. ObserveDuration is usually called with a defer
43
+// statement.
44
+//
45
+// Note that this method is only guaranteed to never observe negative durations
46
+// if used with Go1.9+.
47
+func (t *Timer) ObserveDuration() time.Duration {
48
+	d := time.Since(t.begin)
49
+	if t.observer != nil {
50
+		t.observer.Observe(d.Seconds())
51
+	}
52
+	return d
53
+}
... ...
@@ -13,108 +13,12 @@
13 13
 
14 14
 package prometheus
15 15
 
16
-// Untyped is a Metric that represents a single numerical value that can
17
-// arbitrarily go up and down.
18
-//
19
-// An Untyped metric works the same as a Gauge. The only difference is that to
20
-// no type information is implied.
21
-//
22
-// To create Untyped instances, use NewUntyped.
23
-type Untyped interface {
24
-	Metric
25
-	Collector
26
-
27
-	// Set sets the Untyped metric to an arbitrary value.
28
-	Set(float64)
29
-	// Inc increments the Untyped metric by 1.
30
-	Inc()
31
-	// Dec decrements the Untyped metric by 1.
32
-	Dec()
33
-	// Add adds the given value to the Untyped metric. (The value can be
34
-	// negative, resulting in a decrease.)
35
-	Add(float64)
36
-	// Sub subtracts the given value from the Untyped metric. (The value can
37
-	// be negative, resulting in an increase.)
38
-	Sub(float64)
39
-}
40
-
41 16
 // UntypedOpts is an alias for Opts. See there for doc comments.
42 17
 type UntypedOpts Opts
43 18
 
44
-// NewUntyped creates a new Untyped metric from the provided UntypedOpts.
45
-func NewUntyped(opts UntypedOpts) Untyped {
46
-	return newValue(NewDesc(
47
-		BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
48
-		opts.Help,
49
-		nil,
50
-		opts.ConstLabels,
51
-	), UntypedValue, 0)
52
-}
53
-
54
-// UntypedVec is a Collector that bundles a set of Untyped metrics that all
55
-// share the same Desc, but have different values for their variable
56
-// labels. This is used if you want to count the same thing partitioned by
57
-// various dimensions. Create instances with NewUntypedVec.
58
-type UntypedVec struct {
59
-	*MetricVec
60
-}
61
-
62
-// NewUntypedVec creates a new UntypedVec based on the provided UntypedOpts and
63
-// partitioned by the given label names. At least one label name must be
64
-// provided.
65
-func NewUntypedVec(opts UntypedOpts, labelNames []string) *UntypedVec {
66
-	desc := NewDesc(
67
-		BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
68
-		opts.Help,
69
-		labelNames,
70
-		opts.ConstLabels,
71
-	)
72
-	return &UntypedVec{
73
-		MetricVec: newMetricVec(desc, func(lvs ...string) Metric {
74
-			return newValue(desc, UntypedValue, 0, lvs...)
75
-		}),
76
-	}
77
-}
78
-
79
-// GetMetricWithLabelValues replaces the method of the same name in
80
-// MetricVec. The difference is that this method returns an Untyped and not a
81
-// Metric so that no type conversion is required.
82
-func (m *UntypedVec) GetMetricWithLabelValues(lvs ...string) (Untyped, error) {
83
-	metric, err := m.MetricVec.GetMetricWithLabelValues(lvs...)
84
-	if metric != nil {
85
-		return metric.(Untyped), err
86
-	}
87
-	return nil, err
88
-}
89
-
90
-// GetMetricWith replaces the method of the same name in MetricVec. The
91
-// difference is that this method returns an Untyped and not a Metric so that no
92
-// type conversion is required.
93
-func (m *UntypedVec) GetMetricWith(labels Labels) (Untyped, error) {
94
-	metric, err := m.MetricVec.GetMetricWith(labels)
95
-	if metric != nil {
96
-		return metric.(Untyped), err
97
-	}
98
-	return nil, err
99
-}
100
-
101
-// WithLabelValues works as GetMetricWithLabelValues, but panics where
102
-// GetMetricWithLabelValues would have returned an error. By not returning an
103
-// error, WithLabelValues allows shortcuts like
104
-//     myVec.WithLabelValues("404", "GET").Add(42)
105
-func (m *UntypedVec) WithLabelValues(lvs ...string) Untyped {
106
-	return m.MetricVec.WithLabelValues(lvs...).(Untyped)
107
-}
108
-
109
-// With works as GetMetricWith, but panics where GetMetricWithLabels would have
110
-// returned an error. By not returning an error, With allows shortcuts like
111
-//     myVec.With(Labels{"code": "404", "method": "GET"}).Add(42)
112
-func (m *UntypedVec) With(labels Labels) Untyped {
113
-	return m.MetricVec.With(labels).(Untyped)
114
-}
115
-
116
-// UntypedFunc is an Untyped whose value is determined at collect time by
117
-// calling a provided function.
19
+// UntypedFunc works like GaugeFunc but the collected metric is of type
20
+// "Untyped". UntypedFunc is useful to mirror an external metric of unknown
21
+// type.
118 22
 //
119 23
 // To create UntypedFunc instances, use NewUntypedFunc.
120 24
 type UntypedFunc interface {
... ...
@@ -14,15 +14,12 @@
14 14
 package prometheus
15 15
 
16 16
 import (
17
-	"errors"
18 17
 	"fmt"
19
-	"math"
20 18
 	"sort"
21
-	"sync/atomic"
22
-
23
-	dto "github.com/prometheus/client_model/go"
24 19
 
25 20
 	"github.com/golang/protobuf/proto"
21
+
22
+	dto "github.com/prometheus/client_model/go"
26 23
 )
27 24
 
28 25
 // ValueType is an enumeration of metric types that represent a simple value.
... ...
@@ -36,77 +33,6 @@ const (
36 36
 	UntypedValue
37 37
 )
38 38
 
39
-var errInconsistentCardinality = errors.New("inconsistent label cardinality")
40
-
41
-// value is a generic metric for simple values. It implements Metric, Collector,
42
-// Counter, Gauge, and Untyped. Its effective type is determined by
43
-// ValueType. This is a low-level building block used by the library to back the
44
-// implementations of Counter, Gauge, and Untyped.
45
-type value struct {
46
-	// valBits containst the bits of the represented float64 value. It has
47
-	// to go first in the struct to guarantee alignment for atomic
48
-	// operations.  http://golang.org/pkg/sync/atomic/#pkg-note-BUG
49
-	valBits uint64
50
-
51
-	selfCollector
52
-
53
-	desc       *Desc
54
-	valType    ValueType
55
-	labelPairs []*dto.LabelPair
56
-}
57
-
58
-// newValue returns a newly allocated value with the given Desc, ValueType,
59
-// sample value and label values. It panics if the number of label
60
-// values is different from the number of variable labels in Desc.
61
-func newValue(desc *Desc, valueType ValueType, val float64, labelValues ...string) *value {
62
-	if len(labelValues) != len(desc.variableLabels) {
63
-		panic(errInconsistentCardinality)
64
-	}
65
-	result := &value{
66
-		desc:       desc,
67
-		valType:    valueType,
68
-		valBits:    math.Float64bits(val),
69
-		labelPairs: makeLabelPairs(desc, labelValues),
70
-	}
71
-	result.init(result)
72
-	return result
73
-}
74
-
75
-func (v *value) Desc() *Desc {
76
-	return v.desc
77
-}
78
-
79
-func (v *value) Set(val float64) {
80
-	atomic.StoreUint64(&v.valBits, math.Float64bits(val))
81
-}
82
-
83
-func (v *value) Inc() {
84
-	v.Add(1)
85
-}
86
-
87
-func (v *value) Dec() {
88
-	v.Add(-1)
89
-}
90
-
91
-func (v *value) Add(val float64) {
92
-	for {
93
-		oldBits := atomic.LoadUint64(&v.valBits)
94
-		newBits := math.Float64bits(math.Float64frombits(oldBits) + val)
95
-		if atomic.CompareAndSwapUint64(&v.valBits, oldBits, newBits) {
96
-			return
97
-		}
98
-	}
99
-}
100
-
101
-func (v *value) Sub(val float64) {
102
-	v.Add(val * -1)
103
-}
104
-
105
-func (v *value) Write(out *dto.Metric) error {
106
-	val := math.Float64frombits(atomic.LoadUint64(&v.valBits))
107
-	return populateMetric(v.valType, val, v.labelPairs, out)
108
-}
109
-
110 39
 // valueFunc is a generic metric for simple values retrieved on collect time
111 40
 // from a function. It implements Metric and Collector. Its effective type is
112 41
 // determined by ValueType. This is a low-level building block used by the
... ...
@@ -151,10 +77,14 @@ func (v *valueFunc) Write(out *dto.Metric) error {
151 151
 // operations. However, when implementing custom Collectors, it is useful as a
152 152
 // throw-away metric that is generated on the fly to send it to Prometheus in
153 153
 // the Collect method. NewConstMetric returns an error if the length of
154
-// labelValues is not consistent with the variable labels in Desc.
154
+// labelValues is not consistent with the variable labels in Desc or if Desc is
155
+// invalid.
155 156
 func NewConstMetric(desc *Desc, valueType ValueType, value float64, labelValues ...string) (Metric, error) {
156
-	if len(desc.variableLabels) != len(labelValues) {
157
-		return nil, errInconsistentCardinality
157
+	if desc.err != nil {
158
+		return nil, desc.err
159
+	}
160
+	if err := validateLabelValues(labelValues, len(desc.variableLabels)); err != nil {
161
+		return nil, err
158 162
 	}
159 163
 	return &constMetric{
160 164
 		desc:       desc,
... ...
@@ -226,9 +156,7 @@ func makeLabelPairs(desc *Desc, labelValues []string) []*dto.LabelPair {
226 226
 			Value: proto.String(labelValues[i]),
227 227
 		})
228 228
 	}
229
-	for _, lp := range desc.constLabelPairs {
230
-		labelPairs = append(labelPairs, lp)
231
-	}
232
-	sort.Sort(LabelPairSorter(labelPairs))
229
+	labelPairs = append(labelPairs, desc.constLabelPairs...)
230
+	sort.Sort(labelPairSorter(labelPairs))
233 231
 	return labelPairs
234 232
 }
... ...
@@ -20,200 +20,253 @@ import (
20 20
 	"github.com/prometheus/common/model"
21 21
 )
22 22
 
23
-// MetricVec is a Collector to bundle metrics of the same name that
24
-// differ in their label values. MetricVec is usually not used directly but as a
25
-// building block for implementations of vectors of a given metric
26
-// type. GaugeVec, CounterVec, SummaryVec, and UntypedVec are examples already
27
-// provided in this package.
28
-type MetricVec struct {
29
-	mtx      sync.RWMutex // Protects the children.
30
-	children map[uint64][]metricWithLabelValues
31
-	desc     *Desc
32
-
33
-	newMetric   func(labelValues ...string) Metric
34
-	hashAdd     func(h uint64, s string) uint64 // replace hash function for testing collision handling
23
+// metricVec is a Collector to bundle metrics of the same name that differ in
24
+// their label values. metricVec is not used directly (and therefore
25
+// unexported). It is used as a building block for implementations of vectors of
26
+// a given metric type, like GaugeVec, CounterVec, SummaryVec, and HistogramVec.
27
+// It also handles label currying. It uses basicMetricVec internally.
28
+type metricVec struct {
29
+	*metricMap
30
+
31
+	curry []curriedLabelValue
32
+
33
+	// hashAdd and hashAddByte can be replaced for testing collision handling.
34
+	hashAdd     func(h uint64, s string) uint64
35 35
 	hashAddByte func(h uint64, b byte) uint64
36 36
 }
37 37
 
38
-// newMetricVec returns an initialized MetricVec. The concrete value is
39
-// returned for embedding into another struct.
40
-func newMetricVec(desc *Desc, newMetric func(lvs ...string) Metric) *MetricVec {
41
-	return &MetricVec{
42
-		children:    map[uint64][]metricWithLabelValues{},
43
-		desc:        desc,
44
-		newMetric:   newMetric,
38
+// newMetricVec returns an initialized metricVec.
39
+func newMetricVec(desc *Desc, newMetric func(lvs ...string) Metric) *metricVec {
40
+	return &metricVec{
41
+		metricMap: &metricMap{
42
+			metrics:   map[uint64][]metricWithLabelValues{},
43
+			desc:      desc,
44
+			newMetric: newMetric,
45
+		},
45 46
 		hashAdd:     hashAdd,
46 47
 		hashAddByte: hashAddByte,
47 48
 	}
48 49
 }
49 50
 
50
-// metricWithLabelValues provides the metric and its label values for
51
-// disambiguation on hash collision.
52
-type metricWithLabelValues struct {
53
-	values []string
54
-	metric Metric
55
-}
51
+// DeleteLabelValues removes the metric where the variable labels are the same
52
+// as those passed in as labels (same order as the VariableLabels in Desc). It
53
+// returns true if a metric was deleted.
54
+//
55
+// It is not an error if the number of label values is not the same as the
56
+// number of VariableLabels in Desc. However, such inconsistent label count can
57
+// never match an actual metric, so the method will always return false in that
58
+// case.
59
+//
60
+// Note that for more than one label value, this method is prone to mistakes
61
+// caused by an incorrect order of arguments. Consider Delete(Labels) as an
62
+// alternative to avoid that type of mistake. For higher label numbers, the
63
+// latter has a much more readable (albeit more verbose) syntax, but it comes
64
+// with a performance overhead (for creating and processing the Labels map).
65
+// See also the CounterVec example.
66
+func (m *metricVec) DeleteLabelValues(lvs ...string) bool {
67
+	h, err := m.hashLabelValues(lvs)
68
+	if err != nil {
69
+		return false
70
+	}
56 71
 
57
-// Describe implements Collector. The length of the returned slice
58
-// is always one.
59
-func (m *MetricVec) Describe(ch chan<- *Desc) {
60
-	ch <- m.desc
72
+	return m.metricMap.deleteByHashWithLabelValues(h, lvs, m.curry)
61 73
 }
62 74
 
63
-// Collect implements Collector.
64
-func (m *MetricVec) Collect(ch chan<- Metric) {
65
-	m.mtx.RLock()
66
-	defer m.mtx.RUnlock()
75
+// Delete deletes the metric where the variable labels are the same as those
76
+// passed in as labels. It returns true if a metric was deleted.
77
+//
78
+// It is not an error if the number and names of the Labels are inconsistent
79
+// with those of the VariableLabels in Desc. However, such inconsistent Labels
80
+// can never match an actual metric, so the method will always return false in
81
+// that case.
82
+//
83
+// This method is used for the same purpose as DeleteLabelValues(...string). See
84
+// there for pros and cons of the two methods.
85
+func (m *metricVec) Delete(labels Labels) bool {
86
+	h, err := m.hashLabels(labels)
87
+	if err != nil {
88
+		return false
89
+	}
67 90
 
68
-	for _, metrics := range m.children {
69
-		for _, metric := range metrics {
70
-			ch <- metric.metric
91
+	return m.metricMap.deleteByHashWithLabels(h, labels, m.curry)
92
+}
93
+
94
+func (m *metricVec) curryWith(labels Labels) (*metricVec, error) {
95
+	var (
96
+		newCurry []curriedLabelValue
97
+		oldCurry = m.curry
98
+		iCurry   int
99
+	)
100
+	for i, label := range m.desc.variableLabels {
101
+		val, ok := labels[label]
102
+		if iCurry < len(oldCurry) && oldCurry[iCurry].index == i {
103
+			if ok {
104
+				return nil, fmt.Errorf("label name %q is already curried", label)
105
+			}
106
+			newCurry = append(newCurry, oldCurry[iCurry])
107
+			iCurry++
108
+		} else {
109
+			if !ok {
110
+				continue // Label stays uncurried.
111
+			}
112
+			newCurry = append(newCurry, curriedLabelValue{i, val})
71 113
 		}
72 114
 	}
115
+	if l := len(oldCurry) + len(labels) - len(newCurry); l > 0 {
116
+		return nil, fmt.Errorf("%d unknown label(s) found during currying", l)
117
+	}
118
+
119
+	return &metricVec{
120
+		metricMap:   m.metricMap,
121
+		curry:       newCurry,
122
+		hashAdd:     m.hashAdd,
123
+		hashAddByte: m.hashAddByte,
124
+	}, nil
73 125
 }
74 126
 
75
-// GetMetricWithLabelValues returns the Metric for the given slice of label
76
-// values (same order as the VariableLabels in Desc). If that combination of
77
-// label values is accessed for the first time, a new Metric is created.
78
-//
79
-// It is possible to call this method without using the returned Metric to only
80
-// create the new Metric but leave it at its start value (e.g. a Summary or
81
-// Histogram without any observations). See also the SummaryVec example.
82
-//
83
-// Keeping the Metric for later use is possible (and should be considered if
84
-// performance is critical), but keep in mind that Reset, DeleteLabelValues and
85
-// Delete can be used to delete the Metric from the MetricVec. In that case, the
86
-// Metric will still exist, but it will not be exported anymore, even if a
87
-// Metric with the same label values is created later. See also the CounterVec
88
-// example.
89
-//
90
-// An error is returned if the number of label values is not the same as the
91
-// number of VariableLabels in Desc.
92
-//
93
-// Note that for more than one label value, this method is prone to mistakes
94
-// caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as
95
-// an alternative to avoid that type of mistake. For higher label numbers, the
96
-// latter has a much more readable (albeit more verbose) syntax, but it comes
97
-// with a performance overhead (for creating and processing the Labels map).
98
-// See also the GaugeVec example.
99
-func (m *MetricVec) GetMetricWithLabelValues(lvs ...string) (Metric, error) {
127
+func (m *metricVec) getMetricWithLabelValues(lvs ...string) (Metric, error) {
100 128
 	h, err := m.hashLabelValues(lvs)
101 129
 	if err != nil {
102 130
 		return nil, err
103 131
 	}
104 132
 
105
-	return m.getOrCreateMetricWithLabelValues(h, lvs), nil
133
+	return m.metricMap.getOrCreateMetricWithLabelValues(h, lvs, m.curry), nil
106 134
 }
107 135
 
108
-// GetMetricWith returns the Metric for the given Labels map (the label names
109
-// must match those of the VariableLabels in Desc). If that label map is
110
-// accessed for the first time, a new Metric is created. Implications of
111
-// creating a Metric without using it and keeping the Metric for later use are
112
-// the same as for GetMetricWithLabelValues.
113
-//
114
-// An error is returned if the number and names of the Labels are inconsistent
115
-// with those of the VariableLabels in Desc.
116
-//
117
-// This method is used for the same purpose as
118
-// GetMetricWithLabelValues(...string). See there for pros and cons of the two
119
-// methods.
120
-func (m *MetricVec) GetMetricWith(labels Labels) (Metric, error) {
136
+func (m *metricVec) getMetricWith(labels Labels) (Metric, error) {
121 137
 	h, err := m.hashLabels(labels)
122 138
 	if err != nil {
123 139
 		return nil, err
124 140
 	}
125 141
 
126
-	return m.getOrCreateMetricWithLabels(h, labels), nil
142
+	return m.metricMap.getOrCreateMetricWithLabels(h, labels, m.curry), nil
127 143
 }
128 144
 
129
-// WithLabelValues works as GetMetricWithLabelValues, but panics if an error
130
-// occurs. The method allows neat syntax like:
131
-//     httpReqs.WithLabelValues("404", "POST").Inc()
132
-func (m *MetricVec) WithLabelValues(lvs ...string) Metric {
133
-	metric, err := m.GetMetricWithLabelValues(lvs...)
134
-	if err != nil {
135
-		panic(err)
145
+func (m *metricVec) hashLabelValues(vals []string) (uint64, error) {
146
+	if err := validateLabelValues(vals, len(m.desc.variableLabels)-len(m.curry)); err != nil {
147
+		return 0, err
148
+	}
149
+
150
+	var (
151
+		h             = hashNew()
152
+		curry         = m.curry
153
+		iVals, iCurry int
154
+	)
155
+	for i := 0; i < len(m.desc.variableLabels); i++ {
156
+		if iCurry < len(curry) && curry[iCurry].index == i {
157
+			h = m.hashAdd(h, curry[iCurry].value)
158
+			iCurry++
159
+		} else {
160
+			h = m.hashAdd(h, vals[iVals])
161
+			iVals++
162
+		}
163
+		h = m.hashAddByte(h, model.SeparatorByte)
136 164
 	}
137
-	return metric
165
+	return h, nil
138 166
 }
139 167
 
140
-// With works as GetMetricWith, but panics if an error occurs. The method allows
141
-// neat syntax like:
142
-//     httpReqs.With(Labels{"status":"404", "method":"POST"}).Inc()
143
-func (m *MetricVec) With(labels Labels) Metric {
144
-	metric, err := m.GetMetricWith(labels)
145
-	if err != nil {
146
-		panic(err)
168
+func (m *metricVec) hashLabels(labels Labels) (uint64, error) {
169
+	if err := validateValuesInLabels(labels, len(m.desc.variableLabels)-len(m.curry)); err != nil {
170
+		return 0, err
147 171
 	}
148
-	return metric
172
+
173
+	var (
174
+		h      = hashNew()
175
+		curry  = m.curry
176
+		iCurry int
177
+	)
178
+	for i, label := range m.desc.variableLabels {
179
+		val, ok := labels[label]
180
+		if iCurry < len(curry) && curry[iCurry].index == i {
181
+			if ok {
182
+				return 0, fmt.Errorf("label name %q is already curried", label)
183
+			}
184
+			h = m.hashAdd(h, curry[iCurry].value)
185
+			iCurry++
186
+		} else {
187
+			if !ok {
188
+				return 0, fmt.Errorf("label name %q missing in label map", label)
189
+			}
190
+			h = m.hashAdd(h, val)
191
+		}
192
+		h = m.hashAddByte(h, model.SeparatorByte)
193
+	}
194
+	return h, nil
149 195
 }
150 196
 
151
-// DeleteLabelValues removes the metric where the variable labels are the same
152
-// as those passed in as labels (same order as the VariableLabels in Desc). It
153
-// returns true if a metric was deleted.
154
-//
155
-// It is not an error if the number of label values is not the same as the
156
-// number of VariableLabels in Desc.  However, such inconsistent label count can
157
-// never match an actual Metric, so the method will always return false in that
158
-// case.
159
-//
160
-// Note that for more than one label value, this method is prone to mistakes
161
-// caused by an incorrect order of arguments. Consider Delete(Labels) as an
162
-// alternative to avoid that type of mistake. For higher label numbers, the
163
-// latter has a much more readable (albeit more verbose) syntax, but it comes
164
-// with a performance overhead (for creating and processing the Labels map).
165
-// See also the CounterVec example.
166
-func (m *MetricVec) DeleteLabelValues(lvs ...string) bool {
167
-	m.mtx.Lock()
168
-	defer m.mtx.Unlock()
197
+// metricWithLabelValues provides the metric and its label values for
198
+// disambiguation on hash collision.
199
+type metricWithLabelValues struct {
200
+	values []string
201
+	metric Metric
202
+}
169 203
 
170
-	h, err := m.hashLabelValues(lvs)
171
-	if err != nil {
172
-		return false
204
+// curriedLabelValue sets the curried value for a label at the given index.
205
+type curriedLabelValue struct {
206
+	index int
207
+	value string
208
+}
209
+
210
+// metricMap is a helper for metricVec and shared between differently curried
211
+// metricVecs.
212
+type metricMap struct {
213
+	mtx       sync.RWMutex // Protects metrics.
214
+	metrics   map[uint64][]metricWithLabelValues
215
+	desc      *Desc
216
+	newMetric func(labelValues ...string) Metric
217
+}
218
+
219
+// Describe implements Collector. It will send exactly one Desc to the provided
220
+// channel.
221
+func (m *metricMap) Describe(ch chan<- *Desc) {
222
+	ch <- m.desc
223
+}
224
+
225
+// Collect implements Collector.
226
+func (m *metricMap) Collect(ch chan<- Metric) {
227
+	m.mtx.RLock()
228
+	defer m.mtx.RUnlock()
229
+
230
+	for _, metrics := range m.metrics {
231
+		for _, metric := range metrics {
232
+			ch <- metric.metric
233
+		}
173 234
 	}
174
-	return m.deleteByHashWithLabelValues(h, lvs)
175 235
 }
176 236
 
177
-// Delete deletes the metric where the variable labels are the same as those
178
-// passed in as labels. It returns true if a metric was deleted.
179
-//
180
-// It is not an error if the number and names of the Labels are inconsistent
181
-// with those of the VariableLabels in the Desc of the MetricVec. However, such
182
-// inconsistent Labels can never match an actual Metric, so the method will
183
-// always return false in that case.
184
-//
185
-// This method is used for the same purpose as DeleteLabelValues(...string). See
186
-// there for pros and cons of the two methods.
187
-func (m *MetricVec) Delete(labels Labels) bool {
237
+// Reset deletes all metrics in this vector.
238
+func (m *metricMap) Reset() {
188 239
 	m.mtx.Lock()
189 240
 	defer m.mtx.Unlock()
190 241
 
191
-	h, err := m.hashLabels(labels)
192
-	if err != nil {
193
-		return false
242
+	for h := range m.metrics {
243
+		delete(m.metrics, h)
194 244
 	}
195
-
196
-	return m.deleteByHashWithLabels(h, labels)
197 245
 }
198 246
 
199 247
 // deleteByHashWithLabelValues removes the metric from the hash bucket h. If
200 248
 // there are multiple matches in the bucket, use lvs to select a metric and
201 249
 // remove only that metric.
202
-func (m *MetricVec) deleteByHashWithLabelValues(h uint64, lvs []string) bool {
203
-	metrics, ok := m.children[h]
250
+func (m *metricMap) deleteByHashWithLabelValues(
251
+	h uint64, lvs []string, curry []curriedLabelValue,
252
+) bool {
253
+	m.mtx.Lock()
254
+	defer m.mtx.Unlock()
255
+
256
+	metrics, ok := m.metrics[h]
204 257
 	if !ok {
205 258
 		return false
206 259
 	}
207 260
 
208
-	i := m.findMetricWithLabelValues(metrics, lvs)
261
+	i := findMetricWithLabelValues(metrics, lvs, curry)
209 262
 	if i >= len(metrics) {
210 263
 		return false
211 264
 	}
212 265
 
213 266
 	if len(metrics) > 1 {
214
-		m.children[h] = append(metrics[:i], metrics[i+1:]...)
267
+		m.metrics[h] = append(metrics[:i], metrics[i+1:]...)
215 268
 	} else {
216
-		delete(m.children, h)
269
+		delete(m.metrics, h)
217 270
 	}
218 271
 	return true
219 272
 }
... ...
@@ -221,69 +274,38 @@ func (m *MetricVec) deleteByHashWithLabelValues(h uint64, lvs []string) bool {
221 221
 // deleteByHashWithLabels removes the metric from the hash bucket h. If there
222 222
 // are multiple matches in the bucket, use lvs to select a metric and remove
223 223
 // only that metric.
224
-func (m *MetricVec) deleteByHashWithLabels(h uint64, labels Labels) bool {
225
-	metrics, ok := m.children[h]
224
+func (m *metricMap) deleteByHashWithLabels(
225
+	h uint64, labels Labels, curry []curriedLabelValue,
226
+) bool {
227
+	m.mtx.Lock()
228
+	defer m.mtx.Unlock()
229
+
230
+	metrics, ok := m.metrics[h]
226 231
 	if !ok {
227 232
 		return false
228 233
 	}
229
-	i := m.findMetricWithLabels(metrics, labels)
234
+	i := findMetricWithLabels(m.desc, metrics, labels, curry)
230 235
 	if i >= len(metrics) {
231 236
 		return false
232 237
 	}
233 238
 
234 239
 	if len(metrics) > 1 {
235
-		m.children[h] = append(metrics[:i], metrics[i+1:]...)
240
+		m.metrics[h] = append(metrics[:i], metrics[i+1:]...)
236 241
 	} else {
237
-		delete(m.children, h)
242
+		delete(m.metrics, h)
238 243
 	}
239 244
 	return true
240 245
 }
241 246
 
242
-// Reset deletes all metrics in this vector.
243
-func (m *MetricVec) Reset() {
244
-	m.mtx.Lock()
245
-	defer m.mtx.Unlock()
246
-
247
-	for h := range m.children {
248
-		delete(m.children, h)
249
-	}
250
-}
251
-
252
-func (m *MetricVec) hashLabelValues(vals []string) (uint64, error) {
253
-	if len(vals) != len(m.desc.variableLabels) {
254
-		return 0, errInconsistentCardinality
255
-	}
256
-	h := hashNew()
257
-	for _, val := range vals {
258
-		h = m.hashAdd(h, val)
259
-		h = m.hashAddByte(h, model.SeparatorByte)
260
-	}
261
-	return h, nil
262
-}
263
-
264
-func (m *MetricVec) hashLabels(labels Labels) (uint64, error) {
265
-	if len(labels) != len(m.desc.variableLabels) {
266
-		return 0, errInconsistentCardinality
267
-	}
268
-	h := hashNew()
269
-	for _, label := range m.desc.variableLabels {
270
-		val, ok := labels[label]
271
-		if !ok {
272
-			return 0, fmt.Errorf("label name %q missing in label map", label)
273
-		}
274
-		h = m.hashAdd(h, val)
275
-		h = m.hashAddByte(h, model.SeparatorByte)
276
-	}
277
-	return h, nil
278
-}
279
-
280 247
 // getOrCreateMetricWithLabelValues retrieves the metric by hash and label value
281 248
 // or creates it and returns the new one.
282 249
 //
283 250
 // This function holds the mutex.
284
-func (m *MetricVec) getOrCreateMetricWithLabelValues(hash uint64, lvs []string) Metric {
251
+func (m *metricMap) getOrCreateMetricWithLabelValues(
252
+	hash uint64, lvs []string, curry []curriedLabelValue,
253
+) Metric {
285 254
 	m.mtx.RLock()
286
-	metric, ok := m.getMetricWithLabelValues(hash, lvs)
255
+	metric, ok := m.getMetricWithHashAndLabelValues(hash, lvs, curry)
287 256
 	m.mtx.RUnlock()
288 257
 	if ok {
289 258
 		return metric
... ...
@@ -291,13 +313,11 @@ func (m *MetricVec) getOrCreateMetricWithLabelValues(hash uint64, lvs []string)
291 291
 
292 292
 	m.mtx.Lock()
293 293
 	defer m.mtx.Unlock()
294
-	metric, ok = m.getMetricWithLabelValues(hash, lvs)
294
+	metric, ok = m.getMetricWithHashAndLabelValues(hash, lvs, curry)
295 295
 	if !ok {
296
-		// Copy to avoid allocation in case wo don't go down this code path.
297
-		copiedLVs := make([]string, len(lvs))
298
-		copy(copiedLVs, lvs)
299
-		metric = m.newMetric(copiedLVs...)
300
-		m.children[hash] = append(m.children[hash], metricWithLabelValues{values: copiedLVs, metric: metric})
296
+		inlinedLVs := inlineLabelValues(lvs, curry)
297
+		metric = m.newMetric(inlinedLVs...)
298
+		m.metrics[hash] = append(m.metrics[hash], metricWithLabelValues{values: inlinedLVs, metric: metric})
301 299
 	}
302 300
 	return metric
303 301
 }
... ...
@@ -306,9 +326,11 @@ func (m *MetricVec) getOrCreateMetricWithLabelValues(hash uint64, lvs []string)
306 306
 // or creates it and returns the new one.
307 307
 //
308 308
 // This function holds the mutex.
309
-func (m *MetricVec) getOrCreateMetricWithLabels(hash uint64, labels Labels) Metric {
309
+func (m *metricMap) getOrCreateMetricWithLabels(
310
+	hash uint64, labels Labels, curry []curriedLabelValue,
311
+) Metric {
310 312
 	m.mtx.RLock()
311
-	metric, ok := m.getMetricWithLabels(hash, labels)
313
+	metric, ok := m.getMetricWithHashAndLabels(hash, labels, curry)
312 314
 	m.mtx.RUnlock()
313 315
 	if ok {
314 316
 		return metric
... ...
@@ -316,33 +338,37 @@ func (m *MetricVec) getOrCreateMetricWithLabels(hash uint64, labels Labels) Metr
316 316
 
317 317
 	m.mtx.Lock()
318 318
 	defer m.mtx.Unlock()
319
-	metric, ok = m.getMetricWithLabels(hash, labels)
319
+	metric, ok = m.getMetricWithHashAndLabels(hash, labels, curry)
320 320
 	if !ok {
321
-		lvs := m.extractLabelValues(labels)
321
+		lvs := extractLabelValues(m.desc, labels, curry)
322 322
 		metric = m.newMetric(lvs...)
323
-		m.children[hash] = append(m.children[hash], metricWithLabelValues{values: lvs, metric: metric})
323
+		m.metrics[hash] = append(m.metrics[hash], metricWithLabelValues{values: lvs, metric: metric})
324 324
 	}
325 325
 	return metric
326 326
 }
327 327
 
328
-// getMetricWithLabelValues gets a metric while handling possible collisions in
329
-// the hash space. Must be called while holding read mutex.
330
-func (m *MetricVec) getMetricWithLabelValues(h uint64, lvs []string) (Metric, bool) {
331
-	metrics, ok := m.children[h]
328
+// getMetricWithHashAndLabelValues gets a metric while handling possible
329
+// collisions in the hash space. Must be called while holding the read mutex.
330
+func (m *metricMap) getMetricWithHashAndLabelValues(
331
+	h uint64, lvs []string, curry []curriedLabelValue,
332
+) (Metric, bool) {
333
+	metrics, ok := m.metrics[h]
332 334
 	if ok {
333
-		if i := m.findMetricWithLabelValues(metrics, lvs); i < len(metrics) {
335
+		if i := findMetricWithLabelValues(metrics, lvs, curry); i < len(metrics) {
334 336
 			return metrics[i].metric, true
335 337
 		}
336 338
 	}
337 339
 	return nil, false
338 340
 }
339 341
 
340
-// getMetricWithLabels gets a metric while handling possible collisions in
342
+// getMetricWithHashAndLabels gets a metric while handling possible collisions in
341 343
 // the hash space. Must be called while holding read mutex.
342
-func (m *MetricVec) getMetricWithLabels(h uint64, labels Labels) (Metric, bool) {
343
-	metrics, ok := m.children[h]
344
+func (m *metricMap) getMetricWithHashAndLabels(
345
+	h uint64, labels Labels, curry []curriedLabelValue,
346
+) (Metric, bool) {
347
+	metrics, ok := m.metrics[h]
344 348
 	if ok {
345
-		if i := m.findMetricWithLabels(metrics, labels); i < len(metrics) {
349
+		if i := findMetricWithLabels(m.desc, metrics, labels, curry); i < len(metrics) {
346 350
 			return metrics[i].metric, true
347 351
 		}
348 352
 	}
... ...
@@ -351,9 +377,11 @@ func (m *MetricVec) getMetricWithLabels(h uint64, labels Labels) (Metric, bool)
351 351
 
352 352
 // findMetricWithLabelValues returns the index of the matching metric or
353 353
 // len(metrics) if not found.
354
-func (m *MetricVec) findMetricWithLabelValues(metrics []metricWithLabelValues, lvs []string) int {
354
+func findMetricWithLabelValues(
355
+	metrics []metricWithLabelValues, lvs []string, curry []curriedLabelValue,
356
+) int {
355 357
 	for i, metric := range metrics {
356
-		if m.matchLabelValues(metric.values, lvs) {
358
+		if matchLabelValues(metric.values, lvs, curry) {
357 359
 			return i
358 360
 		}
359 361
 	}
... ...
@@ -362,32 +390,51 @@ func (m *MetricVec) findMetricWithLabelValues(metrics []metricWithLabelValues, l
362 362
 
363 363
 // findMetricWithLabels returns the index of the matching metric or len(metrics)
364 364
 // if not found.
365
-func (m *MetricVec) findMetricWithLabels(metrics []metricWithLabelValues, labels Labels) int {
365
+func findMetricWithLabels(
366
+	desc *Desc, metrics []metricWithLabelValues, labels Labels, curry []curriedLabelValue,
367
+) int {
366 368
 	for i, metric := range metrics {
367
-		if m.matchLabels(metric.values, labels) {
369
+		if matchLabels(desc, metric.values, labels, curry) {
368 370
 			return i
369 371
 		}
370 372
 	}
371 373
 	return len(metrics)
372 374
 }
373 375
 
374
-func (m *MetricVec) matchLabelValues(values []string, lvs []string) bool {
375
-	if len(values) != len(lvs) {
376
+func matchLabelValues(values []string, lvs []string, curry []curriedLabelValue) bool {
377
+	if len(values) != len(lvs)+len(curry) {
376 378
 		return false
377 379
 	}
380
+	var iLVs, iCurry int
378 381
 	for i, v := range values {
379
-		if v != lvs[i] {
382
+		if iCurry < len(curry) && curry[iCurry].index == i {
383
+			if v != curry[iCurry].value {
384
+				return false
385
+			}
386
+			iCurry++
387
+			continue
388
+		}
389
+		if v != lvs[iLVs] {
380 390
 			return false
381 391
 		}
392
+		iLVs++
382 393
 	}
383 394
 	return true
384 395
 }
385 396
 
386
-func (m *MetricVec) matchLabels(values []string, labels Labels) bool {
387
-	if len(labels) != len(values) {
397
+func matchLabels(desc *Desc, values []string, labels Labels, curry []curriedLabelValue) bool {
398
+	if len(values) != len(labels)+len(curry) {
388 399
 		return false
389 400
 	}
390
-	for i, k := range m.desc.variableLabels {
401
+	iCurry := 0
402
+	for i, k := range desc.variableLabels {
403
+		if iCurry < len(curry) && curry[iCurry].index == i {
404
+			if values[i] != curry[iCurry].value {
405
+				return false
406
+			}
407
+			iCurry++
408
+			continue
409
+		}
391 410
 		if values[i] != labels[k] {
392 411
 			return false
393 412
 		}
... ...
@@ -395,10 +442,31 @@ func (m *MetricVec) matchLabels(values []string, labels Labels) bool {
395 395
 	return true
396 396
 }
397 397
 
398
-func (m *MetricVec) extractLabelValues(labels Labels) []string {
399
-	labelValues := make([]string, len(labels))
400
-	for i, k := range m.desc.variableLabels {
398
+func extractLabelValues(desc *Desc, labels Labels, curry []curriedLabelValue) []string {
399
+	labelValues := make([]string, len(labels)+len(curry))
400
+	iCurry := 0
401
+	for i, k := range desc.variableLabels {
402
+		if iCurry < len(curry) && curry[iCurry].index == i {
403
+			labelValues[i] = curry[iCurry].value
404
+			iCurry++
405
+			continue
406
+		}
401 407
 		labelValues[i] = labels[k]
402 408
 	}
403 409
 	return labelValues
404 410
 }
411
+
412
+func inlineLabelValues(lvs []string, curry []curriedLabelValue) []string {
413
+	labelValues := make([]string, len(lvs)+len(curry))
414
+	var iCurry, iLVs int
415
+	for i := range labelValues {
416
+		if iCurry < len(curry) && curry[iCurry].index == i {
417
+			labelValues[i] = curry[iCurry].value
418
+			iCurry++
419
+			continue
420
+		}
421
+		labelValues[i] = lvs[iLVs]
422
+		iLVs++
423
+	}
424
+	return labelValues
425
+}
405 426
new file mode 100644
... ...
@@ -0,0 +1,179 @@
0
+// Copyright 2018 The Prometheus Authors
1
+// Licensed under the Apache License, Version 2.0 (the "License");
2
+// you may not use this file except in compliance with the License.
3
+// You may obtain a copy of the License at
4
+//
5
+// http://www.apache.org/licenses/LICENSE-2.0
6
+//
7
+// Unless required by applicable law or agreed to in writing, software
8
+// distributed under the License is distributed on an "AS IS" BASIS,
9
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+// See the License for the specific language governing permissions and
11
+// limitations under the License.
12
+
13
+package prometheus
14
+
15
+import (
16
+	"fmt"
17
+	"sort"
18
+
19
+	"github.com/golang/protobuf/proto"
20
+
21
+	dto "github.com/prometheus/client_model/go"
22
+)
23
+
24
+// WrapRegistererWith returns a Registerer wrapping the provided
25
+// Registerer. Collectors registered with the returned Registerer will be
26
+// registered with the wrapped Registerer in a modified way. The modified
27
+// Collector adds the provided Labels to all Metrics it collects (as
28
+// ConstLabels). The Metrics collected by the unmodified Collector must not
29
+// duplicate any of those labels.
30
+//
31
+// WrapRegistererWith provides a way to add fixed labels to a subset of
32
+// Collectors. It should not be used to add fixed labels to all metrics exposed.
33
+//
34
+// The Collector example demonstrates a use of WrapRegistererWith.
35
+func WrapRegistererWith(labels Labels, reg Registerer) Registerer {
36
+	return &wrappingRegisterer{
37
+		wrappedRegisterer: reg,
38
+		labels:            labels,
39
+	}
40
+}
41
+
42
+// WrapRegistererWithPrefix returns a Registerer wrapping the provided
43
+// Registerer. Collectors registered with the returned Registerer will be
44
+// registered with the wrapped Registerer in a modified way. The modified
45
+// Collector adds the provided prefix to the name of all Metrics it collects.
46
+//
47
+// WrapRegistererWithPrefix is useful to have one place to prefix all metrics of
48
+// a sub-system. To make this work, register metrics of the sub-system with the
49
+// wrapping Registerer returned by WrapRegistererWithPrefix. It is rarely useful
50
+// to use the same prefix for all metrics exposed. In particular, do not prefix
51
+// metric names that are standardized across applications, as that would break
52
+// horizontal monitoring, for example the metrics provided by the Go collector
53
+// (see NewGoCollector) and the process collector (see NewProcessCollector). (In
54
+// fact, those metrics are already prefixed with “go_” or “process_”,
55
+// respectively.)
56
+func WrapRegistererWithPrefix(prefix string, reg Registerer) Registerer {
57
+	return &wrappingRegisterer{
58
+		wrappedRegisterer: reg,
59
+		prefix:            prefix,
60
+	}
61
+}
62
+
63
+type wrappingRegisterer struct {
64
+	wrappedRegisterer Registerer
65
+	prefix            string
66
+	labels            Labels
67
+}
68
+
69
+func (r *wrappingRegisterer) Register(c Collector) error {
70
+	return r.wrappedRegisterer.Register(&wrappingCollector{
71
+		wrappedCollector: c,
72
+		prefix:           r.prefix,
73
+		labels:           r.labels,
74
+	})
75
+}
76
+
77
+func (r *wrappingRegisterer) MustRegister(cs ...Collector) {
78
+	for _, c := range cs {
79
+		if err := r.Register(c); err != nil {
80
+			panic(err)
81
+		}
82
+	}
83
+}
84
+
85
+func (r *wrappingRegisterer) Unregister(c Collector) bool {
86
+	return r.wrappedRegisterer.Unregister(&wrappingCollector{
87
+		wrappedCollector: c,
88
+		prefix:           r.prefix,
89
+		labels:           r.labels,
90
+	})
91
+}
92
+
93
+type wrappingCollector struct {
94
+	wrappedCollector Collector
95
+	prefix           string
96
+	labels           Labels
97
+}
98
+
99
+func (c *wrappingCollector) Collect(ch chan<- Metric) {
100
+	wrappedCh := make(chan Metric)
101
+	go func() {
102
+		c.wrappedCollector.Collect(wrappedCh)
103
+		close(wrappedCh)
104
+	}()
105
+	for m := range wrappedCh {
106
+		ch <- &wrappingMetric{
107
+			wrappedMetric: m,
108
+			prefix:        c.prefix,
109
+			labels:        c.labels,
110
+		}
111
+	}
112
+}
113
+
114
+func (c *wrappingCollector) Describe(ch chan<- *Desc) {
115
+	wrappedCh := make(chan *Desc)
116
+	go func() {
117
+		c.wrappedCollector.Describe(wrappedCh)
118
+		close(wrappedCh)
119
+	}()
120
+	for desc := range wrappedCh {
121
+		ch <- wrapDesc(desc, c.prefix, c.labels)
122
+	}
123
+}
124
+
125
+type wrappingMetric struct {
126
+	wrappedMetric Metric
127
+	prefix        string
128
+	labels        Labels
129
+}
130
+
131
+func (m *wrappingMetric) Desc() *Desc {
132
+	return wrapDesc(m.wrappedMetric.Desc(), m.prefix, m.labels)
133
+}
134
+
135
+func (m *wrappingMetric) Write(out *dto.Metric) error {
136
+	if err := m.wrappedMetric.Write(out); err != nil {
137
+		return err
138
+	}
139
+	if len(m.labels) == 0 {
140
+		// No wrapping labels.
141
+		return nil
142
+	}
143
+	for ln, lv := range m.labels {
144
+		out.Label = append(out.Label, &dto.LabelPair{
145
+			Name:  proto.String(ln),
146
+			Value: proto.String(lv),
147
+		})
148
+	}
149
+	sort.Sort(labelPairSorter(out.Label))
150
+	return nil
151
+}
152
+
153
+func wrapDesc(desc *Desc, prefix string, labels Labels) *Desc {
154
+	constLabels := Labels{}
155
+	for _, lp := range desc.constLabelPairs {
156
+		constLabels[*lp.Name] = *lp.Value
157
+	}
158
+	for ln, lv := range labels {
159
+		if _, alreadyUsed := constLabels[ln]; alreadyUsed {
160
+			return &Desc{
161
+				fqName:          desc.fqName,
162
+				help:            desc.help,
163
+				variableLabels:  desc.variableLabels,
164
+				constLabelPairs: desc.constLabelPairs,
165
+				err:             fmt.Errorf("attempted wrapping with already existing label name %q", ln),
166
+			}
167
+		}
168
+		constLabels[ln] = lv
169
+	}
170
+	// NewDesc will do remaining validations.
171
+	newDesc := NewDesc(prefix+desc.fqName, desc.help, desc.variableLabels, constLabels)
172
+	// Propagate errors if there was any. This will override any errer
173
+	// created by NewDesc above, i.e. earlier errors get precedence.
174
+	if desc.err != nil {
175
+		newDesc.err = desc.err
176
+	}
177
+	return newDesc
178
+}