pax_global_header00006660000000000000000000000064135450641050014515gustar00rootroot0000000000000052 comment=6af20e3a5340d5e6bde20c8a7a78699efe19ac0a golang-github-grpc-ecosystem-go-grpc-prometheus-1.2.0+git20191002.6af20e3/000077500000000000000000000000001354506410500253765ustar00rootroot00000000000000golang-github-grpc-ecosystem-go-grpc-prometheus-1.2.0+git20191002.6af20e3/.gitignore000066400000000000000000000062611354506410500273730ustar00rootroot00000000000000#vendor vendor/ # Created by .ignore support plugin (hsz.mobi) coverage.txt ### Go template # Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test *.prof ### Windows template # Windows image file caches Thumbs.db ehthumbs.db # Folder config file Desktop.ini # Recycle Bin used on file shares $RECYCLE.BIN/ # Windows Installer files *.cab *.msi *.msm *.msp # Windows shortcuts *.lnk ### Kate template # Swap Files # .*.kate-swp .swp.* ### SublimeText template # cache files for sublime text *.tmlanguage.cache *.tmPreferences.cache *.stTheme.cache # workspace files are user-specific *.sublime-workspace # project files should be checked into the repository, unless a significant # proportion of contributors will probably not be using SublimeText # *.sublime-project # sftp configuration file sftp-config.json ### Linux template *~ # temporary files which can be created if a process still has a handle open of a deleted file .fuse_hidden* # KDE directory preferences .directory # Linux trash folder which might appear on any partition or disk .Trash-* ### JetBrains template # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 # User-specific stuff: .idea .idea/tasks.xml .idea/dictionaries .idea/vcs.xml .idea/jsLibraryMappings.xml # Sensitive or high-churn files: .idea/dataSources.ids .idea/dataSources.xml .idea/dataSources.local.xml .idea/sqlDataSources.xml .idea/dynamic.xml .idea/uiDesigner.xml # Gradle: .idea/gradle.xml .idea/libraries # Mongo Explorer plugin: .idea/mongoSettings.xml ## File-based project format: *.iws ## Plugin-specific files: # IntelliJ /out/ # mpeltonen/sbt-idea plugin .idea_modules/ # JIRA plugin atlassian-ide-plugin.xml # Crashlytics plugin (for Android Studio and IntelliJ) com_crashlytics_export_strings.xml crashlytics.properties crashlytics-build.properties fabric.properties ### Xcode template # Xcode # # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore ## Build generated build/ DerivedData/ ## Various settings *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 xcuserdata/ ## Other *.moved-aside *.xccheckout *.xcscmblueprint ### Eclipse template .metadata bin/ tmp/ *.tmp *.bak *.swp *~.nib local.properties .settings/ .loadpath .recommenders # Eclipse Core .project # External tool builders .externalToolBuilders/ # Locally stored "Eclipse launch configurations" *.launch # PyDev specific (Python IDE for Eclipse) *.pydevproject # CDT-specific (C/C++ Development Tooling) .cproject # JDT-specific (Eclipse Java Development Tools) .classpath # Java annotation processor (APT) .factorypath # PDT-specific (PHP Development Tools) .buildpath # sbteclipse plugin .target # Tern plugin .tern-project # TeXlipse plugin .texlipse # STS (Spring Tool Suite) .springBeans # Code Recommenders .recommenders/ golang-github-grpc-ecosystem-go-grpc-prometheus-1.2.0+git20191002.6af20e3/.travis.yml000066400000000000000000000011431354506410500275060ustar00rootroot00000000000000sudo: false language: go go: - 1.9.x - 1.10.x - 1.11.x - 1.12.x - master env: - GO111MODULE=on install: # With Go modules (eg Go >= 1.11), it isn't necessary anymore to 'go get' the dependencies before running the tests. - if [[ $TRAVIS_GO_VERSION =~ ^1\.(7|8|9|10)\. ]]; then go get github.com/prometheus/client_golang/prometheus; fi - if [[ $TRAVIS_GO_VERSION =~ ^1\.(7|8|9|10)\. ]]; then go get google.golang.org/grpc; fi - if [[ $TRAVIS_GO_VERSION =~ ^1\.(7|8|9|10)\. ]]; then go get github.com/stretchr/testify; fi script: - make test after_success: - bash <(curl -s https://codecov.io/bash) golang-github-grpc-ecosystem-go-grpc-prometheus-1.2.0+git20191002.6af20e3/CHANGELOG.md000066400000000000000000000020661354506410500272130ustar00rootroot00000000000000# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] ### Added * Support for error unwrapping. (Supported for `github.com/pkg/errors` and native wrapping added in go1.13) ## [1.2.0](https://github.com/grpc-ecosystem/go-grpc-prometheus/releases/tag/v1.2.0) - 2018-06-04 ### Added * Require go 1.9 or later and test against these versions in CI. * Provide metrics object as `prometheus.Collector`, for conventional metric registration. * Support non-default/global Prometheus registry. * Allow configuring counters with `prometheus.CounterOpts`. ### Changed * Remove usage of deprecated `grpc.Code()`. * Remove usage of deprecated `grpc.Errorf` and replace with `status.Errorf`. --- This changelog was started with version `v1.2.0`, for earlier versions refer to the respective [GitHub releases](https://github.com/grpc-ecosystem/go-grpc-prometheus/releases). golang-github-grpc-ecosystem-go-grpc-prometheus-1.2.0+git20191002.6af20e3/LICENSE000066400000000000000000000261141354506410500264070ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.golang-github-grpc-ecosystem-go-grpc-prometheus-1.2.0+git20191002.6af20e3/README.md000066400000000000000000000327651354506410500266720ustar00rootroot00000000000000# Go gRPC Interceptors for Prometheus monitoring [![Travis Build](https://travis-ci.org/grpc-ecosystem/go-grpc-prometheus.svg)](https://travis-ci.org/grpc-ecosystem/go-grpc-prometheus) [![Go Report Card](https://goreportcard.com/badge/github.com/grpc-ecosystem/go-grpc-prometheus)](http://goreportcard.com/report/grpc-ecosystem/go-grpc-prometheus) [![GoDoc](http://img.shields.io/badge/GoDoc-Reference-blue.svg)](https://godoc.org/github.com/grpc-ecosystem/go-grpc-prometheus) [![SourceGraph](https://sourcegraph.com/github.com/grpc-ecosystem/go-grpc-prometheus/-/badge.svg)](https://sourcegraph.com/github.com/grpc-ecosystem/go-grpc-prometheus/?badge) [![codecov](https://codecov.io/gh/grpc-ecosystem/go-grpc-prometheus/branch/master/graph/badge.svg)](https://codecov.io/gh/grpc-ecosystem/go-grpc-prometheus) [![Slack](https://img.shields.io/badge/join%20slack-%23go--grpc--prometheus-brightgreen.svg)](https://join.slack.com/t/improbable-eng/shared_invite/enQtMzQ1ODcyMzQ5MjM4LWY5ZWZmNGM2ODc5MmViNmQ3ZTA3ZTY3NzQwOTBlMTkzZmIxZTIxODk0OWU3YjZhNWVlNDU3MDlkZGViZjhkMjc) [![Apache 2.0 License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE) [Prometheus](https://prometheus.io/) monitoring for your [gRPC Go](https://github.com/grpc/grpc-go) servers and clients. A sister implementation for [gRPC Java](https://github.com/grpc/grpc-java) (same metrics, same semantics) is in [grpc-ecosystem/java-grpc-prometheus](https://github.com/grpc-ecosystem/java-grpc-prometheus). ## Interceptors [gRPC Go](https://github.com/grpc/grpc-go) recently acquired support for Interceptors, i.e. middleware that is executed by a gRPC Server before the request is passed onto the user's application logic. It is a perfect way to implement common patterns: auth, logging and... monitoring. To use Interceptors in chains, please see [`go-grpc-middleware`](https://github.com/mwitkow/go-grpc-middleware). This library requires Go 1.9 or later. ## Usage There are two types of interceptors: client-side and server-side. This package provides monitoring Interceptors for both. ### Server-side ```go import "github.com/grpc-ecosystem/go-grpc-prometheus" ... // Initialize your gRPC server's interceptor. myServer := grpc.NewServer( grpc.StreamInterceptor(grpc_prometheus.StreamServerInterceptor), grpc.UnaryInterceptor(grpc_prometheus.UnaryServerInterceptor), ) // Register your gRPC service implementations. myservice.RegisterMyServiceServer(s.server, &myServiceImpl{}) // After all your registrations, make sure all of the Prometheus metrics are initialized. grpc_prometheus.Register(myServer) // Register Prometheus metrics handler. http.Handle("/metrics", promhttp.Handler()) ... ``` ### Client-side ```go import "github.com/grpc-ecosystem/go-grpc-prometheus" ... clientConn, err = grpc.Dial( address, grpc.WithUnaryInterceptor(grpc_prometheus.UnaryClientInterceptor), grpc.WithStreamInterceptor(grpc_prometheus.StreamClientInterceptor) ) client = pb_testproto.NewTestServiceClient(clientConn) resp, err := client.PingEmpty(s.ctx, &myservice.Request{Msg: "hello"}) ... ``` # Metrics ## Labels All server-side metrics start with `grpc_server` as Prometheus subsystem name. All client-side metrics start with `grpc_client`. Both of them have mirror-concepts. Similarly all methods contain the same rich labels: * `grpc_service` - the [gRPC service](http://www.grpc.io/docs/#defining-a-service) name, which is the combination of protobuf `package` and the `grpc_service` section name. E.g. for `package = mwitkow.testproto` and `service TestService` the label will be `grpc_service="mwitkow.testproto.TestService"` * `grpc_method` - the name of the method called on the gRPC service. E.g. `grpc_method="Ping"` * `grpc_type` - the gRPC [type of request](http://www.grpc.io/docs/guides/concepts.html#rpc-life-cycle). Differentiating between the two is important especially for latency measurements. - `unary` is single request, single response RPC - `client_stream` is a multi-request, single response RPC - `server_stream` is a single request, multi-response RPC - `bidi_stream` is a multi-request, multi-response RPC Additionally for completed RPCs, the following labels are used: * `grpc_code` - the human-readable [gRPC status code](https://github.com/grpc/grpc-go/blob/master/codes/codes.go). The list of all statuses is to long, but here are some common ones: - `OK` - means the RPC was successful - `IllegalArgument` - RPC contained bad values - `Internal` - server-side error not disclosed to the clients ## Counters The counters and their up to date documentation is in [server_reporter.go](server_reporter.go) and [client_reporter.go](client_reporter.go) the respective Prometheus handler (usually `/metrics`). For the purpose of this documentation we will only discuss `grpc_server` metrics. The `grpc_client` ones contain mirror concepts. For simplicity, let's assume we're tracking a single server-side RPC call of [`mwitkow.testproto.TestService`](examples/testproto/test.proto), calling the method `PingList`. The call succeeds and returns 20 messages in the stream. First, immediately after the server receives the call it will increment the `grpc_server_started_total` and start the handling time clock (if histograms are enabled). ```jsoniq grpc_server_started_total{grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream"} 1 ``` Then the user logic gets invoked. It receives one message from the client containing the request (it's a `server_stream`): ```jsoniq grpc_server_msg_received_total{grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream"} 1 ``` The user logic may return an error, or send multiple messages back to the client. In this case, on each of the 20 messages sent back, a counter will be incremented: ```jsoniq grpc_server_msg_sent_total{grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream"} 20 ``` After the call completes, its status (`OK` or other [gRPC status code](https://github.com/grpc/grpc-go/blob/master/codes/codes.go)) and the relevant call labels increment the `grpc_server_handled_total` counter. ```jsoniq grpc_server_handled_total{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream"} 1 ``` ## Histograms [Prometheus histograms](https://prometheus.io/docs/concepts/metric_types/#histogram) are a great way to measure latency distributions of your RPCs. However, since it is bad practice to have metrics of [high cardinality](https://prometheus.io/docs/practices/instrumentation/#do-not-overuse-labels) the latency monitoring metrics are disabled by default. To enable them please call the following in your server initialization code: ```jsoniq grpc_prometheus.EnableHandlingTimeHistogram() ``` After the call completes, its handling time will be recorded in a [Prometheus histogram](https://prometheus.io/docs/concepts/metric_types/#histogram) variable `grpc_server_handling_seconds`. The histogram variable contains three sub-metrics: * `grpc_server_handling_seconds_count` - the count of all completed RPCs by status and method * `grpc_server_handling_seconds_sum` - cumulative time of RPCs by status and method, useful for calculating average handling times * `grpc_server_handling_seconds_bucket` - contains the counts of RPCs by status and method in respective handling-time buckets. These buckets can be used by Prometheus to estimate SLAs (see [here](https://prometheus.io/docs/practices/histograms/)) The counter values will look as follows: ```jsoniq grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="0.005"} 1 grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="0.01"} 1 grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="0.025"} 1 grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="0.05"} 1 grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="0.1"} 1 grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="0.25"} 1 grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="0.5"} 1 grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="1"} 1 grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="2.5"} 1 grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="5"} 1 grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="10"} 1 grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="+Inf"} 1 grpc_server_handling_seconds_sum{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream"} 0.0003866430000000001 grpc_server_handling_seconds_count{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream"} 1 ``` ## Useful query examples Prometheus philosophy is to provide raw metrics to the monitoring system, and let the aggregations be handled there. The verbosity of above metrics make it possible to have that flexibility. Here's a couple of useful monitoring queries: ### request inbound rate ```jsoniq sum(rate(grpc_server_started_total{job="foo"}[1m])) by (grpc_service) ``` For `job="foo"` (common label to differentiate between Prometheus monitoring targets), calculate the rate of requests per second (1 minute window) for each gRPC `grpc_service` that the job has. Please note how the `grpc_method` is being omitted here: all methods of a given gRPC service will be summed together. ### unary request error rate ```jsoniq sum(rate(grpc_server_handled_total{job="foo",grpc_type="unary",grpc_code!="OK"}[1m])) by (grpc_service) ``` For `job="foo"`, calculate the per-`grpc_service` rate of `unary` (1:1) RPCs that failed, i.e. the ones that didn't finish with `OK` code. ### unary request error percentage ```jsoniq sum(rate(grpc_server_handled_total{job="foo",grpc_type="unary",grpc_code!="OK"}[1m])) by (grpc_service) / sum(rate(grpc_server_started_total{job="foo",grpc_type="unary"}[1m])) by (grpc_service) * 100.0 ``` For `job="foo"`, calculate the percentage of failed requests by service. It's easy to notice that this is a combination of the two above examples. This is an example of a query you would like to [alert on](https://prometheus.io/docs/alerting/rules/) in your system for SLA violations, e.g. "no more than 1% requests should fail". ### average response stream size ```jsoniq sum(rate(grpc_server_msg_sent_total{job="foo",grpc_type="server_stream"}[10m])) by (grpc_service) / sum(rate(grpc_server_started_total{job="foo",grpc_type="server_stream"}[10m])) by (grpc_service) ``` For `job="foo"` what is the `grpc_service`-wide `10m` average of messages returned for all ` server_stream` RPCs. This allows you to track the stream sizes returned by your system, e.g. allows you to track when clients started to send "wide" queries that ret Note the divisor is the number of started RPCs, in order to account for in-flight requests. ### 99%-tile latency of unary requests ```jsoniq histogram_quantile(0.99, sum(rate(grpc_server_handling_seconds_bucket{job="foo",grpc_type="unary"}[5m])) by (grpc_service,le) ) ``` For `job="foo"`, returns an 99%-tile [quantile estimation](https://prometheus.io/docs/practices/histograms/#quantiles) of the handling time of RPCs per service. Please note the `5m` rate, this means that the quantile estimation will take samples in a rolling `5m` window. When combined with other quantiles (e.g. 50%, 90%), this query gives you tremendous insight into the responsiveness of your system (e.g. impact of caching). ### percentage of slow unary queries (>250ms) ```jsoniq 100.0 - ( sum(rate(grpc_server_handling_seconds_bucket{job="foo",grpc_type="unary",le="0.25"}[5m])) by (grpc_service) / sum(rate(grpc_server_handling_seconds_count{job="foo",grpc_type="unary"}[5m])) by (grpc_service) ) * 100.0 ``` For `job="foo"` calculate the by-`grpc_service` fraction of slow requests that took longer than `0.25` seconds. This query is relatively complex, since the Prometheus aggregations use `le` (less or equal) buckets, meaning that counting "fast" requests fractions is easier. However, simple maths helps. This is an example of a query you would like to alert on in your system for SLA violations, e.g. "less than 1% of requests are slower than 250ms". ## Status This code has been used since August 2015 as the basis for monitoring of *production* gRPC micro services at [Improbable](https://improbable.io). ## License `go-grpc-prometheus` is released under the Apache 2.0 license. See the [LICENSE](LICENSE) file for details. golang-github-grpc-ecosystem-go-grpc-prometheus-1.2.0+git20191002.6af20e3/client.go000066400000000000000000000045571354506410500272160ustar00rootroot00000000000000// Copyright 2016 Michal Witkowski. All Rights Reserved. // See LICENSE for licensing terms. // gRPC Prometheus monitoring interceptors for client-side gRPC. package grpc_prometheus import ( prom "github.com/prometheus/client_golang/prometheus" ) var ( // DefaultClientMetrics is the default instance of ClientMetrics. It is // intended to be used in conjunction the default Prometheus metrics // registry. DefaultClientMetrics = NewClientMetrics() // UnaryClientInterceptor is a gRPC client-side interceptor that provides Prometheus monitoring for Unary RPCs. UnaryClientInterceptor = DefaultClientMetrics.UnaryClientInterceptor() // StreamClientInterceptor is a gRPC client-side interceptor that provides Prometheus monitoring for Streaming RPCs. StreamClientInterceptor = DefaultClientMetrics.StreamClientInterceptor() ) func init() { prom.MustRegister(DefaultClientMetrics.clientStartedCounter) prom.MustRegister(DefaultClientMetrics.clientHandledCounter) prom.MustRegister(DefaultClientMetrics.clientStreamMsgReceived) prom.MustRegister(DefaultClientMetrics.clientStreamMsgSent) } // EnableClientHandlingTimeHistogram turns on recording of handling time of // RPCs. Histogram metrics can be very expensive for Prometheus to retain and // query. This function acts on the DefaultClientMetrics variable and the // default Prometheus metrics registry. func EnableClientHandlingTimeHistogram(opts ...HistogramOption) { DefaultClientMetrics.EnableClientHandlingTimeHistogram(opts...) prom.Register(DefaultClientMetrics.clientHandledHistogram) } // EnableClientStreamReceiveTimeHistogram turns on recording of // single message receive time of streaming RPCs. // This function acts on the DefaultClientMetrics variable and the // default Prometheus metrics registry. func EnableClientStreamReceiveTimeHistogram(opts ...HistogramOption) { DefaultClientMetrics.EnableClientStreamReceiveTimeHistogram(opts...) prom.Register(DefaultClientMetrics.clientStreamRecvHistogram) } // EnableClientStreamReceiveTimeHistogram turns on recording of // single message send time of streaming RPCs. // This function acts on the DefaultClientMetrics variable and the // default Prometheus metrics registry. func EnableClientStreamSendTimeHistogram(opts ...HistogramOption) { DefaultClientMetrics.EnableClientStreamSendTimeHistogram(opts...) prom.Register(DefaultClientMetrics.clientStreamSendHistogram) } golang-github-grpc-ecosystem-go-grpc-prometheus-1.2.0+git20191002.6af20e3/client_metrics.go000066400000000000000000000212611354506410500307330ustar00rootroot00000000000000package grpc_prometheus import ( "context" "io" prom "github.com/prometheus/client_golang/prometheus" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) // ClientMetrics represents a collection of metrics to be registered on a // Prometheus metrics registry for a gRPC client. type ClientMetrics struct { clientStartedCounter *prom.CounterVec clientHandledCounter *prom.CounterVec clientStreamMsgReceived *prom.CounterVec clientStreamMsgSent *prom.CounterVec clientHandledHistogramEnabled bool clientHandledHistogramOpts prom.HistogramOpts clientHandledHistogram *prom.HistogramVec clientStreamRecvHistogramEnabled bool clientStreamRecvHistogramOpts prom.HistogramOpts clientStreamRecvHistogram *prom.HistogramVec clientStreamSendHistogramEnabled bool clientStreamSendHistogramOpts prom.HistogramOpts clientStreamSendHistogram *prom.HistogramVec } // NewClientMetrics returns a ClientMetrics object. Use a new instance of // ClientMetrics when not using the default Prometheus metrics registry, for // example when wanting to control which metrics are added to a registry as // opposed to automatically adding metrics via init functions. func NewClientMetrics(counterOpts ...CounterOption) *ClientMetrics { opts := counterOptions(counterOpts) return &ClientMetrics{ clientStartedCounter: prom.NewCounterVec( opts.apply(prom.CounterOpts{ Name: "grpc_client_started_total", Help: "Total number of RPCs started on the client.", }), []string{"grpc_type", "grpc_service", "grpc_method"}), clientHandledCounter: prom.NewCounterVec( opts.apply(prom.CounterOpts{ Name: "grpc_client_handled_total", Help: "Total number of RPCs completed by the client, regardless of success or failure.", }), []string{"grpc_type", "grpc_service", "grpc_method", "grpc_code"}), clientStreamMsgReceived: prom.NewCounterVec( opts.apply(prom.CounterOpts{ Name: "grpc_client_msg_received_total", Help: "Total number of RPC stream messages received by the client.", }), []string{"grpc_type", "grpc_service", "grpc_method"}), clientStreamMsgSent: prom.NewCounterVec( opts.apply(prom.CounterOpts{ Name: "grpc_client_msg_sent_total", Help: "Total number of gRPC stream messages sent by the client.", }), []string{"grpc_type", "grpc_service", "grpc_method"}), clientHandledHistogramEnabled: false, clientHandledHistogramOpts: prom.HistogramOpts{ Name: "grpc_client_handling_seconds", Help: "Histogram of response latency (seconds) of the gRPC until it is finished by the application.", Buckets: prom.DefBuckets, }, clientHandledHistogram: nil, clientStreamRecvHistogramEnabled: false, clientStreamRecvHistogramOpts: prom.HistogramOpts{ Name: "grpc_client_msg_recv_handling_seconds", Help: "Histogram of response latency (seconds) of the gRPC single message receive.", Buckets: prom.DefBuckets, }, clientStreamRecvHistogram: nil, clientStreamSendHistogramEnabled: false, clientStreamSendHistogramOpts: prom.HistogramOpts{ Name: "grpc_client_msg_send_handling_seconds", Help: "Histogram of response latency (seconds) of the gRPC single message send.", Buckets: prom.DefBuckets, }, clientStreamSendHistogram: nil, } } // Describe sends the super-set of all possible descriptors of metrics // collected by this Collector to the provided channel and returns once // the last descriptor has been sent. func (m *ClientMetrics) Describe(ch chan<- *prom.Desc) { m.clientStartedCounter.Describe(ch) m.clientHandledCounter.Describe(ch) m.clientStreamMsgReceived.Describe(ch) m.clientStreamMsgSent.Describe(ch) if m.clientHandledHistogramEnabled { m.clientHandledHistogram.Describe(ch) } if m.clientStreamRecvHistogramEnabled { m.clientStreamRecvHistogram.Describe(ch) } if m.clientStreamSendHistogramEnabled { m.clientStreamSendHistogram.Describe(ch) } } // Collect is called by the Prometheus registry when collecting // metrics. The implementation sends each collected metric via the // provided channel and returns once the last metric has been sent. func (m *ClientMetrics) Collect(ch chan<- prom.Metric) { m.clientStartedCounter.Collect(ch) m.clientHandledCounter.Collect(ch) m.clientStreamMsgReceived.Collect(ch) m.clientStreamMsgSent.Collect(ch) if m.clientHandledHistogramEnabled { m.clientHandledHistogram.Collect(ch) } if m.clientStreamRecvHistogramEnabled { m.clientStreamRecvHistogram.Collect(ch) } if m.clientStreamSendHistogramEnabled { m.clientStreamSendHistogram.Collect(ch) } } // EnableClientHandlingTimeHistogram turns on recording of handling time of RPCs. // Histogram metrics can be very expensive for Prometheus to retain and query. func (m *ClientMetrics) EnableClientHandlingTimeHistogram(opts ...HistogramOption) { for _, o := range opts { o(&m.clientHandledHistogramOpts) } if !m.clientHandledHistogramEnabled { m.clientHandledHistogram = prom.NewHistogramVec( m.clientHandledHistogramOpts, []string{"grpc_type", "grpc_service", "grpc_method"}, ) } m.clientHandledHistogramEnabled = true } // EnableClientStreamReceiveTimeHistogram turns on recording of single message receive time of streaming RPCs. // Histogram metrics can be very expensive for Prometheus to retain and query. func (m *ClientMetrics) EnableClientStreamReceiveTimeHistogram(opts ...HistogramOption) { for _, o := range opts { o(&m.clientStreamRecvHistogramOpts) } if !m.clientStreamRecvHistogramEnabled { m.clientStreamRecvHistogram = prom.NewHistogramVec( m.clientStreamRecvHistogramOpts, []string{"grpc_type", "grpc_service", "grpc_method"}, ) } m.clientStreamRecvHistogramEnabled = true } // EnableClientStreamSendTimeHistogram turns on recording of single message send time of streaming RPCs. // Histogram metrics can be very expensive for Prometheus to retain and query. func (m *ClientMetrics) EnableClientStreamSendTimeHistogram(opts ...HistogramOption) { for _, o := range opts { o(&m.clientStreamSendHistogramOpts) } if !m.clientStreamSendHistogramEnabled { m.clientStreamSendHistogram = prom.NewHistogramVec( m.clientStreamSendHistogramOpts, []string{"grpc_type", "grpc_service", "grpc_method"}, ) } m.clientStreamSendHistogramEnabled = true } // UnaryClientInterceptor is a gRPC client-side interceptor that provides Prometheus monitoring for Unary RPCs. func (m *ClientMetrics) UnaryClientInterceptor() func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { monitor := newClientReporter(m, Unary, method) monitor.SentMessage() err := invoker(ctx, method, req, reply, cc, opts...) if err != nil { monitor.ReceivedMessage() } st, _ := status.FromError(err) monitor.Handled(st.Code()) return err } } // StreamClientInterceptor is a gRPC client-side interceptor that provides Prometheus monitoring for Streaming RPCs. func (m *ClientMetrics) StreamClientInterceptor() func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) { return func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) { monitor := newClientReporter(m, clientStreamType(desc), method) clientStream, err := streamer(ctx, desc, cc, method, opts...) if err != nil { st, _ := status.FromError(err) monitor.Handled(st.Code()) return nil, err } return &monitoredClientStream{clientStream, monitor}, nil } } func clientStreamType(desc *grpc.StreamDesc) grpcType { if desc.ClientStreams && !desc.ServerStreams { return ClientStream } else if !desc.ClientStreams && desc.ServerStreams { return ServerStream } return BidiStream } // monitoredClientStream wraps grpc.ClientStream allowing each Sent/Recv of message to increment counters. type monitoredClientStream struct { grpc.ClientStream monitor *clientReporter } func (s *monitoredClientStream) SendMsg(m interface{}) error { timer := s.monitor.SendMessageTimer() err := s.ClientStream.SendMsg(m) timer.ObserveDuration() if err == nil { s.monitor.SentMessage() } return err } func (s *monitoredClientStream) RecvMsg(m interface{}) error { timer := s.monitor.ReceiveMessageTimer() err := s.ClientStream.RecvMsg(m) timer.ObserveDuration() if err == nil { s.monitor.ReceivedMessage() } else if err == io.EOF { s.monitor.Handled(codes.OK) } else { st, _ := status.FromError(err) s.monitor.Handled(st.Code()) } return err } golang-github-grpc-ecosystem-go-grpc-prometheus-1.2.0+git20191002.6af20e3/client_reporter.go000066400000000000000000000042421354506410500311270ustar00rootroot00000000000000// Copyright 2016 Michal Witkowski. All Rights Reserved. // See LICENSE for licensing terms. package grpc_prometheus import ( "time" "github.com/prometheus/client_golang/prometheus" "google.golang.org/grpc/codes" ) type clientReporter struct { metrics *ClientMetrics rpcType grpcType serviceName string methodName string startTime time.Time } func newClientReporter(m *ClientMetrics, rpcType grpcType, fullMethod string) *clientReporter { r := &clientReporter{ metrics: m, rpcType: rpcType, } if r.metrics.clientHandledHistogramEnabled { r.startTime = time.Now() } r.serviceName, r.methodName = splitMethodName(fullMethod) r.metrics.clientStartedCounter.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Inc() return r } // timer is a helper interface to time functions. type timer interface { ObserveDuration() time.Duration } type noOpTimer struct { } func (noOpTimer) ObserveDuration() time.Duration { return 0 } var emptyTimer = noOpTimer{} func (r *clientReporter) ReceiveMessageTimer() timer { if r.metrics.clientStreamRecvHistogramEnabled { hist := r.metrics.clientStreamRecvHistogram.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName) return prometheus.NewTimer(hist) } return emptyTimer } func (r *clientReporter) ReceivedMessage() { r.metrics.clientStreamMsgReceived.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Inc() } func (r *clientReporter) SendMessageTimer() timer { if r.metrics.clientStreamSendHistogramEnabled { hist := r.metrics.clientStreamSendHistogram.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName) return prometheus.NewTimer(hist) } return emptyTimer } func (r *clientReporter) SentMessage() { r.metrics.clientStreamMsgSent.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Inc() } func (r *clientReporter) Handled(code codes.Code) { r.metrics.clientHandledCounter.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName, code.String()).Inc() if r.metrics.clientHandledHistogramEnabled { r.metrics.clientHandledHistogram.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Observe(time.Since(r.startTime).Seconds()) } } golang-github-grpc-ecosystem-go-grpc-prometheus-1.2.0+git20191002.6af20e3/client_test.go000066400000000000000000000152111354506410500302420ustar00rootroot00000000000000// Copyright 2016 Michal Witkowski. All Rights Reserved. // See LICENSE for licensing terms. package grpc_prometheus import ( "context" "io" "net" "testing" "time" pb_testproto "github.com/grpc-ecosystem/go-grpc-prometheus/examples/testproto" "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) var ( // client metrics must satisfy the Collector interface _ prometheus.Collector = NewClientMetrics() ) func TestClientInterceptorSuite(t *testing.T) { suite.Run(t, &ClientInterceptorTestSuite{}) } type ClientInterceptorTestSuite struct { suite.Suite serverListener net.Listener server *grpc.Server clientConn *grpc.ClientConn testClient pb_testproto.TestServiceClient ctx context.Context cancel context.CancelFunc } func (s *ClientInterceptorTestSuite) SetupSuite() { var err error EnableClientHandlingTimeHistogram() s.serverListener, err = net.Listen("tcp", "127.0.0.1:0") require.NoError(s.T(), err, "must be able to allocate a port for serverListener") // This is the point where we hook up the interceptor s.server = grpc.NewServer() pb_testproto.RegisterTestServiceServer(s.server, &testService{t: s.T()}) go func() { s.server.Serve(s.serverListener) }() s.clientConn, err = grpc.Dial( s.serverListener.Addr().String(), grpc.WithInsecure(), grpc.WithBlock(), grpc.WithUnaryInterceptor(UnaryClientInterceptor), grpc.WithStreamInterceptor(StreamClientInterceptor), grpc.WithTimeout(2*time.Second)) require.NoError(s.T(), err, "must not error on client Dial") s.testClient = pb_testproto.NewTestServiceClient(s.clientConn) } func (s *ClientInterceptorTestSuite) SetupTest() { // Make all RPC calls last at most 2 sec, meaning all async issues or deadlock will not kill tests. s.ctx, s.cancel = context.WithTimeout(context.TODO(), 2*time.Second) // Make sure every test starts with same fresh, intialized metric state. DefaultClientMetrics.clientStartedCounter.Reset() DefaultClientMetrics.clientHandledCounter.Reset() DefaultClientMetrics.clientHandledHistogram.Reset() DefaultClientMetrics.clientStreamMsgReceived.Reset() DefaultClientMetrics.clientStreamMsgSent.Reset() } func (s *ClientInterceptorTestSuite) TearDownSuite() { if s.serverListener != nil { s.server.Stop() s.T().Logf("stopped grpc.Server at: %v", s.serverListener.Addr().String()) s.serverListener.Close() } if s.clientConn != nil { s.clientConn.Close() } } func (s *ClientInterceptorTestSuite) TearDownTest() { s.cancel() } func (s *ClientInterceptorTestSuite) TestUnaryIncrementsMetrics() { _, err := s.testClient.PingEmpty(s.ctx, &pb_testproto.Empty{}) // should return with code=OK require.NoError(s.T(), err) requireValue(s.T(), 1, DefaultClientMetrics.clientStartedCounter.WithLabelValues("unary", "mwitkow.testproto.TestService", "PingEmpty")) requireValue(s.T(), 1, DefaultClientMetrics.clientHandledCounter.WithLabelValues("unary", "mwitkow.testproto.TestService", "PingEmpty", "OK")) requireValueHistCount(s.T(), 1, DefaultClientMetrics.clientHandledHistogram.WithLabelValues("unary", "mwitkow.testproto.TestService", "PingEmpty")) _, err = s.testClient.PingError(s.ctx, &pb_testproto.PingRequest{ErrorCodeReturned: uint32(codes.FailedPrecondition)}) // should return with code=FailedPrecondition require.Error(s.T(), err) requireValue(s.T(), 1, DefaultClientMetrics.clientStartedCounter.WithLabelValues("unary", "mwitkow.testproto.TestService", "PingError")) requireValue(s.T(), 1, DefaultClientMetrics.clientHandledCounter.WithLabelValues("unary", "mwitkow.testproto.TestService", "PingError", "FailedPrecondition")) requireValueHistCount(s.T(), 1, DefaultClientMetrics.clientHandledHistogram.WithLabelValues("unary", "mwitkow.testproto.TestService", "PingError")) } func (s *ClientInterceptorTestSuite) TestStartedStreamingIncrementsStarted() { _, err := s.testClient.PingList(s.ctx, &pb_testproto.PingRequest{}) require.NoError(s.T(), err) requireValue(s.T(), 1, DefaultClientMetrics.clientStartedCounter.WithLabelValues("server_stream", "mwitkow.testproto.TestService", "PingList")) _, err = s.testClient.PingList(s.ctx, &pb_testproto.PingRequest{ErrorCodeReturned: uint32(codes.FailedPrecondition)}) // should return with code=FailedPrecondition require.NoError(s.T(), err, "PingList must not fail immediately") requireValue(s.T(), 2, DefaultClientMetrics.clientStartedCounter.WithLabelValues("server_stream", "mwitkow.testproto.TestService", "PingList")) } func (s *ClientInterceptorTestSuite) TestStreamingIncrementsMetrics() { ss, _ := s.testClient.PingList(s.ctx, &pb_testproto.PingRequest{}) // should return with code=OK // Do a read, just for kicks. count := 0 for { _, err := ss.Recv() if err == io.EOF { break } require.NoError(s.T(), err, "reading pingList shouldn't fail") count++ } require.EqualValues(s.T(), countListResponses, count, "Number of received msg on the wire must match") requireValue(s.T(), 1, DefaultClientMetrics.clientStartedCounter.WithLabelValues("server_stream", "mwitkow.testproto.TestService", "PingList")) requireValue(s.T(), 1, DefaultClientMetrics.clientHandledCounter.WithLabelValues("server_stream", "mwitkow.testproto.TestService", "PingList", "OK")) requireValue(s.T(), countListResponses, DefaultClientMetrics.clientStreamMsgReceived.WithLabelValues("server_stream", "mwitkow.testproto.TestService", "PingList")) requireValue(s.T(), 1, DefaultClientMetrics.clientStreamMsgSent.WithLabelValues("server_stream", "mwitkow.testproto.TestService", "PingList")) requireValueHistCount(s.T(), 1, DefaultClientMetrics.clientHandledHistogram.WithLabelValues("server_stream", "mwitkow.testproto.TestService", "PingList")) ss, err := s.testClient.PingList(s.ctx, &pb_testproto.PingRequest{ErrorCodeReturned: uint32(codes.FailedPrecondition)}) // should return with code=FailedPrecondition require.NoError(s.T(), err, "PingList must not fail immediately") // Do a read, just to progate errors. _, err = ss.Recv() st, _ := status.FromError(err) require.Equal(s.T(), codes.FailedPrecondition, st.Code(), "Recv must return FailedPrecondition, otherwise the test is wrong") requireValue(s.T(), 2, DefaultClientMetrics.clientStartedCounter.WithLabelValues("server_stream", "mwitkow.testproto.TestService", "PingList")) requireValue(s.T(), 1, DefaultClientMetrics.clientHandledCounter.WithLabelValues("server_stream", "mwitkow.testproto.TestService", "PingList", "FailedPrecondition")) requireValueHistCount(s.T(), 2, DefaultClientMetrics.clientHandledHistogram.WithLabelValues("server_stream", "mwitkow.testproto.TestService", "PingList")) } golang-github-grpc-ecosystem-go-grpc-prometheus-1.2.0+git20191002.6af20e3/examples/000077500000000000000000000000001354506410500272145ustar00rootroot00000000000000grpc-server-with-prometheus/000077500000000000000000000000001354506410500345365ustar00rootroot00000000000000golang-github-grpc-ecosystem-go-grpc-prometheus-1.2.0+git20191002.6af20e3/examplesclient/000077500000000000000000000000001354506410500360145ustar00rootroot00000000000000golang-github-grpc-ecosystem-go-grpc-prometheus-1.2.0+git20191002.6af20e3/examples/grpc-server-with-prometheusclient.go000066400000000000000000000037611354506410500376300ustar00rootroot00000000000000golang-github-grpc-ecosystem-go-grpc-prometheus-1.2.0+git20191002.6af20e3/examples/grpc-server-with-prometheus/clientpackage main import ( "bufio" "context" "fmt" "log" "net/http" "os" "strings" "time" "google.golang.org/grpc" "github.com/grpc-ecosystem/go-grpc-prometheus" pb "github.com/grpc-ecosystem/go-grpc-prometheus/examples/grpc-server-with-prometheus/protobuf" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" ) func main() { // Create a metrics registry. reg := prometheus.NewRegistry() // Create some standard client metrics. grpcMetrics := grpc_prometheus.NewClientMetrics() // Register client metrics to registry. reg.MustRegister(grpcMetrics) // Create a insecure gRPC channel to communicate with the server. conn, err := grpc.Dial( fmt.Sprintf("localhost:%v", 9093), grpc.WithUnaryInterceptor(grpcMetrics.UnaryClientInterceptor()), grpc.WithStreamInterceptor(grpcMetrics.StreamClientInterceptor()), grpc.WithInsecure(), ) if err != nil { log.Fatal(err) } defer conn.Close() // Create a HTTP server for prometheus. httpServer := &http.Server{Handler: promhttp.HandlerFor(reg, promhttp.HandlerOpts{}), Addr: fmt.Sprintf("0.0.0.0:%d", 9094)} // Start your http server for prometheus. go func() { if err := httpServer.ListenAndServe(); err != nil { log.Fatal("Unable to start a http server.") } }() // Create a gRPC server client. client := pb.NewDemoServiceClient(conn) fmt.Println("Start to call the method called SayHello every 3 seconds") go func() { for { // Call “SayHello” method and wait for response from gRPC Server. _, err := client.SayHello(context.Background(), &pb.HelloRequest{Name: "Test"}) if err != nil { log.Printf("Calling the SayHello method unsuccessfully. ErrorInfo: %+v", err) log.Printf("You should to stop the process") return } time.Sleep(3 * time.Second) } }() scanner := bufio.NewScanner(os.Stdin) fmt.Println("You can press n or N to stop the process of client") for scanner.Scan() { if strings.ToLower(scanner.Text()) == "n" { os.Exit(0) } } } prometheus/000077500000000000000000000000001354506410500367315ustar00rootroot00000000000000golang-github-grpc-ecosystem-go-grpc-prometheus-1.2.0+git20191002.6af20e3/examples/grpc-server-with-prometheusprometheus.yaml000066400000000000000000000022301354506410500420050ustar00rootroot00000000000000golang-github-grpc-ecosystem-go-grpc-prometheus-1.2.0+git20191002.6af20e3/examples/grpc-server-with-prometheus/prometheus# my global config global: scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute. evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute. # scrape_timeout is set to the global default (10s). # Attach these labels to any time series or alerts when communicating with # external systems (federation, remote storage, Alertmanager). external_labels: monitor: 'kirk-grpc-service-monitor' # Load rules once and periodically evaluate them according to the global 'evaluation_interval'. rule_files: # - "first.rules" # - "second.rules" # A scrape configuration containing exactly one endpoint to scrape: # Here it's Prometheus itself. scrape_configs: # The job name is added as a label `job=` to any timeseries scraped from this config. # - job_name: 'prometheus' # scrape_interval: 5s # static_configs: # - targets: ['localhost:9090'] - job_name: 'grpcserver' scrape_interval: 1s static_configs: - targets: ['localhost:9092'] - job_name: 'grpcclient' scrape_interval: 1s static_configs: - targets: ['localhost:9094'] protobuf/000077500000000000000000000000001354506410500363765ustar00rootroot00000000000000golang-github-grpc-ecosystem-go-grpc-prometheus-1.2.0+git20191002.6af20e3/examples/grpc-server-with-prometheusservice.pb.go000066400000000000000000000151171354506410500407720ustar00rootroot00000000000000golang-github-grpc-ecosystem-go-grpc-prometheus-1.2.0+git20191002.6af20e3/examples/grpc-server-with-prometheus/protobuf// Code generated by protoc-gen-go. DO NOT EDIT. // source: service.proto package proto import proto "github.com/golang/protobuf/proto" import fmt "fmt" import math "math" import ( context "golang.org/x/net/context" grpc "google.golang.org/grpc" ) // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. // A compilation error at this line likely means your copy of the // proto package needs to be updated. const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package type HelloRequest struct { Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } func (m *HelloRequest) Reset() { *m = HelloRequest{} } func (m *HelloRequest) String() string { return proto.CompactTextString(m) } func (*HelloRequest) ProtoMessage() {} func (*HelloRequest) Descriptor() ([]byte, []int) { return fileDescriptor_service_85197be0990351b4, []int{0} } func (m *HelloRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_HelloRequest.Unmarshal(m, b) } func (m *HelloRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_HelloRequest.Marshal(b, m, deterministic) } func (dst *HelloRequest) XXX_Merge(src proto.Message) { xxx_messageInfo_HelloRequest.Merge(dst, src) } func (m *HelloRequest) XXX_Size() int { return xxx_messageInfo_HelloRequest.Size(m) } func (m *HelloRequest) XXX_DiscardUnknown() { xxx_messageInfo_HelloRequest.DiscardUnknown(m) } var xxx_messageInfo_HelloRequest proto.InternalMessageInfo func (m *HelloRequest) GetName() string { if m != nil { return m.Name } return "" } type HelloResponse struct { Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } func (m *HelloResponse) Reset() { *m = HelloResponse{} } func (m *HelloResponse) String() string { return proto.CompactTextString(m) } func (*HelloResponse) ProtoMessage() {} func (*HelloResponse) Descriptor() ([]byte, []int) { return fileDescriptor_service_85197be0990351b4, []int{1} } func (m *HelloResponse) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_HelloResponse.Unmarshal(m, b) } func (m *HelloResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_HelloResponse.Marshal(b, m, deterministic) } func (dst *HelloResponse) XXX_Merge(src proto.Message) { xxx_messageInfo_HelloResponse.Merge(dst, src) } func (m *HelloResponse) XXX_Size() int { return xxx_messageInfo_HelloResponse.Size(m) } func (m *HelloResponse) XXX_DiscardUnknown() { xxx_messageInfo_HelloResponse.DiscardUnknown(m) } var xxx_messageInfo_HelloResponse proto.InternalMessageInfo func (m *HelloResponse) GetMessage() string { if m != nil { return m.Message } return "" } func init() { proto.RegisterType((*HelloRequest)(nil), "proto.HelloRequest") proto.RegisterType((*HelloResponse)(nil), "proto.HelloResponse") } // Reference imports to suppress errors if they are not otherwise used. var _ context.Context var _ grpc.ClientConn // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. const _ = grpc.SupportPackageIsVersion4 // DemoServiceClient is the client API for DemoService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. type DemoServiceClient interface { SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error) } type demoServiceClient struct { cc *grpc.ClientConn } func NewDemoServiceClient(cc *grpc.ClientConn) DemoServiceClient { return &demoServiceClient{cc} } func (c *demoServiceClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error) { out := new(HelloResponse) err := c.cc.Invoke(ctx, "/proto.DemoService/SayHello", in, out, opts...) if err != nil { return nil, err } return out, nil } // DemoServiceServer is the server API for DemoService service. type DemoServiceServer interface { SayHello(context.Context, *HelloRequest) (*HelloResponse, error) } func RegisterDemoServiceServer(s *grpc.Server, srv DemoServiceServer) { s.RegisterService(&_DemoService_serviceDesc, srv) } func _DemoService_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(HelloRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(DemoServiceServer).SayHello(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/proto.DemoService/SayHello", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DemoServiceServer).SayHello(ctx, req.(*HelloRequest)) } return interceptor(ctx, in, info, handler) } var _DemoService_serviceDesc = grpc.ServiceDesc{ ServiceName: "proto.DemoService", HandlerType: (*DemoServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "SayHello", Handler: _DemoService_SayHello_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "service.proto", } func init() { proto.RegisterFile("service.proto", fileDescriptor_service_85197be0990351b4) } var fileDescriptor_service_85197be0990351b4 = []byte{ // 142 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2d, 0x4e, 0x2d, 0x2a, 0xcb, 0x4c, 0x4e, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x05, 0x53, 0x4a, 0x4a, 0x5c, 0x3c, 0x1e, 0xa9, 0x39, 0x39, 0xf9, 0x41, 0xa9, 0x85, 0xa5, 0xa9, 0xc5, 0x25, 0x42, 0x42, 0x5c, 0x2c, 0x79, 0x89, 0xb9, 0xa9, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x60, 0xb6, 0x92, 0x26, 0x17, 0x2f, 0x54, 0x4d, 0x71, 0x41, 0x7e, 0x5e, 0x71, 0xaa, 0x90, 0x04, 0x17, 0x7b, 0x6e, 0x6a, 0x71, 0x71, 0x62, 0x3a, 0x4c, 0x1d, 0x8c, 0x6b, 0xe4, 0xc6, 0xc5, 0xed, 0x92, 0x9a, 0x9b, 0x1f, 0x0c, 0xb1, 0x4a, 0xc8, 0x9c, 0x8b, 0x23, 0x38, 0xb1, 0x12, 0xac, 0x59, 0x48, 0x18, 0x62, 0xb1, 0x1e, 0xb2, 0x75, 0x52, 0x22, 0xa8, 0x82, 0x10, 0xf3, 0x95, 0x18, 0x92, 0xd8, 0xc0, 0xc2, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0xf2, 0xf5, 0x90, 0x47, 0xb5, 0x00, 0x00, 0x00, } service.proto000066400000000000000000000003261354506410500411240ustar00rootroot00000000000000golang-github-grpc-ecosystem-go-grpc-prometheus-1.2.0+git20191002.6af20e3/examples/grpc-server-with-prometheus/protobufsyntax="proto3"; package proto; service DemoService { rpc SayHello(HelloRequest) returns (HelloResponse) {} } message HelloRequest { string name = 1; } message HelloResponse { string message = 1; } server/000077500000000000000000000000001354506410500360445ustar00rootroot00000000000000golang-github-grpc-ecosystem-go-grpc-prometheus-1.2.0+git20191002.6af20e3/examples/grpc-server-with-prometheusserver.go000066400000000000000000000047121354506410500377050ustar00rootroot00000000000000golang-github-grpc-ecosystem-go-grpc-prometheus-1.2.0+git20191002.6af20e3/examples/grpc-server-with-prometheus/serverpackage main import ( "context" "fmt" "log" "net" "net/http" "google.golang.org/grpc" "github.com/grpc-ecosystem/go-grpc-prometheus" pb "github.com/grpc-ecosystem/go-grpc-prometheus/examples/grpc-server-with-prometheus/protobuf" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" ) // DemoServiceServer defines a Server. type DemoServiceServer struct{} func newDemoServer() *DemoServiceServer { return &DemoServiceServer{} } // SayHello implements a interface defined by protobuf. func (s *DemoServiceServer) SayHello(ctx context.Context, request *pb.HelloRequest) (*pb.HelloResponse, error) { customizedCounterMetric.WithLabelValues(request.Name).Inc() return &pb.HelloResponse{Message: fmt.Sprintf("Hello %s", request.Name)}, nil } var ( // Create a metrics registry. reg = prometheus.NewRegistry() // Create some standard server metrics. grpcMetrics = grpc_prometheus.NewServerMetrics() // Create a customized counter metric. customizedCounterMetric = prometheus.NewCounterVec(prometheus.CounterOpts{ Name: "demo_server_say_hello_method_handle_count", Help: "Total number of RPCs handled on the server.", }, []string{"name"}) ) func init() { // Register standard server metrics and customized metrics to registry. reg.MustRegister(grpcMetrics, customizedCounterMetric) customizedCounterMetric.WithLabelValues("Test") } // NOTE: Graceful shutdown is missing. Don't use this demo in your production setup. func main() { // Listen an actual port. lis, err := net.Listen("tcp", fmt.Sprintf(":%d", 9093)) if err != nil { log.Fatalf("failed to listen: %v", err) } defer lis.Close() // Create a HTTP server for prometheus. httpServer := &http.Server{Handler: promhttp.HandlerFor(reg, promhttp.HandlerOpts{}), Addr: fmt.Sprintf("0.0.0.0:%d", 9092)} // Create a gRPC Server with gRPC interceptor. grpcServer := grpc.NewServer( grpc.StreamInterceptor(grpcMetrics.StreamServerInterceptor()), grpc.UnaryInterceptor(grpcMetrics.UnaryServerInterceptor()), ) // Create a new api server. demoServer := newDemoServer() // Register your service. pb.RegisterDemoServiceServer(grpcServer, demoServer) // Initialize all metrics. grpcMetrics.InitializeMetrics(grpcServer) // Start your http server for prometheus. go func() { if err := httpServer.ListenAndServe(); err != nil { log.Fatal("Unable to start a http server.") } }() // Start your gRPC server. log.Fatal(grpcServer.Serve(lis)) } golang-github-grpc-ecosystem-go-grpc-prometheus-1.2.0+git20191002.6af20e3/examples/testproto/000077500000000000000000000000001354506410500312575ustar00rootroot00000000000000Makefile000066400000000000000000000002211354506410500326330ustar00rootroot00000000000000golang-github-grpc-ecosystem-go-grpc-prometheus-1.2.0+git20191002.6af20e3/examples/testprotoall: test_go test_go: test.proto PATH="${GOPATH}/bin:${PATH}" protoc \ -I. \ -I${GOPATH}/src \ --go_out=plugins=grpc:. \ test.proto test.pb.go000066400000000000000000000314731354506410500331160ustar00rootroot00000000000000golang-github-grpc-ecosystem-go-grpc-prometheus-1.2.0+git20191002.6af20e3/examples/testproto// Code generated by protoc-gen-go. DO NOT EDIT. // source: test.proto package mwitkow_testproto import proto "github.com/golang/protobuf/proto" import fmt "fmt" import math "math" import ( context "golang.org/x/net/context" grpc "google.golang.org/grpc" ) // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. // A compilation error at this line likely means your copy of the // proto package needs to be updated. const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package type Empty struct { XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } func (m *Empty) Reset() { *m = Empty{} } func (m *Empty) String() string { return proto.CompactTextString(m) } func (*Empty) ProtoMessage() {} func (*Empty) Descriptor() ([]byte, []int) { return fileDescriptor_test_61863aae6603e9b8, []int{0} } func (m *Empty) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_Empty.Unmarshal(m, b) } func (m *Empty) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_Empty.Marshal(b, m, deterministic) } func (dst *Empty) XXX_Merge(src proto.Message) { xxx_messageInfo_Empty.Merge(dst, src) } func (m *Empty) XXX_Size() int { return xxx_messageInfo_Empty.Size(m) } func (m *Empty) XXX_DiscardUnknown() { xxx_messageInfo_Empty.DiscardUnknown(m) } var xxx_messageInfo_Empty proto.InternalMessageInfo type PingRequest struct { Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"` SleepTimeMs int32 `protobuf:"varint,2,opt,name=sleep_time_ms,json=sleepTimeMs,proto3" json:"sleep_time_ms,omitempty"` ErrorCodeReturned uint32 `protobuf:"varint,3,opt,name=error_code_returned,json=errorCodeReturned,proto3" json:"error_code_returned,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } func (m *PingRequest) Reset() { *m = PingRequest{} } func (m *PingRequest) String() string { return proto.CompactTextString(m) } func (*PingRequest) ProtoMessage() {} func (*PingRequest) Descriptor() ([]byte, []int) { return fileDescriptor_test_61863aae6603e9b8, []int{1} } func (m *PingRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_PingRequest.Unmarshal(m, b) } func (m *PingRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_PingRequest.Marshal(b, m, deterministic) } func (dst *PingRequest) XXX_Merge(src proto.Message) { xxx_messageInfo_PingRequest.Merge(dst, src) } func (m *PingRequest) XXX_Size() int { return xxx_messageInfo_PingRequest.Size(m) } func (m *PingRequest) XXX_DiscardUnknown() { xxx_messageInfo_PingRequest.DiscardUnknown(m) } var xxx_messageInfo_PingRequest proto.InternalMessageInfo func (m *PingRequest) GetValue() string { if m != nil { return m.Value } return "" } func (m *PingRequest) GetSleepTimeMs() int32 { if m != nil { return m.SleepTimeMs } return 0 } func (m *PingRequest) GetErrorCodeReturned() uint32 { if m != nil { return m.ErrorCodeReturned } return 0 } type PingResponse struct { Value string `protobuf:"bytes,1,opt,name=Value,proto3" json:"Value,omitempty"` Counter int32 `protobuf:"varint,2,opt,name=counter,proto3" json:"counter,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } func (m *PingResponse) Reset() { *m = PingResponse{} } func (m *PingResponse) String() string { return proto.CompactTextString(m) } func (*PingResponse) ProtoMessage() {} func (*PingResponse) Descriptor() ([]byte, []int) { return fileDescriptor_test_61863aae6603e9b8, []int{2} } func (m *PingResponse) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_PingResponse.Unmarshal(m, b) } func (m *PingResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_PingResponse.Marshal(b, m, deterministic) } func (dst *PingResponse) XXX_Merge(src proto.Message) { xxx_messageInfo_PingResponse.Merge(dst, src) } func (m *PingResponse) XXX_Size() int { return xxx_messageInfo_PingResponse.Size(m) } func (m *PingResponse) XXX_DiscardUnknown() { xxx_messageInfo_PingResponse.DiscardUnknown(m) } var xxx_messageInfo_PingResponse proto.InternalMessageInfo func (m *PingResponse) GetValue() string { if m != nil { return m.Value } return "" } func (m *PingResponse) GetCounter() int32 { if m != nil { return m.Counter } return 0 } func init() { proto.RegisterType((*Empty)(nil), "mwitkow.testproto.Empty") proto.RegisterType((*PingRequest)(nil), "mwitkow.testproto.PingRequest") proto.RegisterType((*PingResponse)(nil), "mwitkow.testproto.PingResponse") } // Reference imports to suppress errors if they are not otherwise used. var _ context.Context var _ grpc.ClientConn // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. const _ = grpc.SupportPackageIsVersion4 // TestServiceClient is the client API for TestService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. type TestServiceClient interface { PingEmpty(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*PingResponse, error) Ping(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PingResponse, error) PingError(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*Empty, error) PingList(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (TestService_PingListClient, error) } type testServiceClient struct { cc *grpc.ClientConn } func NewTestServiceClient(cc *grpc.ClientConn) TestServiceClient { return &testServiceClient{cc} } func (c *testServiceClient) PingEmpty(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*PingResponse, error) { out := new(PingResponse) err := c.cc.Invoke(ctx, "/mwitkow.testproto.TestService/PingEmpty", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *testServiceClient) Ping(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PingResponse, error) { out := new(PingResponse) err := c.cc.Invoke(ctx, "/mwitkow.testproto.TestService/Ping", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *testServiceClient) PingError(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*Empty, error) { out := new(Empty) err := c.cc.Invoke(ctx, "/mwitkow.testproto.TestService/PingError", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *testServiceClient) PingList(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (TestService_PingListClient, error) { stream, err := c.cc.NewStream(ctx, &_TestService_serviceDesc.Streams[0], "/mwitkow.testproto.TestService/PingList", opts...) if err != nil { return nil, err } x := &testServicePingListClient{stream} if err := x.ClientStream.SendMsg(in); err != nil { return nil, err } if err := x.ClientStream.CloseSend(); err != nil { return nil, err } return x, nil } type TestService_PingListClient interface { Recv() (*PingResponse, error) grpc.ClientStream } type testServicePingListClient struct { grpc.ClientStream } func (x *testServicePingListClient) Recv() (*PingResponse, error) { m := new(PingResponse) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } return m, nil } // TestServiceServer is the server API for TestService service. type TestServiceServer interface { PingEmpty(context.Context, *Empty) (*PingResponse, error) Ping(context.Context, *PingRequest) (*PingResponse, error) PingError(context.Context, *PingRequest) (*Empty, error) PingList(*PingRequest, TestService_PingListServer) error } func RegisterTestServiceServer(s *grpc.Server, srv TestServiceServer) { s.RegisterService(&_TestService_serviceDesc, srv) } func _TestService_PingEmpty_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(Empty) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(TestServiceServer).PingEmpty(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/mwitkow.testproto.TestService/PingEmpty", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(TestServiceServer).PingEmpty(ctx, req.(*Empty)) } return interceptor(ctx, in, info, handler) } func _TestService_Ping_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(PingRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(TestServiceServer).Ping(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/mwitkow.testproto.TestService/Ping", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(TestServiceServer).Ping(ctx, req.(*PingRequest)) } return interceptor(ctx, in, info, handler) } func _TestService_PingError_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(PingRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(TestServiceServer).PingError(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/mwitkow.testproto.TestService/PingError", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(TestServiceServer).PingError(ctx, req.(*PingRequest)) } return interceptor(ctx, in, info, handler) } func _TestService_PingList_Handler(srv interface{}, stream grpc.ServerStream) error { m := new(PingRequest) if err := stream.RecvMsg(m); err != nil { return err } return srv.(TestServiceServer).PingList(m, &testServicePingListServer{stream}) } type TestService_PingListServer interface { Send(*PingResponse) error grpc.ServerStream } type testServicePingListServer struct { grpc.ServerStream } func (x *testServicePingListServer) Send(m *PingResponse) error { return x.ServerStream.SendMsg(m) } var _TestService_serviceDesc = grpc.ServiceDesc{ ServiceName: "mwitkow.testproto.TestService", HandlerType: (*TestServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "PingEmpty", Handler: _TestService_PingEmpty_Handler, }, { MethodName: "Ping", Handler: _TestService_Ping_Handler, }, { MethodName: "PingError", Handler: _TestService_PingError_Handler, }, }, Streams: []grpc.StreamDesc{ { StreamName: "PingList", Handler: _TestService_PingList_Handler, ServerStreams: true, }, }, Metadata: "test.proto", } func init() { proto.RegisterFile("test.proto", fileDescriptor_test_61863aae6603e9b8) } var fileDescriptor_test_61863aae6603e9b8 = []byte{ // 274 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x50, 0x4d, 0x4b, 0xc3, 0x40, 0x10, 0x6d, 0xaa, 0xb1, 0x76, 0x62, 0x0f, 0x1d, 0x3d, 0x04, 0x0f, 0x1a, 0xf6, 0x94, 0x53, 0x10, 0xbd, 0x7b, 0x11, 0x51, 0x41, 0x51, 0x62, 0xf1, 0x1a, 0x34, 0x19, 0x64, 0xb1, 0x9b, 0x8d, 0xbb, 0x93, 0x06, 0xff, 0x9b, 0x3f, 0x4e, 0x76, 0x1b, 0xa1, 0xa0, 0x45, 0x0f, 0x3d, 0xbe, 0xf7, 0x86, 0xf7, 0x31, 0x00, 0x4c, 0x96, 0xb3, 0xc6, 0x68, 0xd6, 0x38, 0x55, 0x9d, 0xe4, 0x37, 0xdd, 0x65, 0x8e, 0xf3, 0x94, 0x18, 0x41, 0x78, 0xa9, 0x1a, 0xfe, 0x10, 0x1d, 0x44, 0x0f, 0xb2, 0x7e, 0xcd, 0xe9, 0xbd, 0x25, 0xcb, 0x78, 0x00, 0xe1, 0xe2, 0x79, 0xde, 0x52, 0x1c, 0x24, 0x41, 0x3a, 0xce, 0x97, 0x00, 0x05, 0x4c, 0xec, 0x9c, 0xa8, 0x29, 0x58, 0x2a, 0x2a, 0x94, 0x8d, 0x87, 0x49, 0x90, 0x86, 0x79, 0xe4, 0xc9, 0x99, 0x54, 0x74, 0x67, 0x31, 0x83, 0x7d, 0x32, 0x46, 0x9b, 0xa2, 0xd4, 0x15, 0x15, 0x86, 0xb8, 0x35, 0x35, 0x55, 0xf1, 0x56, 0x12, 0xa4, 0x93, 0x7c, 0xea, 0xa5, 0x0b, 0x5d, 0x51, 0xde, 0x0b, 0xe2, 0x1c, 0xf6, 0x96, 0xc1, 0xb6, 0xd1, 0xb5, 0x25, 0x97, 0xfc, 0xb4, 0x9a, 0xec, 0x01, 0xc6, 0x30, 0x2a, 0x75, 0x5b, 0x33, 0x99, 0x3e, 0xf3, 0x1b, 0x9e, 0x7e, 0x0e, 0x21, 0x9a, 0x91, 0xe5, 0x47, 0x32, 0x0b, 0x59, 0x12, 0x5e, 0xc3, 0xd8, 0xf9, 0xf9, 0x55, 0x18, 0x67, 0x3f, 0x26, 0x67, 0x5e, 0x39, 0x3c, 0xfe, 0x45, 0x59, 0xed, 0x21, 0x06, 0x78, 0x03, 0xdb, 0x8e, 0xc1, 0xa3, 0xb5, 0xa7, 0xfe, 0x57, 0xff, 0xb1, 0xba, 0xea, 0x4b, 0xb9, 0xf5, 0x7f, 0xfa, 0xad, 0x2d, 0x2d, 0x06, 0x78, 0x0f, 0xbb, 0xee, 0xf4, 0x56, 0x5a, 0xde, 0x40, 0xaf, 0x93, 0xe0, 0x65, 0xc7, 0xf3, 0x67, 0x5f, 0x01, 0x00, 0x00, 0xff, 0xff, 0xdb, 0x2f, 0xc7, 0x45, 0x28, 0x02, 0x00, 0x00, } test.proto000066400000000000000000000007341354506410500332500ustar00rootroot00000000000000golang-github-grpc-ecosystem-go-grpc-prometheus-1.2.0+git20191002.6af20e3/examples/testprotosyntax = "proto3"; package mwitkow.testproto; message Empty { } message PingRequest { string value = 1; int32 sleep_time_ms = 2; uint32 error_code_returned = 3; } message PingResponse { string Value = 1; int32 counter = 2; } service TestService { rpc PingEmpty(Empty) returns (PingResponse) {} rpc Ping(PingRequest) returns (PingResponse) {} rpc PingError(PingRequest) returns (Empty) {} rpc PingList(PingRequest) returns (stream PingResponse) {} } golang-github-grpc-ecosystem-go-grpc-prometheus-1.2.0+git20191002.6af20e3/go.mod000066400000000000000000000005201354506410500265010ustar00rootroot00000000000000module github.com/grpc-ecosystem/go-grpc-prometheus require ( github.com/golang/protobuf v1.2.0 github.com/prometheus/client_golang v0.9.2 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 github.com/stretchr/testify v1.3.0 golang.org/x/net v0.0.0-20190213061140-3a22650c66bd google.golang.org/grpc v1.18.0 ) golang-github-grpc-ecosystem-go-grpc-prometheus-1.2.0+git20191002.6af20e3/go.sum000066400000000000000000000113171354506410500265340ustar00rootroot00000000000000cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740= github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275 h1:PnBWHBf+6L0jOqq0gIVUe6Yk0/QMZ640k6NvkxcBf+8= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a h1:9a8MnZMP0X2nLJdBg+pBmGgkJlSaKC2KaQmTCk1XDtE= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd h1:HuTn7WObtcDo9uEEU7rEqL0jYthdXAmZ6PP+meazmaU= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522 h1:Ve1ORMCxvRmSXBwJK+t3Oy+V2vRW2OetUQBq4rJIkZE= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/grpc v1.18.0 h1:IZl7mfBGfbhYx2p2rKRtYgDFw6SBz+kclmxYrCksPPA= google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= golang-github-grpc-ecosystem-go-grpc-prometheus-1.2.0+git20191002.6af20e3/makefile000066400000000000000000000003141354506410500270740ustar00rootroot00000000000000GOFILES_NOVENDOR = $(shell go list ./... | grep -v /vendor/) all: vet fmt test fmt: go fmt $(GOFILES_NOVENDOR) vet: go vet $(GOFILES_NOVENDOR) test: vet ./scripts/test_all.sh .PHONY: all vet test golang-github-grpc-ecosystem-go-grpc-prometheus-1.2.0+git20191002.6af20e3/metric_options.go000066400000000000000000000022201354506410500307570ustar00rootroot00000000000000package grpc_prometheus import ( prom "github.com/prometheus/client_golang/prometheus" ) // A CounterOption lets you add options to Counter metrics using With* funcs. type CounterOption func(*prom.CounterOpts) type counterOptions []CounterOption func (co counterOptions) apply(o prom.CounterOpts) prom.CounterOpts { for _, f := range co { f(&o) } return o } // WithConstLabels allows you to add ConstLabels to Counter metrics. func WithConstLabels(labels prom.Labels) CounterOption { return func(o *prom.CounterOpts) { o.ConstLabels = labels } } // A HistogramOption lets you add options to Histogram metrics using With* // funcs. type HistogramOption func(*prom.HistogramOpts) // WithHistogramBuckets allows you to specify custom bucket ranges for histograms if EnableHandlingTimeHistogram is on. func WithHistogramBuckets(buckets []float64) HistogramOption { return func(o *prom.HistogramOpts) { o.Buckets = buckets } } // WithHistogramConstLabels allows you to add custom ConstLabels to // histograms metrics. func WithHistogramConstLabels(labels prom.Labels) HistogramOption { return func(o *prom.HistogramOpts) { o.ConstLabels = labels } } golang-github-grpc-ecosystem-go-grpc-prometheus-1.2.0+git20191002.6af20e3/packages/000077500000000000000000000000001354506410500271545ustar00rootroot00000000000000golang-github-grpc-ecosystem-go-grpc-prometheus-1.2.0+git20191002.6af20e3/packages/grpcstatus/000077500000000000000000000000001354506410500313535ustar00rootroot00000000000000grpcstatus.go000066400000000000000000000032121354506410500340200ustar00rootroot00000000000000golang-github-grpc-ecosystem-go-grpc-prometheus-1.2.0+git20191002.6af20e3/packages/grpcstatuspackage grpcstatus import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) type gRPCStatus interface { GRPCStatus() *status.Status } func unwrapPkgErrorsGRPCStatus(err error) (*status.Status, bool) { type causer interface { Cause() error } // Unwrapping the github.com/pkg/errors causer interface, using `Cause` directly could miss some error implementing // the `GRPCStatus` function so we have to check it on our selves. unwrappedCauser := err for unwrappedCauser != nil { if s, ok := unwrappedCauser.(gRPCStatus); ok { return s.GRPCStatus(), true } cause, ok := unwrappedCauser.(causer) if !ok { break } unwrappedCauser = cause.Cause() } return nil, false } // Since error can be wrapped and the `FromError` function only checks for `GRPCStatus` function // and as a fallback uses the `Unknown` gRPC status we need to unwrap the error if possible to get the original status. // pkg/errors and Go native errors packages have two different approaches so we try to unwrap both types. // Eventually should be implemented in the go-grpc status function `FromError`. See https://github.com/grpc/grpc-go/issues/2934 func FromError(err error) (s *status.Status, ok bool) { s, ok = status.FromError(err) if ok { return s, true } // Try to unwrap `github.com/pkg/errors` wrapped error s, ok = unwrapPkgErrorsGRPCStatus(err) if ok { return s, true } // Try to unwrap native wrapped errors using `fmt.Errorf` and `%w` s, ok = unwrapNativeWrappedGRPCStatus(err) if ok { return s, true } // We failed to unwrap any GRPSStatus so return default `Unknown` return status.New(codes.Unknown, err.Error()), false } grpcstatus1.13+_test.go000066400000000000000000000011421354506410500354350ustar00rootroot00000000000000golang-github-grpc-ecosystem-go-grpc-prometheus-1.2.0+git20191002.6af20e3/packages/grpcstatus// +build go1.13 package grpcstatus import ( "fmt" "github.com/stretchr/testify/require" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "testing" ) func TestNativeErrorUnwrapping(t *testing.T) { gRPCCode := codes.FailedPrecondition gRPCError := status.Errorf(gRPCCode, "Userspace error.") expectedGRPCStatus, _ := status.FromError(gRPCError) testedErrors := []error{ fmt.Errorf("go native wrapped error: %w", gRPCError), } for _, e := range testedErrors { resultingStatus, ok := FromError(e) require.True(t, ok) require.Equal(t, expectedGRPCStatus, resultingStatus) } } grpcstatus_test.go000066400000000000000000000015561354506410500350700ustar00rootroot00000000000000golang-github-grpc-ecosystem-go-grpc-prometheus-1.2.0+git20191002.6af20e3/packages/grpcstatuspackage grpcstatus import ( "github.com/stretchr/testify/require" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "testing" ) // Own implementation of pkg/errors withStack to avoid additional dependency type wrappedError struct { cause error msg string } func (w *wrappedError) Error() string { return w.msg + ": " + w.cause.Error() } func (w *wrappedError) Cause() error { return w.cause } func TestErrorUnwrapping(t *testing.T) { gRPCCode := codes.FailedPrecondition gRPCError := status.Errorf(gRPCCode, "Userspace error.") expectedGRPCStatus, _ := status.FromError(gRPCError) testedErrors := []error{ gRPCError, &wrappedError{cause: gRPCError, msg: "pkg/errors wrapped error: "}, } for _, e := range testedErrors { resultingStatus, ok := FromError(e) require.True(t, ok) require.Equal(t, expectedGRPCStatus, resultingStatus) } } native_unwrap1.12-.go000066400000000000000000000002601354506410500350620ustar00rootroot00000000000000golang-github-grpc-ecosystem-go-grpc-prometheus-1.2.0+git20191002.6af20e3/packages/grpcstatus// +build !go1.13 package grpcstatus import ( "google.golang.org/grpc/status" ) func unwrapNativeWrappedGRPCStatus(err error) (*status.Status, bool) { return nil, false } native_unwrap1.13+.go000066400000000000000000000005471354506410500350710ustar00rootroot00000000000000golang-github-grpc-ecosystem-go-grpc-prometheus-1.2.0+git20191002.6af20e3/packages/grpcstatus// +build go1.13 package grpcstatus import ( "errors" "google.golang.org/grpc/status" ) func unwrapNativeWrappedGRPCStatus(err error) (*status.Status, bool) { // Unwrapping the native Go unwrap interface var unwrappedStatus gRPCStatus if ok := errors.As(err, &unwrappedStatus); ok { return unwrappedStatus.GRPCStatus(), true } return nil, false } golang-github-grpc-ecosystem-go-grpc-prometheus-1.2.0+git20191002.6af20e3/scripts/000077500000000000000000000000001354506410500270655ustar00rootroot00000000000000golang-github-grpc-ecosystem-go-grpc-prometheus-1.2.0+git20191002.6af20e3/scripts/test_all.sh000077500000000000000000000005651354506410500312410ustar00rootroot00000000000000#!/usr/bin/env bash set -e echo "" > coverage.txt for d in $(go list ./... | grep -v vendor); do echo -e "TESTS FOR: for \033[0;35m${d}\033[0m" go test -race -v -coverprofile=profile.coverage.out -covermode=atomic $d if [ -f profile.coverage.out ]; then cat profile.coverage.out >> coverage.txt rm profile.coverage.out fi echo "" done golang-github-grpc-ecosystem-go-grpc-prometheus-1.2.0+git20191002.6af20e3/server.go000066400000000000000000000036371354506410500272440ustar00rootroot00000000000000// Copyright 2016 Michal Witkowski. All Rights Reserved. // See LICENSE for licensing terms. // gRPC Prometheus monitoring interceptors for server-side gRPC. package grpc_prometheus import ( prom "github.com/prometheus/client_golang/prometheus" "google.golang.org/grpc" ) var ( // DefaultServerMetrics is the default instance of ServerMetrics. It is // intended to be used in conjunction the default Prometheus metrics // registry. DefaultServerMetrics = NewServerMetrics() // UnaryServerInterceptor is a gRPC server-side interceptor that provides Prometheus monitoring for Unary RPCs. UnaryServerInterceptor = DefaultServerMetrics.UnaryServerInterceptor() // StreamServerInterceptor is a gRPC server-side interceptor that provides Prometheus monitoring for Streaming RPCs. StreamServerInterceptor = DefaultServerMetrics.StreamServerInterceptor() ) func init() { prom.MustRegister(DefaultServerMetrics.serverStartedCounter) prom.MustRegister(DefaultServerMetrics.serverHandledCounter) prom.MustRegister(DefaultServerMetrics.serverStreamMsgReceived) prom.MustRegister(DefaultServerMetrics.serverStreamMsgSent) } // Register takes a gRPC server and pre-initializes all counters to 0. This // allows for easier monitoring in Prometheus (no missing metrics), and should // be called *after* all services have been registered with the server. This // function acts on the DefaultServerMetrics variable. func Register(server *grpc.Server) { DefaultServerMetrics.InitializeMetrics(server) } // EnableHandlingTimeHistogram turns on recording of handling time // of RPCs. Histogram metrics can be very expensive for Prometheus // to retain and query. This function acts on the DefaultServerMetrics // variable and the default Prometheus metrics registry. func EnableHandlingTimeHistogram(opts ...HistogramOption) { DefaultServerMetrics.EnableHandlingTimeHistogram(opts...) prom.Register(DefaultServerMetrics.serverHandledHistogram) } golang-github-grpc-ecosystem-go-grpc-prometheus-1.2.0+git20191002.6af20e3/server_metrics.go000066400000000000000000000164621354506410500307720ustar00rootroot00000000000000package grpc_prometheus import ( "context" "github.com/grpc-ecosystem/go-grpc-prometheus/packages/grpcstatus" prom "github.com/prometheus/client_golang/prometheus" "google.golang.org/grpc" ) // ServerMetrics represents a collection of metrics to be registered on a // Prometheus metrics registry for a gRPC server. type ServerMetrics struct { serverStartedCounter *prom.CounterVec serverHandledCounter *prom.CounterVec serverStreamMsgReceived *prom.CounterVec serverStreamMsgSent *prom.CounterVec serverHandledHistogramEnabled bool serverHandledHistogramOpts prom.HistogramOpts serverHandledHistogram *prom.HistogramVec } // NewServerMetrics returns a ServerMetrics object. Use a new instance of // ServerMetrics when not using the default Prometheus metrics registry, for // example when wanting to control which metrics are added to a registry as // opposed to automatically adding metrics via init functions. func NewServerMetrics(counterOpts ...CounterOption) *ServerMetrics { opts := counterOptions(counterOpts) return &ServerMetrics{ serverStartedCounter: prom.NewCounterVec( opts.apply(prom.CounterOpts{ Name: "grpc_server_started_total", Help: "Total number of RPCs started on the server.", }), []string{"grpc_type", "grpc_service", "grpc_method"}), serverHandledCounter: prom.NewCounterVec( opts.apply(prom.CounterOpts{ Name: "grpc_server_handled_total", Help: "Total number of RPCs completed on the server, regardless of success or failure.", }), []string{"grpc_type", "grpc_service", "grpc_method", "grpc_code"}), serverStreamMsgReceived: prom.NewCounterVec( opts.apply(prom.CounterOpts{ Name: "grpc_server_msg_received_total", Help: "Total number of RPC stream messages received on the server.", }), []string{"grpc_type", "grpc_service", "grpc_method"}), serverStreamMsgSent: prom.NewCounterVec( opts.apply(prom.CounterOpts{ Name: "grpc_server_msg_sent_total", Help: "Total number of gRPC stream messages sent by the server.", }), []string{"grpc_type", "grpc_service", "grpc_method"}), serverHandledHistogramEnabled: false, serverHandledHistogramOpts: prom.HistogramOpts{ Name: "grpc_server_handling_seconds", Help: "Histogram of response latency (seconds) of gRPC that had been application-level handled by the server.", Buckets: prom.DefBuckets, }, serverHandledHistogram: nil, } } // EnableHandlingTimeHistogram enables histograms being registered when // registering the ServerMetrics on a Prometheus registry. Histograms can be // expensive on Prometheus servers. It takes options to configure histogram // options such as the defined buckets. func (m *ServerMetrics) EnableHandlingTimeHistogram(opts ...HistogramOption) { for _, o := range opts { o(&m.serverHandledHistogramOpts) } if !m.serverHandledHistogramEnabled { m.serverHandledHistogram = prom.NewHistogramVec( m.serverHandledHistogramOpts, []string{"grpc_type", "grpc_service", "grpc_method"}, ) } m.serverHandledHistogramEnabled = true } // Describe sends the super-set of all possible descriptors of metrics // collected by this Collector to the provided channel and returns once // the last descriptor has been sent. func (m *ServerMetrics) Describe(ch chan<- *prom.Desc) { m.serverStartedCounter.Describe(ch) m.serverHandledCounter.Describe(ch) m.serverStreamMsgReceived.Describe(ch) m.serverStreamMsgSent.Describe(ch) if m.serverHandledHistogramEnabled { m.serverHandledHistogram.Describe(ch) } } // Collect is called by the Prometheus registry when collecting // metrics. The implementation sends each collected metric via the // provided channel and returns once the last metric has been sent. func (m *ServerMetrics) Collect(ch chan<- prom.Metric) { m.serverStartedCounter.Collect(ch) m.serverHandledCounter.Collect(ch) m.serverStreamMsgReceived.Collect(ch) m.serverStreamMsgSent.Collect(ch) if m.serverHandledHistogramEnabled { m.serverHandledHistogram.Collect(ch) } } // UnaryServerInterceptor is a gRPC server-side interceptor that provides Prometheus monitoring for Unary RPCs. func (m *ServerMetrics) UnaryServerInterceptor() func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { monitor := newServerReporter(m, Unary, info.FullMethod) monitor.ReceivedMessage() resp, err := handler(ctx, req) st, _ := grpcstatus.FromError(err) monitor.Handled(st.Code()) if err == nil { monitor.SentMessage() } return resp, err } } // StreamServerInterceptor is a gRPC server-side interceptor that provides Prometheus monitoring for Streaming RPCs. func (m *ServerMetrics) StreamServerInterceptor() func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { monitor := newServerReporter(m, streamRPCType(info), info.FullMethod) err := handler(srv, &monitoredServerStream{ss, monitor}) st, _ := grpcstatus.FromError(err) monitor.Handled(st.Code()) return err } } // InitializeMetrics initializes all metrics, with their appropriate null // value, for all gRPC methods registered on a gRPC server. This is useful, to // ensure that all metrics exist when collecting and querying. func (m *ServerMetrics) InitializeMetrics(server *grpc.Server) { serviceInfo := server.GetServiceInfo() for serviceName, info := range serviceInfo { for _, mInfo := range info.Methods { preRegisterMethod(m, serviceName, &mInfo) } } } func streamRPCType(info *grpc.StreamServerInfo) grpcType { if info.IsClientStream && !info.IsServerStream { return ClientStream } else if !info.IsClientStream && info.IsServerStream { return ServerStream } return BidiStream } // monitoredStream wraps grpc.ServerStream allowing each Sent/Recv of message to increment counters. type monitoredServerStream struct { grpc.ServerStream monitor *serverReporter } func (s *monitoredServerStream) SendMsg(m interface{}) error { err := s.ServerStream.SendMsg(m) if err == nil { s.monitor.SentMessage() } return err } func (s *monitoredServerStream) RecvMsg(m interface{}) error { err := s.ServerStream.RecvMsg(m) if err == nil { s.monitor.ReceivedMessage() } return err } // preRegisterMethod is invoked on Register of a Server, allowing all gRPC services labels to be pre-populated. func preRegisterMethod(metrics *ServerMetrics, serviceName string, mInfo *grpc.MethodInfo) { methodName := mInfo.Name methodType := string(typeFromMethodInfo(mInfo)) // These are just references (no increments), as just referencing will create the labels but not set values. metrics.serverStartedCounter.GetMetricWithLabelValues(methodType, serviceName, methodName) metrics.serverStreamMsgReceived.GetMetricWithLabelValues(methodType, serviceName, methodName) metrics.serverStreamMsgSent.GetMetricWithLabelValues(methodType, serviceName, methodName) if metrics.serverHandledHistogramEnabled { metrics.serverHandledHistogram.GetMetricWithLabelValues(methodType, serviceName, methodName) } for _, code := range allCodes { metrics.serverHandledCounter.GetMetricWithLabelValues(methodType, serviceName, methodName, code.String()) } } golang-github-grpc-ecosystem-go-grpc-prometheus-1.2.0+git20191002.6af20e3/server_reporter.go000066400000000000000000000025471354506410500311650ustar00rootroot00000000000000// Copyright 2016 Michal Witkowski. All Rights Reserved. // See LICENSE for licensing terms. package grpc_prometheus import ( "time" "google.golang.org/grpc/codes" ) type serverReporter struct { metrics *ServerMetrics rpcType grpcType serviceName string methodName string startTime time.Time } func newServerReporter(m *ServerMetrics, rpcType grpcType, fullMethod string) *serverReporter { r := &serverReporter{ metrics: m, rpcType: rpcType, } if r.metrics.serverHandledHistogramEnabled { r.startTime = time.Now() } r.serviceName, r.methodName = splitMethodName(fullMethod) r.metrics.serverStartedCounter.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Inc() return r } func (r *serverReporter) ReceivedMessage() { r.metrics.serverStreamMsgReceived.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Inc() } func (r *serverReporter) SentMessage() { r.metrics.serverStreamMsgSent.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Inc() } func (r *serverReporter) Handled(code codes.Code) { r.metrics.serverHandledCounter.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName, code.String()).Inc() if r.metrics.serverHandledHistogramEnabled { r.metrics.serverHandledHistogram.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Observe(time.Since(r.startTime).Seconds()) } } golang-github-grpc-ecosystem-go-grpc-prometheus-1.2.0+git20191002.6af20e3/server_test.go000066400000000000000000000314641354506410500303020ustar00rootroot00000000000000// Copyright 2016 Michal Witkowski. All Rights Reserved. // See LICENSE for licensing terms. package grpc_prometheus import ( "bufio" "context" "fmt" "io" "net" "net/http" "net/http/httptest" "reflect" "strings" "testing" "time" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/client_golang/prometheus/testutil" pb_testproto "github.com/grpc-ecosystem/go-grpc-prometheus/examples/testproto" "github.com/prometheus/client_golang/prometheus" dto "github.com/prometheus/client_model/go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) var ( // server metrics must satisfy the Collector interface _ prometheus.Collector = NewServerMetrics() ) const ( pingDefaultValue = "I like kittens." countListResponses = 20 ) func TestServerInterceptorSuite(t *testing.T) { suite.Run(t, &ServerInterceptorTestSuite{}) } type ServerInterceptorTestSuite struct { suite.Suite serverListener net.Listener server *grpc.Server clientConn *grpc.ClientConn testClient pb_testproto.TestServiceClient ctx context.Context cancel context.CancelFunc } func (s *ServerInterceptorTestSuite) SetupSuite() { var err error EnableHandlingTimeHistogram() s.serverListener, err = net.Listen("tcp", "127.0.0.1:0") require.NoError(s.T(), err, "must be able to allocate a port for serverListener") // This is the point where we hook up the interceptor s.server = grpc.NewServer( grpc.StreamInterceptor(StreamServerInterceptor), grpc.UnaryInterceptor(UnaryServerInterceptor), ) pb_testproto.RegisterTestServiceServer(s.server, &testService{t: s.T()}) go func() { s.server.Serve(s.serverListener) }() s.clientConn, err = grpc.Dial(s.serverListener.Addr().String(), grpc.WithInsecure(), grpc.WithBlock(), grpc.WithTimeout(2*time.Second)) require.NoError(s.T(), err, "must not error on client Dial") s.testClient = pb_testproto.NewTestServiceClient(s.clientConn) } func (s *ServerInterceptorTestSuite) SetupTest() { // Make all RPC calls last at most 2 sec, meaning all async issues or deadlock will not kill tests. s.ctx, s.cancel = context.WithTimeout(context.TODO(), 2*time.Second) // Make sure every test starts with same fresh, intialized metric state. DefaultServerMetrics.serverStartedCounter.Reset() DefaultServerMetrics.serverHandledCounter.Reset() DefaultServerMetrics.serverHandledHistogram.Reset() DefaultServerMetrics.serverStreamMsgReceived.Reset() DefaultServerMetrics.serverStreamMsgSent.Reset() Register(s.server) } func (s *ServerInterceptorTestSuite) TearDownSuite() { if s.serverListener != nil { s.server.Stop() s.T().Logf("stopped grpc.Server at: %v", s.serverListener.Addr().String()) s.serverListener.Close() } if s.clientConn != nil { s.clientConn.Close() } } func (s *ServerInterceptorTestSuite) TearDownTest() { s.cancel() } func (s *ServerInterceptorTestSuite) TestRegisterPresetsStuff() { for testID, testCase := range []struct { metricName string existingLabels []string }{ // Order of label is irrelevant. {"grpc_server_started_total", []string{"mwitkow.testproto.TestService", "PingEmpty", "unary"}}, {"grpc_server_started_total", []string{"mwitkow.testproto.TestService", "PingList", "server_stream"}}, {"grpc_server_msg_received_total", []string{"mwitkow.testproto.TestService", "PingList", "server_stream"}}, {"grpc_server_msg_sent_total", []string{"mwitkow.testproto.TestService", "PingEmpty", "unary"}}, {"grpc_server_handling_seconds_sum", []string{"mwitkow.testproto.TestService", "PingEmpty", "unary"}}, {"grpc_server_handling_seconds_count", []string{"mwitkow.testproto.TestService", "PingList", "server_stream"}}, {"grpc_server_handled_total", []string{"mwitkow.testproto.TestService", "PingList", "server_stream", "OutOfRange"}}, {"grpc_server_handled_total", []string{"mwitkow.testproto.TestService", "PingList", "server_stream", "Aborted"}}, {"grpc_server_handled_total", []string{"mwitkow.testproto.TestService", "PingEmpty", "unary", "FailedPrecondition"}}, {"grpc_server_handled_total", []string{"mwitkow.testproto.TestService", "PingEmpty", "unary", "ResourceExhausted"}}, } { lineCount := len(fetchPrometheusLines(s.T(), testCase.metricName, testCase.existingLabels...)) assert.NotEqual(s.T(), 0, lineCount, "metrics must exist for test case %d", testID) } } func (s *ServerInterceptorTestSuite) TestUnaryIncrementsMetrics() { _, err := s.testClient.PingEmpty(s.ctx, &pb_testproto.Empty{}) // should return with code=OK require.NoError(s.T(), err) requireValue(s.T(), 1, DefaultServerMetrics.serverStartedCounter.WithLabelValues("unary", "mwitkow.testproto.TestService", "PingEmpty")) requireValue(s.T(), 1, DefaultServerMetrics.serverHandledCounter.WithLabelValues("unary", "mwitkow.testproto.TestService", "PingEmpty", "OK")) requireValueHistCount(s.T(), 1, DefaultServerMetrics.serverHandledHistogram.WithLabelValues("unary", "mwitkow.testproto.TestService", "PingEmpty")) _, err = s.testClient.PingError(s.ctx, &pb_testproto.PingRequest{ErrorCodeReturned: uint32(codes.FailedPrecondition)}) // should return with code=FailedPrecondition require.Error(s.T(), err) requireValue(s.T(), 1, DefaultServerMetrics.serverStartedCounter.WithLabelValues("unary", "mwitkow.testproto.TestService", "PingError")) requireValue(s.T(), 1, DefaultServerMetrics.serverHandledCounter.WithLabelValues("unary", "mwitkow.testproto.TestService", "PingError", "FailedPrecondition")) requireValueHistCount(s.T(), 1, DefaultServerMetrics.serverHandledHistogram.WithLabelValues("unary", "mwitkow.testproto.TestService", "PingError")) } func (s *ServerInterceptorTestSuite) TestStartedStreamingIncrementsStarted() { _, err := s.testClient.PingList(s.ctx, &pb_testproto.PingRequest{}) require.NoError(s.T(), err) requireValueWithRetry(s.ctx, s.T(), 1, DefaultServerMetrics.serverStartedCounter.WithLabelValues("server_stream", "mwitkow.testproto.TestService", "PingList")) _, err = s.testClient.PingList(s.ctx, &pb_testproto.PingRequest{ErrorCodeReturned: uint32(codes.FailedPrecondition)}) // should return with code=FailedPrecondition require.NoError(s.T(), err, "PingList must not fail immediately") requireValueWithRetry(s.ctx, s.T(), 2, DefaultServerMetrics.serverStartedCounter.WithLabelValues("server_stream", "mwitkow.testproto.TestService", "PingList")) } func (s *ServerInterceptorTestSuite) TestStreamingIncrementsMetrics() { ss, _ := s.testClient.PingList(s.ctx, &pb_testproto.PingRequest{}) // should return with code=OK // Do a read, just for kicks. count := 0 for { _, err := ss.Recv() if err == io.EOF { break } require.NoError(s.T(), err, "reading pingList shouldn't fail") count++ } require.EqualValues(s.T(), countListResponses, count, "Number of received msg on the wire must match") requireValueWithRetry(s.ctx, s.T(), 1, DefaultServerMetrics.serverStartedCounter.WithLabelValues("server_stream", "mwitkow.testproto.TestService", "PingList")) requireValueWithRetry(s.ctx, s.T(), 1, DefaultServerMetrics.serverHandledCounter.WithLabelValues("server_stream", "mwitkow.testproto.TestService", "PingList", "OK")) requireValueWithRetry(s.ctx, s.T(), countListResponses, DefaultServerMetrics.serverStreamMsgSent.WithLabelValues("server_stream", "mwitkow.testproto.TestService", "PingList")) requireValueWithRetry(s.ctx, s.T(), 1, DefaultServerMetrics.serverStreamMsgReceived.WithLabelValues("server_stream", "mwitkow.testproto.TestService", "PingList")) requireValueWithRetryHistCount(s.ctx, s.T(), 1, DefaultServerMetrics.serverHandledHistogram.WithLabelValues("server_stream", "mwitkow.testproto.TestService", "PingList")) _, err := s.testClient.PingList(s.ctx, &pb_testproto.PingRequest{ErrorCodeReturned: uint32(codes.FailedPrecondition)}) // should return with code=FailedPrecondition require.NoError(s.T(), err, "PingList must not fail immediately") requireValueWithRetry(s.ctx, s.T(), 2, DefaultServerMetrics.serverStartedCounter.WithLabelValues("server_stream", "mwitkow.testproto.TestService", "PingList")) requireValueWithRetry(s.ctx, s.T(), 1, DefaultServerMetrics.serverHandledCounter.WithLabelValues("server_stream", "mwitkow.testproto.TestService", "PingList", "FailedPrecondition")) requireValueWithRetryHistCount(s.ctx, s.T(), 2, DefaultServerMetrics.serverHandledHistogram.WithLabelValues("server_stream", "mwitkow.testproto.TestService", "PingList")) } // fetchPrometheusLines does mocked HTTP GET request against real prometheus handler to get the same view that Prometheus // would have while scraping this endpoint. // Order of matching label vales does not matter. func fetchPrometheusLines(t *testing.T, metricName string, matchingLabelValues ...string) []string { resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/", nil) require.NoError(t, err, "failed creating request for Prometheus handler") promhttp.Handler().ServeHTTP(resp, req) reader := bufio.NewReader(resp.Body) var ret []string for { line, err := reader.ReadString('\n') if err == io.EOF { break } else { require.NoError(t, err, "error reading stuff") } if !strings.HasPrefix(line, metricName) { continue } matches := true for _, labelValue := range matchingLabelValues { if !strings.Contains(line, `"`+labelValue+`"`) { matches = false } } if matches { ret = append(ret, line) } } return ret } type testService struct { t *testing.T } func (s *testService) PingEmpty(ctx context.Context, _ *pb_testproto.Empty) (*pb_testproto.PingResponse, error) { return &pb_testproto.PingResponse{Value: pingDefaultValue, Counter: 42}, nil } func (s *testService) Ping(ctx context.Context, ping *pb_testproto.PingRequest) (*pb_testproto.PingResponse, error) { // Send user trailers and headers. return &pb_testproto.PingResponse{Value: ping.Value, Counter: 42}, nil } func (s *testService) PingError(ctx context.Context, ping *pb_testproto.PingRequest) (*pb_testproto.Empty, error) { code := codes.Code(ping.ErrorCodeReturned) return nil, status.Errorf(code, "Userspace error.") } func (s *testService) PingList(ping *pb_testproto.PingRequest, stream pb_testproto.TestService_PingListServer) error { if ping.ErrorCodeReturned != 0 { return status.Errorf(codes.Code(ping.ErrorCodeReturned), "foobar") } // Send user trailers and headers. for i := 0; i < countListResponses; i++ { stream.Send(&pb_testproto.PingResponse{Value: ping.Value, Counter: int32(i)}) } return nil } // toFloat64HistCount does the same thing as prometheus go client testutil.ToFloat64, but for histograms. // TODO(bwplotka): Upstream this function to prometheus client. func toFloat64HistCount(h prometheus.Observer) uint64 { var ( m prometheus.Metric mCount int mChan = make(chan prometheus.Metric) done = make(chan struct{}) ) go func() { for m = range mChan { mCount++ } close(done) }() c, ok := h.(prometheus.Collector) if !ok { panic(fmt.Errorf("observer is not a collector; got: %T", h)) } c.Collect(mChan) close(mChan) <-done if mCount != 1 { panic(fmt.Errorf("collected %d metrics instead of exactly 1", mCount)) } pb := &dto.Metric{} m.Write(pb) if pb.Histogram != nil { return pb.Histogram.GetSampleCount() } panic(fmt.Errorf("collected a non-histogram metric: %s", pb)) } func requireValue(t *testing.T, expect int, c prometheus.Collector) { v := int(testutil.ToFloat64(c)) if v == expect { return } metricFullName := reflect.ValueOf(*c.(prometheus.Metric).Desc()).FieldByName("fqName").String() t.Errorf("expected %d %s value; got %d; ", expect, metricFullName, v) t.Fail() } func requireValueHistCount(t *testing.T, expect int, o prometheus.Observer) { v := int(toFloat64HistCount(o)) if v == expect { return } metricFullName := reflect.ValueOf(*o.(prometheus.Metric).Desc()).FieldByName("fqName").String() t.Errorf("expected %d %s value; got %d; ", expect, metricFullName, v) t.Fail() } func requireValueWithRetry(ctx context.Context, t *testing.T, expect int, c prometheus.Collector) { for { v := int(testutil.ToFloat64(c)) if v == expect { return } select { case <-ctx.Done(): metricFullName := reflect.ValueOf(*c.(prometheus.Metric).Desc()).FieldByName("fqName").String() t.Errorf("timeout while expecting %d %s value; got %d; ", expect, metricFullName, v) t.Fail() return case <-time.After(100 * time.Millisecond): } } } func requireValueWithRetryHistCount(ctx context.Context, t *testing.T, expect int, o prometheus.Observer) { for { v := int(toFloat64HistCount(o)) if v == expect { return } select { case <-ctx.Done(): metricFullName := reflect.ValueOf(*o.(prometheus.Metric).Desc()).FieldByName("fqName").String() t.Errorf("timeout while expecting %d %s histogram count value; got %d; ", expect, metricFullName, v) t.Fail() return case <-time.After(100 * time.Millisecond): } } } golang-github-grpc-ecosystem-go-grpc-prometheus-1.2.0+git20191002.6af20e3/util.go000066400000000000000000000025071354506410500267060ustar00rootroot00000000000000// Copyright 2016 Michal Witkowski. All Rights Reserved. // See LICENSE for licensing terms. package grpc_prometheus import ( "strings" "google.golang.org/grpc" "google.golang.org/grpc/codes" ) type grpcType string const ( Unary grpcType = "unary" ClientStream grpcType = "client_stream" ServerStream grpcType = "server_stream" BidiStream grpcType = "bidi_stream" ) var ( allCodes = []codes.Code{ codes.OK, codes.Canceled, codes.Unknown, codes.InvalidArgument, codes.DeadlineExceeded, codes.NotFound, codes.AlreadyExists, codes.PermissionDenied, codes.Unauthenticated, codes.ResourceExhausted, codes.FailedPrecondition, codes.Aborted, codes.OutOfRange, codes.Unimplemented, codes.Internal, codes.Unavailable, codes.DataLoss, } ) func splitMethodName(fullMethodName string) (string, string) { fullMethodName = strings.TrimPrefix(fullMethodName, "/") // remove leading slash if i := strings.Index(fullMethodName, "/"); i >= 0 { return fullMethodName[:i], fullMethodName[i+1:] } return "unknown", "unknown" } func typeFromMethodInfo(mInfo *grpc.MethodInfo) grpcType { if !mInfo.IsClientStream && !mInfo.IsServerStream { return Unary } if mInfo.IsClientStream && !mInfo.IsServerStream { return ClientStream } if !mInfo.IsClientStream && mInfo.IsServerStream { return ServerStream } return BidiStream }