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>
| ... | ... |
@@ -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 |
|
| ... | ... |
@@ -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 |
[](https://travis-ci.org/prometheus/client_golang) |
| 4 |
+[](https://goreportcard.com/report/github.com/prometheus/client_golang) |
|
| 5 |
+[](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 |
[](http://gocover.io/github.com/prometheus/client_golang/prometheus) [](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 |
-[](http://gocover.io/github.com/prometheus/client_golang/api/prometheus) [](https://godoc.org/github.com/prometheus/client_golang/api/prometheus) |
|
| 26 |
+[](http://gocover.io/github.com/prometheus/client_golang/api/prometheus/v1) [](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 |
+} |