Vendor temporarily
This commit is contained in:
1 parent
dd8e532d18
commit
1c02ed9115
4708 files changed
+1226392
-12
No files matched your search
+28
@@ -0,0 +1,28 @@
|
||||
# How to Contribute
|
||||
|
||||
We'd love to accept your patches and contributions to this project. There are
|
||||
just a few small guidelines you need to follow.
|
||||
|
||||
## Contributor License Agreement
|
||||
|
||||
Contributions to this project must be accompanied by a Contributor License
|
||||
Agreement. You (or your employer) retain the copyright to your contribution;
|
||||
this simply gives us permission to use and redistribute your contributions as
|
||||
part of the project. Head over to <https://cla.developers.google.com/> to see
|
||||
your current agreements on file or to sign a new one.
|
||||
|
||||
You generally only need to submit a CLA once, so if you've already submitted one
|
||||
(even if it was for a different project), you probably don't need to do it
|
||||
again.
|
||||
|
||||
## Code reviews
|
||||
|
||||
All submissions, including submissions by project members, require review. We
|
||||
use GitHub pull requests for this purpose. Consult
|
||||
[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
|
||||
information on using pull requests.
|
||||
|
||||
## Community Guidelines
|
||||
|
||||
This project follows [Google's Open Source Community
|
||||
Guidelines](https://opensource.google.com/conduct/).
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
|
||||
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.
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
# go-metrics-stackdriver
|
||||
[](http://godoc.org/github.com/google/go-metrics-stackdriver)
|
||||
|
||||
This library provides a stackdriver sink for applications instrumented with the
|
||||
[go-metrics](https://github.com/armon/go-metrics) library.
|
||||
|
||||
## Details
|
||||
|
||||
[stackdriver.NewSink](https://godoc.org/github.com/google/go-metrics-stackdriver#NewSink)'s return value satisfies the go-metrics library's [MetricSink](https://godoc.org/github.com/armon/go-metrics#MetricSink) interface. When providing a `stackdriver.Sink` to libraries and applications instrumented against `MetricSink`, the metrics will be aggregated within this library and written to stackdriver as [Generic Task](https://cloud.google.com/monitoring/api/resources#tag_generic_task) timeseries metrics.
|
||||
|
||||
## Example
|
||||
|
||||
```go
|
||||
import "github.com/google/go-metrics-stackdriver"
|
||||
...
|
||||
client, _ := monitoring.NewMetricClient(context.Background())
|
||||
ss := stackdriver.NewSink(client, &stackdriver.Config{
|
||||
ProjectID: projectID,
|
||||
})
|
||||
...
|
||||
ss.SetGauge([]string{"foo"}, 42)
|
||||
ss.IncrCounter([]string{"baz"}, 1)
|
||||
ss.AddSample([]string{"method", "const"}, 200)
|
||||
```
|
||||
|
||||
The [full example](example/main.go) can be run from a cloud shell console to test how metrics are collected and displayed.
|
||||
|
||||
You can also try out the example using Cloud Run!
|
||||
|
||||
[](https://console.cloud.google.com/cloudshell/editor?shellonly=true&cloudshell_image=gcr.io/cloudrun/button&cloudshell_git_repo=https://github.com/google/go-metrics-stackdriver.git&cloudshell_working_dir=example)
|
||||
|
||||
|
||||
__This is not an officially supported Google product.__
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
module github.com/google/go-metrics-stackdriver
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.39.0
|
||||
github.com/armon/go-metrics v0.3.0
|
||||
github.com/golang/protobuf v1.3.2
|
||||
github.com/google/go-cmp v0.2.0
|
||||
google.golang.org/api v0.5.0
|
||||
google.golang.org/genproto v0.0.0-20190508193815-b515fa19cec8
|
||||
google.golang.org/grpc v1.22.0
|
||||
)
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.39.0 h1:UgQP9na6OTfp4dsAiz/eFpFA1C6tPdH5wiRdi19tuMw=
|
||||
cloud.google.com/go v0.39.0/go.mod h1:rVLT6fkc8chs9sfPtFc1SBH6em7n+ZoXaG+87tDISts=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
github.com/armon/go-metrics v0.3.0 h1:B7AQgHi8QSEi4uHu7Sbsga+IJDU+CENgjxoo81vDUqU=
|
||||
github.com/armon/go-metrics v0.3.0/go.mod h1:zXjbSimjXTd7vOpY8B0/2LpvNvDoXBuplAD+gJD3GYs=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
|
||||
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
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/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4 h1:hU4mGcQI4DaAYW+IbTun+2qEZVFxK0ySjQLTbS0VQKc=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
||||
github.com/hashicorp/go-uuid v1.0.0 h1:RS8zrF7PhGwyNPOtxSClXXj9HA8feRnJzgnI1RJCSnM=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
|
||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
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/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
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/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||
go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
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-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
google.golang.org/api v0.5.0 h1:lj9SyhMzyoa38fgFF0oO2T6pjs5IzkLPKfVtxpyCRMM=
|
||||
google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190508193815-b515fa19cec8 h1:x913Lq/RebkvUmRSdQ8MNb0GZKn+SR1ESfoetcQSeak=
|
||||
google.golang.org/genproto v0.0.0-20190508193815-b515fa19cec8/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/grpc v1.19.0 h1:cfg4PD8YEdSFnm7qLV4++93WcmhH2nIUhMjhdCvl3j8=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.22.0 h1:J0UbZOIrCAl+fpTOf8YLs4dJo8L/owV4LYVtAXQoPkw=
|
||||
google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
+588
@@ -0,0 +1,588 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
package stackdriver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/compute/metadata"
|
||||
monitoring "cloud.google.com/go/monitoring/apiv3"
|
||||
metrics "github.com/armon/go-metrics"
|
||||
googlepb "github.com/golang/protobuf/ptypes/timestamp"
|
||||
distributionpb "google.golang.org/genproto/googleapis/api/distribution"
|
||||
"google.golang.org/genproto/googleapis/api/metric"
|
||||
metricpb "google.golang.org/genproto/googleapis/api/metric"
|
||||
monitoredrespb "google.golang.org/genproto/googleapis/api/monitoredres"
|
||||
monitoringpb "google.golang.org/genproto/googleapis/monitoring/v3"
|
||||
)
|
||||
|
||||
// Sink conforms to the metrics.MetricSink interface and is used to transmit
|
||||
// metrics information to stackdriver.
|
||||
//
|
||||
// Sink performs in-process aggregation of metrics to limit calls to
|
||||
// stackdriver.
|
||||
type Sink struct {
|
||||
client *monitoring.MetricClient
|
||||
interval time.Duration
|
||||
firstTime time.Time
|
||||
|
||||
gauges map[string]*gauge
|
||||
counters map[string]*counter
|
||||
histograms map[string]*histogram
|
||||
|
||||
bucketer BucketFn
|
||||
extractor ExtractLabelsFn
|
||||
taskInfo *taskInfo
|
||||
|
||||
mu sync.Mutex
|
||||
debugLogs bool
|
||||
}
|
||||
|
||||
// Config options for the stackdriver Sink.
|
||||
type Config struct {
|
||||
// The Google Cloud Project ID to publish metrics to.
|
||||
// Optional. GCP instance metadata is used to determine the ProjectID if
|
||||
// not set.
|
||||
ProjectID string
|
||||
// The label extractor provides a way to rewrite metric key into a new key
|
||||
// with multiple labels. This is useful if the instrumented code includes
|
||||
// variable parameters within a metric name.
|
||||
// Optional. Defaults to DefaultLabelExtractor.
|
||||
LabelExtractor ExtractLabelsFn
|
||||
// The bucketer is used to determine histogram bucket boundaries
|
||||
// for the sampled metrics. This will execute before the LabelExtractor.
|
||||
// Optional. Defaults to DefaultBucketer.
|
||||
Bucketer BucketFn
|
||||
// The interval between sampled metric points. Must be > 1 minute.
|
||||
// https://cloud.google.com/monitoring/custom-metrics/creating-metrics#writing-ts
|
||||
// Optional. Defaults to 1 minute.
|
||||
ReportingInterval time.Duration
|
||||
|
||||
// The location of the running task. See:
|
||||
// https://cloud.google.com/monitoring/api/resources#tag_generic_task
|
||||
// Optional. GCP instance metadata is used to determine the location,
|
||||
// otherwise it defaults to 'global'.
|
||||
Location string
|
||||
// The namespace for the running task. See:
|
||||
// https://cloud.google.com/monitoring/api/resources#tag_generic_task
|
||||
// Optional. Defaults to 'default'.
|
||||
Namespace string
|
||||
// The job name for the running task. See:
|
||||
// https://cloud.google.com/monitoring/api/resources#tag_generic_task
|
||||
// Optional. Defaults to the running program name.
|
||||
Job string
|
||||
// The task ID for the running task. See:
|
||||
// https://cloud.google.com/monitoring/api/resources#tag_generic_task
|
||||
// Optional. Defaults to a combination of hostname+pid.
|
||||
TaskID string
|
||||
|
||||
// Debug logging. Errors will be logged to stderr, but setting this to true
|
||||
// will log additional information that is helpful when debugging errors.
|
||||
// Optional. Defaults to false.
|
||||
DebugLogs bool
|
||||
}
|
||||
|
||||
type taskInfo struct {
|
||||
ProjectID string
|
||||
Location string
|
||||
Namespace string
|
||||
Job string
|
||||
TaskID string
|
||||
}
|
||||
|
||||
// BucketFn returns the histogram bucket thresholds based on the given metric
|
||||
// name.
|
||||
type BucketFn func([]string) []float64
|
||||
|
||||
// ExtractLabelsFn converts a given metric name and type into a new metric
|
||||
// name and optionally additional labels. Errors will prevent the metric from
|
||||
// writing to stackdriver.
|
||||
type ExtractLabelsFn func([]string, string) ([]string, []metrics.Label, error)
|
||||
|
||||
// DefaultBucketer is the default BucketFn used to determing bucketing values
|
||||
// for metrics.
|
||||
func DefaultBucketer(key []string) []float64 {
|
||||
return []float64{10.0, 25.0, 50.0, 100.0, 150.0, 200.0, 250.0, 300.0, 500.0, 1000.0, 1500.0, 2000.0, 3000.0, 4000.0, 5000.0}
|
||||
}
|
||||
|
||||
// DefaultLabelExtractor is the default ExtractLabelsFn and is a direct
|
||||
// passthrough. Counter and Gauge metrics are renamed to include the type in
|
||||
// their name to avoid duplicate metrics with the same name but different
|
||||
// types (which is allowed by go-metrics but not by Stackdriver).
|
||||
func DefaultLabelExtractor(key []string, kind string) ([]string, []metrics.Label, error) {
|
||||
switch kind {
|
||||
case "counter":
|
||||
return append(key, "counter"), nil, nil
|
||||
case "gauge":
|
||||
return append(key, "gauge"), nil, nil
|
||||
case "histogram":
|
||||
return key, nil, nil
|
||||
}
|
||||
return nil, nil, fmt.Errorf("Unknown metric kind: %s", kind)
|
||||
}
|
||||
|
||||
// NewSink creates a Sink to flush metrics to stackdriver every interval. The
|
||||
// interval should be greater than 1 minute.
|
||||
func NewSink(client *monitoring.MetricClient, config *Config) *Sink {
|
||||
s := &Sink{
|
||||
client: client,
|
||||
extractor: config.LabelExtractor,
|
||||
bucketer: config.Bucketer,
|
||||
interval: config.ReportingInterval,
|
||||
taskInfo: &taskInfo{
|
||||
ProjectID: config.ProjectID,
|
||||
Location: config.Location,
|
||||
Namespace: config.Namespace,
|
||||
Job: config.Job,
|
||||
TaskID: config.TaskID,
|
||||
},
|
||||
debugLogs: config.DebugLogs,
|
||||
}
|
||||
|
||||
// apply defaults if not configured explicitly
|
||||
if s.extractor == nil {
|
||||
s.extractor = DefaultLabelExtractor
|
||||
}
|
||||
if s.bucketer == nil {
|
||||
s.bucketer = DefaultBucketer
|
||||
}
|
||||
if s.interval < 60*time.Second {
|
||||
s.interval = 60 * time.Second
|
||||
}
|
||||
if s.taskInfo.ProjectID == "" {
|
||||
id, err := metadata.ProjectID()
|
||||
if err != nil {
|
||||
log.Printf("could not configure go-metrics stackdriver ProjectID: %s", err)
|
||||
}
|
||||
s.taskInfo.ProjectID = id
|
||||
}
|
||||
if s.taskInfo.Location == "" {
|
||||
// attempt to detect
|
||||
zone, err := metadata.Zone()
|
||||
if err != nil {
|
||||
log.Printf("could not configure go-metric stackdriver location: %s", err)
|
||||
zone = "global"
|
||||
}
|
||||
s.taskInfo.Location = zone
|
||||
}
|
||||
if s.taskInfo.Namespace == "" {
|
||||
s.taskInfo.Namespace = "default"
|
||||
}
|
||||
if s.taskInfo.Job == "" {
|
||||
s.taskInfo.Job = path.Base(os.Args[0])
|
||||
}
|
||||
if s.taskInfo.TaskID == "" {
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
hostname = "localhost"
|
||||
}
|
||||
s.taskInfo.TaskID = "go-" + strconv.Itoa(os.Getpid()) + "@" + hostname
|
||||
}
|
||||
|
||||
s.reset()
|
||||
|
||||
// run cancelable goroutine that reports on interval
|
||||
go s.flushMetrics(context.Background())
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *Sink) flushMetrics(ctx context.Context) {
|
||||
if s.interval == 0*time.Second {
|
||||
return
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(s.interval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
log.Println("stopped flushing metrics")
|
||||
return
|
||||
case <-ticker.C:
|
||||
s.report(ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Sink) reset() {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
s.firstTime = time.Now()
|
||||
s.gauges = make(map[string]*gauge)
|
||||
s.counters = make(map[string]*counter)
|
||||
s.histograms = make(map[string]*histogram)
|
||||
}
|
||||
|
||||
func (s *Sink) deep() (time.Time, map[string]*gauge, map[string]*counter, map[string]*histogram) {
|
||||
rGauges := make(map[string]*gauge, len(s.gauges))
|
||||
rCounters := make(map[string]*counter, len(s.counters))
|
||||
rHistograms := make(map[string]*histogram, len(s.histograms))
|
||||
|
||||
s.mu.Lock()
|
||||
end := time.Now()
|
||||
for k, v := range s.gauges {
|
||||
rGauges[k] = &gauge{
|
||||
name: v.name,
|
||||
value: v.value,
|
||||
}
|
||||
}
|
||||
for k, v := range s.counters {
|
||||
rCounters[k] = &counter{
|
||||
name: v.name,
|
||||
value: v.value,
|
||||
}
|
||||
}
|
||||
for k, v := range s.histograms {
|
||||
r := &histogram{
|
||||
name: v.name,
|
||||
buckets: v.buckets,
|
||||
counts: make([]int64, len(v.counts)),
|
||||
}
|
||||
copy(r.counts, v.counts)
|
||||
rHistograms[k] = r
|
||||
}
|
||||
s.mu.Unlock()
|
||||
|
||||
return end, rGauges, rCounters, rHistograms
|
||||
}
|
||||
|
||||
func (s *Sink) report(ctx context.Context) {
|
||||
end, rGauges, rCounters, rHistograms := s.deep()
|
||||
|
||||
// https://cloud.google.com/monitoring/api/resources
|
||||
resource := &monitoredrespb.MonitoredResource{
|
||||
Type: "generic_task",
|
||||
Labels: map[string]string{
|
||||
"project_id": s.taskInfo.ProjectID,
|
||||
"location": s.taskInfo.Location,
|
||||
"namespace": s.taskInfo.Namespace,
|
||||
"job": s.taskInfo.Job,
|
||||
"task_id": s.taskInfo.TaskID,
|
||||
},
|
||||
}
|
||||
|
||||
ts := []*monitoringpb.TimeSeries{}
|
||||
|
||||
for _, v := range rCounters {
|
||||
name, labels, err := v.name.labelMap(s.extractor, "counter")
|
||||
if err != nil {
|
||||
log.Printf("Could not extract labels from %s: %v", v.name.hash, err)
|
||||
continue
|
||||
}
|
||||
if s.debugLogs {
|
||||
log.Printf("%v is now %s + (%v)\n", v.name.key, name, labels)
|
||||
}
|
||||
ts = append(ts, &monitoringpb.TimeSeries{
|
||||
Metric: &metricpb.Metric{
|
||||
Type: path.Join("custom.googleapis.com", "go-metrics", name),
|
||||
Labels: labels,
|
||||
},
|
||||
MetricKind: metric.MetricDescriptor_GAUGE,
|
||||
Resource: resource,
|
||||
Points: []*monitoringpb.Point{
|
||||
&monitoringpb.Point{
|
||||
Interval: &monitoringpb.TimeInterval{
|
||||
EndTime: &googlepb.Timestamp{
|
||||
Seconds: end.Unix(),
|
||||
},
|
||||
},
|
||||
Value: &monitoringpb.TypedValue{
|
||||
Value: &monitoringpb.TypedValue_DoubleValue{
|
||||
DoubleValue: v.value,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
for _, v := range rGauges {
|
||||
name, labels, err := v.name.labelMap(s.extractor, "gauge")
|
||||
if err != nil {
|
||||
log.Printf("Could not extract labels from %s: %v", v.name.hash, err)
|
||||
continue
|
||||
}
|
||||
if s.debugLogs {
|
||||
log.Printf("%v is now %s + (%v)\n", v.name.key, name, labels)
|
||||
}
|
||||
ts = append(ts, &monitoringpb.TimeSeries{
|
||||
Metric: &metricpb.Metric{
|
||||
Type: path.Join("custom.googleapis.com", "go-metrics", name),
|
||||
Labels: labels,
|
||||
},
|
||||
MetricKind: metric.MetricDescriptor_GAUGE,
|
||||
Resource: resource,
|
||||
Points: []*monitoringpb.Point{
|
||||
&monitoringpb.Point{
|
||||
Interval: &monitoringpb.TimeInterval{
|
||||
EndTime: &googlepb.Timestamp{
|
||||
Seconds: end.Unix(),
|
||||
},
|
||||
},
|
||||
Value: &monitoringpb.TypedValue{
|
||||
Value: &monitoringpb.TypedValue_DoubleValue{
|
||||
DoubleValue: float64(v.value),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
for _, v := range rHistograms {
|
||||
name, labels, err := v.name.labelMap(s.extractor, "histogram")
|
||||
if err != nil {
|
||||
log.Printf("Could not extract labels from %s: %v", v.name.hash, err)
|
||||
continue
|
||||
}
|
||||
if s.debugLogs {
|
||||
log.Printf("%v is now %s + (%v)\n", v.name.key, name, labels)
|
||||
}
|
||||
|
||||
var count int64
|
||||
count = 0
|
||||
for _, i := range v.counts {
|
||||
count += int64(i)
|
||||
}
|
||||
|
||||
ts = append(ts, &monitoringpb.TimeSeries{
|
||||
Metric: &metricpb.Metric{
|
||||
Type: path.Join("custom.googleapis.com", "go-metrics", name),
|
||||
Labels: labels,
|
||||
},
|
||||
MetricKind: metric.MetricDescriptor_CUMULATIVE,
|
||||
Resource: resource,
|
||||
Points: []*monitoringpb.Point{
|
||||
&monitoringpb.Point{
|
||||
Interval: &monitoringpb.TimeInterval{
|
||||
StartTime: &googlepb.Timestamp{
|
||||
Seconds: s.firstTime.Unix(),
|
||||
},
|
||||
EndTime: &googlepb.Timestamp{
|
||||
Seconds: end.Unix(),
|
||||
},
|
||||
},
|
||||
Value: &monitoringpb.TypedValue{
|
||||
Value: &monitoringpb.TypedValue_DistributionValue{
|
||||
DistributionValue: &distributionpb.Distribution{
|
||||
BucketOptions: &distributionpb.Distribution_BucketOptions{
|
||||
Options: &distributionpb.Distribution_BucketOptions_ExplicitBuckets{
|
||||
ExplicitBuckets: &distributionpb.Distribution_BucketOptions_Explicit{
|
||||
Bounds: v.buckets,
|
||||
},
|
||||
},
|
||||
},
|
||||
BucketCounts: v.counts,
|
||||
Count: count,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if s.client == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i < len(ts); i += 200 {
|
||||
end := i + 200
|
||||
|
||||
if end > len(ts) {
|
||||
end = len(ts)
|
||||
}
|
||||
|
||||
req := &monitoringpb.CreateTimeSeriesRequest{
|
||||
Name: fmt.Sprintf("projects/%s", s.taskInfo.ProjectID),
|
||||
TimeSeries: ts[i:end],
|
||||
}
|
||||
err := s.client.CreateTimeSeries(ctx, req)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Failed to write time series data: %v", err)
|
||||
if s.debugLogs {
|
||||
for i, a := range req.TimeSeries {
|
||||
log.Printf("request timeseries[%d]: %v", i, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A Gauge should retain the last value it is set to.
|
||||
func (s *Sink) SetGauge(key []string, val float32) {
|
||||
s.SetGaugeWithLabels(key, val, nil)
|
||||
}
|
||||
|
||||
// A Gauge should retain the last value it is set to.
|
||||
func (s *Sink) SetGaugeWithLabels(key []string, val float32, labels []metrics.Label) {
|
||||
n := newSeries(key, labels)
|
||||
|
||||
g := &gauge{
|
||||
name: n,
|
||||
value: val,
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
s.gauges[n.hash] = g
|
||||
}
|
||||
|
||||
// Should emit a Key/Value pair for each call.
|
||||
func (s *Sink) EmitKey(key []string, val float32) {
|
||||
// EmitKey is not implemented for stackdriver
|
||||
}
|
||||
|
||||
// Counters should accumulate values.
|
||||
func (s *Sink) IncrCounter(key []string, val float32) {
|
||||
s.IncrCounterWithLabels(key, val, nil)
|
||||
}
|
||||
|
||||
// Counters should accumulate values.
|
||||
func (s *Sink) IncrCounterWithLabels(key []string, val float32, labels []metrics.Label) {
|
||||
n := newSeries(key, labels)
|
||||
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
c, ok := s.counters[n.hash]
|
||||
if ok {
|
||||
c.value += float64(val)
|
||||
} else {
|
||||
s.counters[n.hash] = &counter{
|
||||
name: n,
|
||||
value: float64(val),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Samples are for timing information, where quantiles are used.
|
||||
func (s *Sink) AddSample(key []string, val float32) {
|
||||
s.AddSampleWithLabels(key, val, nil)
|
||||
}
|
||||
|
||||
// Samples are for timing information, where quantiles are used.
|
||||
func (s *Sink) AddSampleWithLabels(key []string, val float32, labels []metrics.Label) {
|
||||
n := newSeries(key, labels)
|
||||
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
h, ok := s.histograms[n.hash]
|
||||
if ok {
|
||||
h.addSample(val)
|
||||
} else {
|
||||
h = newHistogram(n, key, s.bucketer)
|
||||
h.addSample(val)
|
||||
s.histograms[n.hash] = h
|
||||
}
|
||||
}
|
||||
|
||||
var _ metrics.MetricSink = (*Sink)(nil)
|
||||
|
||||
// Series holds the naming for a timeseries metric.
|
||||
type series struct {
|
||||
key []string
|
||||
labels []metrics.Label
|
||||
hash string
|
||||
}
|
||||
|
||||
var forbiddenChars = regexp.MustCompile("[ .=\\-/]")
|
||||
|
||||
func newSeries(key []string, labels []metrics.Label) *series {
|
||||
hash := strings.Join(key, "_")
|
||||
hash = forbiddenChars.ReplaceAllString(hash, "_")
|
||||
for _, label := range labels {
|
||||
hash += fmt.Sprintf(";%s=%s", label.Name, label.Value)
|
||||
}
|
||||
|
||||
return &series{
|
||||
key: key,
|
||||
labels: labels,
|
||||
hash: hash,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *series) labelMap(extractor ExtractLabelsFn, kind string) (string, map[string]string, error) {
|
||||
// extract labels from the key
|
||||
key, extractedLabels, err := extractor(s.key, kind)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
name := strings.Join(key, "_")
|
||||
name = forbiddenChars.ReplaceAllString(name, "_")
|
||||
|
||||
o := make(map[string]string, len(s.labels))
|
||||
for _, v := range s.labels {
|
||||
o[v.Name] = v.Value
|
||||
}
|
||||
for _, v := range extractedLabels {
|
||||
o[v.Name] = v.Value
|
||||
}
|
||||
return name, o, nil
|
||||
}
|
||||
|
||||
// https://cloud.google.com/monitoring/api/ref_v3/rest/v3/TimeSeries#point
|
||||
type gauge struct {
|
||||
name *series
|
||||
value float32
|
||||
}
|
||||
|
||||
// https://cloud.google.com/monitoring/api/ref_v3/rest/v3/TimeSeries#point
|
||||
type counter struct {
|
||||
name *series
|
||||
value float64
|
||||
}
|
||||
|
||||
// https://cloud.google.com/monitoring/api/ref_v3/rest/v3/TimeSeries#distribution
|
||||
type histogram struct {
|
||||
name *series
|
||||
buckets []float64
|
||||
counts []int64
|
||||
}
|
||||
|
||||
func newHistogram(name *series, key []string, bucketer BucketFn) *histogram {
|
||||
h := &histogram{
|
||||
name: name,
|
||||
buckets: bucketer(key),
|
||||
}
|
||||
h.counts = make([]int64, len(h.buckets))
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *histogram) addSample(val float32) {
|
||||
for i := range h.buckets {
|
||||
if float64(val) <= h.buckets[i] {
|
||||
h.counts[i]++
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
h.counts[len(h.buckets)-1]++
|
||||
}
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package vault provides helper functions to improve the go-metrics to stackdriver metric
|
||||
// conversions specific to HashiCorp Vault.
|
||||
package vault
|
||||
|
||||
import "github.com/armon/go-metrics"
|
||||
|
||||
// Extractor extracts known patterns from the key into metrics.Label for better metric grouping
|
||||
// and to help avoid the limit of 500 custom metric descriptors per project
|
||||
// (https://cloud.google.com/monitoring/quotas).
|
||||
func Extractor(key []string, kind string) ([]string, []metrics.Label, error) {
|
||||
// Metrics documented at https://www.vaultproject.io/docs/internals/telemetry.html should be
|
||||
// extracted here into a base metric name with appropriate labels extracted from the 'key'.
|
||||
switch len(key) {
|
||||
case 3: // metrics of format: *.*.*
|
||||
// vault.database.<method>
|
||||
if key[0] == "vault" && key[1] == "database" {
|
||||
return typeWrap(key[:2], kind), []metrics.Label{
|
||||
{
|
||||
Name: "method",
|
||||
Value: key[2],
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
// vault.token.create_root
|
||||
if key[0] == "vault" && key[1] == "token" && key[2] == "create_root" {
|
||||
return typeWrap(key, kind), nil, nil
|
||||
}
|
||||
|
||||
// vault.barrier.<method>
|
||||
// vault.token.<method>
|
||||
// vault.policy.<method>
|
||||
if key[0] == "vault" && (key[1] == "barrier" || key[1] == "token" || key[1] == "policy") {
|
||||
return typeWrap(key[:2], kind), []metrics.Label{
|
||||
{
|
||||
Name: "method",
|
||||
Value: key[2],
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
// vault.<backend>.<method>
|
||||
if key[0] == "vault" && (key[2] == "put" || key[2] == "get" || key[2] == "delete" || key[2] == "list") {
|
||||
return typeWrap(key[:2], kind), []metrics.Label{
|
||||
{
|
||||
Name: "method",
|
||||
Value: key[2],
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
case 4: // metrics of format: *.*.*.*
|
||||
// vault.route.<method>.<mount>
|
||||
if key[0] == "vault" && key[1] == "route" {
|
||||
return typeWrap(key[:2], kind), []metrics.Label{
|
||||
{
|
||||
Name: "method",
|
||||
Value: key[2],
|
||||
},
|
||||
{
|
||||
Name: "mount",
|
||||
Value: key[3],
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
// vault.audit.<type>.*
|
||||
if key[0] == "vault" && key[1] == "audit" {
|
||||
return typeWrap([]string{"vault", "audit", key[3]}, kind), []metrics.Label{
|
||||
{
|
||||
Name: "type",
|
||||
Value: key[2],
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
// vault.rollback.attempt.<mount>
|
||||
if key[0] == "vault" && key[1] == "rollback" && key[2] == "attempt" {
|
||||
return typeWrap(key[:3], kind), []metrics.Label{
|
||||
{
|
||||
Name: "mount",
|
||||
Value: key[3],
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
// vault.<backend>.lock.<method>
|
||||
if key[0] == "vault" && key[2] == "lock" {
|
||||
return typeWrap(key[:3], kind), []metrics.Label{
|
||||
{
|
||||
Name: "method",
|
||||
Value: key[3],
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
// vault.database.<name>.<method>
|
||||
// note: there are vault.database.<method>.error counters. Those are handled separately.
|
||||
if key[0] == "vault" && key[1] == "database" && key[3] != "error" {
|
||||
return typeWrap(key[:2], kind), []metrics.Label{
|
||||
{
|
||||
Name: "name",
|
||||
Value: key[2],
|
||||
},
|
||||
{
|
||||
Name: "method",
|
||||
Value: key[3],
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
//ivault.database.<method>.error
|
||||
if key[0] == "vault" && key[1] == "database" && key[3] == "error" {
|
||||
return typeWrap([]string{"vault", "database", "error"}, kind), []metrics.Label{
|
||||
{
|
||||
Name: "method",
|
||||
Value: key[2],
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
case 5:
|
||||
// vault.database.<name>.<method>.error
|
||||
if key[0] == "vault" && key[1] == "database" && key[4] == "error" {
|
||||
return typeWrap([]string{key[0], key[1], key[4]}, kind), []metrics.Label{
|
||||
{
|
||||
Name: "name",
|
||||
Value: key[2],
|
||||
},
|
||||
{
|
||||
Name: "method",
|
||||
Value: key[3],
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
default:
|
||||
// unknown key pattern, keep it as-is.
|
||||
}
|
||||
return typeWrap(key, kind), nil, nil
|
||||
}
|
||||
|
||||
func typeWrap(key []string, kind string) []string {
|
||||
out := []string{}
|
||||
for _, a := range key {
|
||||
out = append(out, a)
|
||||
}
|
||||
switch kind {
|
||||
case "counter":
|
||||
return append(out, kind)
|
||||
case "gauge":
|
||||
return append(out, kind)
|
||||
default:
|
||||
return out
|
||||
}
|
||||
}
|
||||
|
||||
// Bucketer specifies the bucket boundaries that should be used for the given metric key.
|
||||
func Bucketer(key []string) []float64 {
|
||||
// These were chosen to give some reasonable boundaires for RPC times in the 10-100ms range and
|
||||
// then rough values for 1-5 seconds.
|
||||
// TODO: investigate better boundaires for different metrics.
|
||||
return []float64{10.0, 25.0, 50.0, 100.0, 150.0, 200.0, 250.0, 300.0, 500.0, 1000.0, 1500.0, 2000.0, 3000.0, 4000.0, 5000.0}
|
||||
}
|
||||
Reference in new issue
Block a user