forked from TrueCloudLab/rclone
Tardigrade Backend: Dependencies
This commit is contained in:
parent
962fbc8257
commit
03b629064a
544 changed files with 86690 additions and 2 deletions
1
vendor/github.com/spacemonkeygo/monkit/v3/AUTHORS
generated
vendored
Normal file
1
vendor/github.com/spacemonkeygo/monkit/v3/AUTHORS
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Space Monkey, Inc.
|
191
vendor/github.com/spacemonkeygo/monkit/v3/LICENSE
generated
vendored
Normal file
191
vendor/github.com/spacemonkeygo/monkit/v3/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,191 @@
|
|||
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:
|
||||
|
||||
You must give any other recipients of the Work or Derivative Works a copy of
|
||||
this License; and
|
||||
You must cause any modified files to carry prominent notices stating that You
|
||||
changed the files; and
|
||||
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
|
||||
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.
|
637
vendor/github.com/spacemonkeygo/monkit/v3/README.md
generated
vendored
Normal file
637
vendor/github.com/spacemonkeygo/monkit/v3/README.md
generated
vendored
Normal file
|
@ -0,0 +1,637 @@
|
|||
# 
|
||||
|
||||
Package monkit is a flexible code instrumenting and data collection library.
|
||||
|
||||
See documentation at https://godoc.org/gopkg.in/spacemonkeygo/monkit.v3
|
||||
|
||||
Software is hard. Like, really hard.
|
||||
[Just the worst](http://www.stilldrinking.org/programming-sucks). Sometimes it
|
||||
feels like we've constructed a field where the whole point is to see how
|
||||
tangled we can get ourselves before seeing if we can get tangled up more while
|
||||
trying to get untangled.
|
||||
|
||||
Many software engineering teams are coming to realize (some slower than others)
|
||||
that collecting data over time about how their systems are functioning is a
|
||||
super power you can't turn back from. Some teams are calling this
|
||||
[Telemetry](http://techblog.netflix.com/2014/12/introducing-atlas-netflixs-primary.html),
|
||||
[Observability](https://blog.twitter.com/2013/observability-at-twitter), or
|
||||
describing it more basically through subcomponents such as
|
||||
[distributed tracing](http://research.google.com/pubs/pub36356.html),
|
||||
[time-series data](https://influxdata.com/), or even just
|
||||
[metrics](http://metrics.dropwizard.io/). We've been calling it monitoring, but
|
||||
geez, I suppose if trends continue and you want to do this yourself your first
|
||||
step should be to open a thesaurus and pick an unused term.
|
||||
|
||||
I'm not here to tell you about our whole platform. Instead, I'm here to
|
||||
explain a redesign of a Go library for instrumenting your Go programs that we
|
||||
rather quietly launched a few years ago. If you are already using version 1 of
|
||||
our [old library](https://github.com/spacemonkeygo/monitor), we're sorry, but
|
||||
we rewrote it from scratch and renamed it to monkit. This one (this one!) is
|
||||
better - you should switch!
|
||||
|
||||
I'm going to try and sell you as fast as I can on this library.
|
||||
|
||||
## Example usage
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"gopkg.in/spacemonkeygo/monkit.v3"
|
||||
"gopkg.in/spacemonkeygo/monkit.v3/environment"
|
||||
"gopkg.in/spacemonkeygo/monkit.v3/present"
|
||||
)
|
||||
|
||||
var (
|
||||
mon = monkit.Package()
|
||||
)
|
||||
|
||||
func ComputeThing(ctx context.Context, arg1, arg2 int) (res int, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
timer := mon.Timer("subcomputation").Start()
|
||||
res = arg1 + arg2
|
||||
timer.Stop()
|
||||
|
||||
if res == 3 {
|
||||
mon.Event("hit 3")
|
||||
}
|
||||
|
||||
mon.BoolVal("was-4").Observe(res == 4)
|
||||
mon.IntVal("res").Observe(int64(res))
|
||||
mon.Counter("calls").Inc(1)
|
||||
mon.Gauge("arg1", func() float64 { return float64(arg1) })
|
||||
mon.Meter("arg2").Mark(arg2)
|
||||
|
||||
return arg1 + arg2, nil
|
||||
}
|
||||
|
||||
func DoStuff(ctx context.Context) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
result, err := ComputeThing(ctx, 1, 2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(result)
|
||||
return
|
||||
}
|
||||
|
||||
func main() {
|
||||
environment.Register(monkit.Default)
|
||||
go http.ListenAndServe("localhost:9000", present.HTTP(monkit.Default))
|
||||
log.Println(DoStuff(context.Background()))
|
||||
}
|
||||
```
|
||||
|
||||
## Metrics
|
||||
|
||||
We've got tools that capture distribution information (including quantiles)
|
||||
about int64, float64, and bool types. We have tools that capture data about
|
||||
events (we've got meters for deltas, rates, etc). We have rich tools for
|
||||
capturing information about tasks and functions, and literally anything that
|
||||
can generate a name and a number.
|
||||
|
||||
Almost just as importantly, the amount of boilerplate and code you have to
|
||||
write to get these features is very minimal. Data that's hard to measure
|
||||
probably won't get measured.
|
||||
|
||||
This data can be collected and sent to [Graphite](http://graphite.wikidot.com/)
|
||||
or any other time-series database.
|
||||
|
||||
Here's a selection of live stats from one of our storage nodes:
|
||||
|
||||
```
|
||||
env.os.fds 120.000000
|
||||
env.os.proc.stat.Minflt 81155.000000
|
||||
env.os.proc.stat.Cminflt 11789.000000
|
||||
env.os.proc.stat.Majflt 10.000000
|
||||
env.os.proc.stat.Cmajflt 6.000000
|
||||
...
|
||||
|
||||
env.process.control 1.000000
|
||||
env.process.crc 3819014369.000000
|
||||
env.process.uptime 163225.292925
|
||||
env.runtime.goroutines 52.000000
|
||||
env.runtime.memory.Alloc 2414080.000000
|
||||
...
|
||||
|
||||
env.rusage.Maxrss 26372.000000
|
||||
...
|
||||
|
||||
sm/flud/csl/client.(*CSLClient).Verify.current 0.000000
|
||||
sm/flud/csl/client.(*CSLClient).Verify.success 788.000000
|
||||
sm/flud/csl/client.(*CSLClient).Verify.error volume missing 91.000000
|
||||
sm/flud/csl/client.(*CSLClient).Verify.error dial error 1.000000
|
||||
sm/flud/csl/client.(*CSLClient).Verify.panics 0.000000
|
||||
sm/flud/csl/client.(*CSLClient).Verify.success times min 0.102214
|
||||
sm/flud/csl/client.(*CSLClient).Verify.success times avg 1.899133
|
||||
sm/flud/csl/client.(*CSLClient).Verify.success times max 8.601230
|
||||
sm/flud/csl/client.(*CSLClient).Verify.success times recent 2.673128
|
||||
sm/flud/csl/client.(*CSLClient).Verify.failure times min 0.682881
|
||||
sm/flud/csl/client.(*CSLClient).Verify.failure times avg 3.936571
|
||||
sm/flud/csl/client.(*CSLClient).Verify.failure times max 6.102318
|
||||
sm/flud/csl/client.(*CSLClient).Verify.failure times recent 2.208020
|
||||
sm/flud/csl/server.store.avg 710800.000000
|
||||
sm/flud/csl/server.store.count 271.000000
|
||||
sm/flud/csl/server.store.max 3354194.000000
|
||||
sm/flud/csl/server.store.min 467.000000
|
||||
sm/flud/csl/server.store.recent 1661376.000000
|
||||
sm/flud/csl/server.store.sum 192626890.000000
|
||||
...
|
||||
```
|
||||
|
||||
## Call graphs
|
||||
|
||||
This library generates call graphs of your live process for you.
|
||||
|
||||
These call graphs aren't created through sampling. They're full pictures of all
|
||||
of the interesting functions you've annotated, along with quantile information
|
||||
about their successes, failures, how often they panic, return an error (if so
|
||||
instrumented), how many are currently running, etc.
|
||||
|
||||
The data can be returned in dot format, in json, in text, and can be about
|
||||
just the functions that are currently executing, or all the functions the
|
||||
monitoring system has ever seen.
|
||||
|
||||
Here's another example of one of our production nodes:
|
||||
|
||||

|
||||
|
||||
## Trace graphs
|
||||
|
||||
This library generates trace graphs of your live process for you directly,
|
||||
without requiring standing up some tracing system such as Zipkin (though you
|
||||
can do that too).
|
||||
|
||||
Inspired by [Google's Dapper](http://research.google.com/pubs/pub36356.html)
|
||||
and [Twitter's Zipkin](http://zipkin.io), we have process-internal trace
|
||||
graphs, triggerable by a number of different methods.
|
||||
|
||||
You get this trace information for free whenever you use
|
||||
[Go contexts](https://blog.golang.org/context) and function monitoring. The
|
||||
output formats are svg and json.
|
||||
|
||||
Additionally, the library supports trace observation plugins, and we've written
|
||||
[a plugin that sends this data to Zipkin](http://github.com/spacemonkeygo/monkit-zipkin).
|
||||
|
||||

|
||||
|
||||
## History
|
||||
|
||||
Before our crazy
|
||||
[Go rewrite of everything](https://www.spacemonkey.com/blog/posts/go-space-monkey)
|
||||
(and before we had even seen Google's Dapper paper), we were a Python shop, and
|
||||
all of our "interesting" functions were decorated with a helper that collected
|
||||
timing information and sent it to Graphite.
|
||||
|
||||
When we transliterated to Go, we wanted to preserve that functionality, so the
|
||||
first version of our monitoring package was born.
|
||||
|
||||
Over time it started to get janky, especially as we found Zipkin and started
|
||||
adding tracing functionality to it. We rewrote all of our Go code to use Google
|
||||
contexts, and then realized we could get call graph information. We decided a
|
||||
refactor and then an all-out rethinking of our monitoring package was best,
|
||||
and so now we have this library.
|
||||
|
||||
## Aside about contexts
|
||||
|
||||
Sometimes you really want callstack contextual information without having to
|
||||
pass arguments through everything on the call stack. In other languages, many
|
||||
people implement this with thread-local storage.
|
||||
|
||||
Example: let's say you have written a big system that responds to user
|
||||
requests. All of your libraries log using your log library. During initial
|
||||
development everything is easy to debug, since there's low user load, but now
|
||||
you've scaled and there's OVER TEN USERS and it's kind of hard to tell what log
|
||||
lines were caused by what. Wouldn't it be nice to add request ids to all of the
|
||||
log lines kicked off by that request? Then you could grep for all log lines
|
||||
caused by a specific request id. Geez, it would suck to have to pass all
|
||||
contextual debugging information through all of your callsites.
|
||||
|
||||
Google solved this problem by always passing a `context.Context` interface
|
||||
through from call to call. A `Context` is basically just a mapping of arbitrary
|
||||
keys to arbitrary values that users can add new values for. This way if you
|
||||
decide to add a request context, you can add it to your `Context` and then all
|
||||
callsites that descend from that place will have the new data in their contexts.
|
||||
|
||||
It is admittedly very verbose to add contexts to every function call.
|
||||
Painfully so. I hope to write more about it in the future, but [Google also
|
||||
wrote up their thoughts about it](https://blog.golang.org/context), which you
|
||||
can go read. For now, just swallow your disgust and let's keep moving.
|
||||
|
||||
## Motivating program
|
||||
|
||||
Let's make a super simple [Varnish](https://www.varnish-cache.org/) clone.
|
||||
Open up gedit! (Okay just kidding, open whatever text editor you want.)
|
||||
|
||||
For this motivating program, we won't even add the caching, though there's
|
||||
comments for where to add it if you'd like. For now, let's just make a
|
||||
barebones system that will proxy HTTP requests. We'll call it VLite, but
|
||||
maybe we should call it VReallyLite.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type VLite struct {
|
||||
target *url.URL
|
||||
proxy *httputil.ReverseProxy
|
||||
}
|
||||
|
||||
func NewVLite(target *url.URL) *VLite {
|
||||
return &VLite{
|
||||
target: target,
|
||||
proxy: httputil.NewSingleHostReverseProxy(target),
|
||||
}
|
||||
}
|
||||
|
||||
func (v *VLite) Proxy(w http.ResponseWriter, r *http.Request) {
|
||||
r.Host = v.target.Host // let the proxied server get the right vhost
|
||||
v.proxy.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func (v *VLite) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// here's where you'd put caching logic
|
||||
v.Proxy(w, r)
|
||||
}
|
||||
|
||||
func main() {
|
||||
target := flag.String(
|
||||
"proxy",
|
||||
"http://hasthelargehadroncolliderdestroyedtheworldyet.com/",
|
||||
"server to cache")
|
||||
flag.Parse()
|
||||
targetURL, err := url.Parse(*target)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
panic(http.ListenAndServe(":8080", NewVLite(targetURL)))
|
||||
}
|
||||
```
|
||||
|
||||
Run and build this and open `localhost:8080` in your browser. If you use the
|
||||
default proxy target, it should inform you that the world hasn't been
|
||||
destroyed yet.
|
||||
|
||||
## Adding basic instrumentation
|
||||
|
||||
The first thing you'll want to do is add the small amount of boilerplate to
|
||||
make the instrumentation we're going to add to your process observable later.
|
||||
|
||||
Import the basic monkit packages:
|
||||
|
||||
```go
|
||||
"gopkg.in/spacemonkeygo/monkit.v3"
|
||||
"gopkg.in/spacemonkeygo/monkit.v3/environment"
|
||||
"gopkg.in/spacemonkeygo/monkit.v3/present"
|
||||
```
|
||||
|
||||
and then register environmental statistics and kick off a goroutine in your
|
||||
main method to serve debug requests:
|
||||
|
||||
```go
|
||||
environment.Register(monkit.Default)
|
||||
go http.ListenAndServe("localhost:9000", present.HTTP(monkit.Default))
|
||||
```
|
||||
|
||||
Rebuild, and then check out `localhost:9000/stats` (or
|
||||
`localhost:9000/stats/json`, if you prefer) in your browser!
|
||||
|
||||
## Request contexts
|
||||
|
||||
Remember what I said about [Google's contexts](https://blog.golang.org/context)?
|
||||
It might seem a bit overkill for such a small project, but it's time to add
|
||||
them.
|
||||
|
||||
To help out here, I've created a library that constructs contexts for you
|
||||
for incoming HTTP requests. Nothing that's about to happen requires my
|
||||
[webhelp library](https://godoc.org/github.com/jtolds/webhelp), but here is the
|
||||
code now refactored to receive and pass contexts through our two per-request
|
||||
calls.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
|
||||
"github.com/jtolds/webhelp"
|
||||
"gopkg.in/spacemonkeygo/monkit.v3"
|
||||
"gopkg.in/spacemonkeygo/monkit.v3/environment"
|
||||
"gopkg.in/spacemonkeygo/monkit.v3/present"
|
||||
)
|
||||
|
||||
type VLite struct {
|
||||
target *url.URL
|
||||
proxy *httputil.ReverseProxy
|
||||
}
|
||||
|
||||
func NewVLite(target *url.URL) *VLite {
|
||||
return &VLite{
|
||||
target: target,
|
||||
proxy: httputil.NewSingleHostReverseProxy(target),
|
||||
}
|
||||
}
|
||||
|
||||
func (v *VLite) Proxy(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
r.Host = v.target.Host // let the proxied server get the right vhost
|
||||
v.proxy.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func (v *VLite) HandleHTTP(ctx context.Context, w webhelp.ResponseWriter, r *http.Request) error {
|
||||
// here's where you'd put caching logic
|
||||
v.Proxy(ctx, w, r)
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
target := flag.String(
|
||||
"proxy",
|
||||
"http://hasthelargehadroncolliderdestroyedtheworldyet.com/",
|
||||
"server to cache")
|
||||
flag.Parse()
|
||||
targetURL, err := url.Parse(*target)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
environment.Register(monkit.Default)
|
||||
go http.ListenAndServe("localhost:9000", present.HTTP(monkit.Default))
|
||||
panic(webhelp.ListenAndServe(":8080", NewVLite(targetURL)))
|
||||
}
|
||||
```
|
||||
|
||||
You can create a new context for a request however you want. One reason to use
|
||||
something like webhelp is that the cancelation feature of Contexts is hooked
|
||||
up to the HTTP request getting canceled.
|
||||
|
||||
## Monitor some requests
|
||||
|
||||
Let's start to get statistics about how many requests we receive! First, this
|
||||
package (main) will need to get a monitoring Scope. Add this global definition
|
||||
right after all your imports, much like you'd create a logger with many logging
|
||||
libraries:
|
||||
|
||||
```go
|
||||
var mon = monkit.Package()
|
||||
```
|
||||
|
||||
Now, make the error return value of HandleHTTP named (so, (err error)), and add
|
||||
this defer line as the very first instruction of HandleHTTP:
|
||||
|
||||
```go
|
||||
func (v *VLite) HandleHTTP(ctx context.Context, w webhelp.ResponseWriter, r *http.Request) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
```
|
||||
|
||||
Let's also add the same line (albeit modified for the lack of error) to
|
||||
Proxy, replacing &err with nil:
|
||||
|
||||
```go
|
||||
func (v *VLite) Proxy(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
defer mon.Task()(&ctx)(nil)
|
||||
```
|
||||
|
||||
You should now have something like:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
|
||||
"github.com/jtolds/webhelp"
|
||||
"gopkg.in/spacemonkeygo/monkit.v3"
|
||||
"gopkg.in/spacemonkeygo/monkit.v3/environment"
|
||||
"gopkg.in/spacemonkeygo/monkit.v3/present"
|
||||
)
|
||||
|
||||
var mon = monkit.Package()
|
||||
|
||||
type VLite struct {
|
||||
target *url.URL
|
||||
proxy *httputil.ReverseProxy
|
||||
}
|
||||
|
||||
func NewVLite(target *url.URL) *VLite {
|
||||
return &VLite{
|
||||
target: target,
|
||||
proxy: httputil.NewSingleHostReverseProxy(target),
|
||||
}
|
||||
}
|
||||
|
||||
func (v *VLite) Proxy(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
defer mon.Task()(&ctx)(nil)
|
||||
r.Host = v.target.Host // let the proxied server get the right vhost
|
||||
v.proxy.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func (v *VLite) HandleHTTP(ctx context.Context, w webhelp.ResponseWriter, r *http.Request) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
// here's where you'd put caching logic
|
||||
v.Proxy(ctx, w, r)
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
target := flag.String(
|
||||
"proxy",
|
||||
"http://hasthelargehadroncolliderdestroyedtheworldyet.com/",
|
||||
"server to cache")
|
||||
flag.Parse()
|
||||
targetURL, err := url.Parse(*target)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
environment.Register(monkit.Default)
|
||||
go http.ListenAndServe("localhost:9000", present.HTTP(monkit.Default))
|
||||
panic(webhelp.ListenAndServe(":8080", NewVLite(targetURL)))
|
||||
}
|
||||
```
|
||||
|
||||
We'll unpack what's going on here, but for now:
|
||||
|
||||
* Rebuild and restart!
|
||||
* Trigger a full refresh at `localhost:8080` to make sure your new HTTP
|
||||
handler runs
|
||||
* Visit `localhost:9000/stats` and then `localhost:9000/funcs`
|
||||
|
||||
For this new funcs dataset, if you want a graph, you can download a dot
|
||||
graph at `localhost:9000/funcs/dot` and json information from
|
||||
`localhost:9000/funcs/json`.
|
||||
|
||||
You should see something like:
|
||||
|
||||
```
|
||||
[3693964236144930897] main.(*VLite).HandleHTTP
|
||||
parents: entry
|
||||
current: 0, highwater: 1, success: 2, errors: 0, panics: 0
|
||||
success times:
|
||||
0.00: 63.930436ms
|
||||
0.10: 70.482159ms
|
||||
0.25: 80.309745ms
|
||||
0.50: 96.689054ms
|
||||
0.75: 113.068363ms
|
||||
0.90: 122.895948ms
|
||||
0.95: 126.17181ms
|
||||
1.00: 129.447675ms
|
||||
avg: 96.689055ms
|
||||
failure times:
|
||||
0.00: 0
|
||||
0.10: 0
|
||||
0.25: 0
|
||||
0.50: 0
|
||||
0.75: 0
|
||||
0.90: 0
|
||||
0.95: 0
|
||||
1.00: 0
|
||||
avg: 0
|
||||
```
|
||||
|
||||
with a similar report for the Proxy method, or a graph like:
|
||||
|
||||

|
||||
|
||||
This data reports the overall callgraph of execution for known traces, along
|
||||
with how many of each function are currently running, the most running
|
||||
concurrently (the highwater), how many were successful along with quantile
|
||||
timing information, how many errors there were (with quantile timing
|
||||
information if applicable), and how many panics there were. Since the Proxy
|
||||
method isn't capturing a returned err value, and since HandleHTTP always
|
||||
returns nil, this example won't ever have failures.
|
||||
|
||||
If you're wondering about the success count being higher than you expected,
|
||||
keep in mind your browser probably requested a favicon.ico.
|
||||
|
||||
Cool, eh?
|
||||
|
||||
## How it works
|
||||
|
||||
```go
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
```
|
||||
|
||||
is an interesting line of code - there's three function calls. If you look at
|
||||
the Go spec, all of the function calls will run at the time the function starts
|
||||
except for the very last one.
|
||||
|
||||
The first function call, mon.Task(), creates or looks up a wrapper around a
|
||||
Func. You could get this yourself by requesting mon.Func() inside of the
|
||||
appropriate function or mon.FuncNamed(). Both mon.Task() and mon.Func()
|
||||
are inspecting runtime.Caller to determine the name of the function. Because
|
||||
this is a heavy operation, you can actually store the result of mon.Task() and
|
||||
reuse it somehow else if you prefer, so instead of
|
||||
|
||||
```go
|
||||
func MyFunc(ctx context.Context) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
}
|
||||
```
|
||||
|
||||
you could instead use
|
||||
|
||||
```go
|
||||
var myFuncMon = mon.Task()
|
||||
|
||||
func MyFunc(ctx context.Context) (err error) {
|
||||
defer myFuncMon(&ctx)(&err)
|
||||
}
|
||||
```
|
||||
|
||||
which is more performant every time after the first time. runtime.Caller only
|
||||
gets called once.
|
||||
|
||||
Careful! Don't use the same myFuncMon in different functions unless you want to
|
||||
screw up your statistics!
|
||||
|
||||
The second function call starts all the various stop watches and bookkeeping to
|
||||
keep track of the function. It also mutates the context pointer it's given to
|
||||
extend the context with information about what current span (in Zipkin
|
||||
parlance) is active. Notably, you *can* pass nil for the context if you really
|
||||
don't want a context. You just lose callgraph information.
|
||||
|
||||
The last function call stops all the stop watches ad makes a note of any
|
||||
observed errors or panics (it repanics after observing them).
|
||||
|
||||
## Tracing
|
||||
|
||||
Turns out, we don't even need to change our program anymore to get rich tracing
|
||||
information!
|
||||
|
||||
Open your browser and go to `localhost:9000/trace/svg?regex=HandleHTTP`. It
|
||||
won't load, and in fact, it's waiting for you to open another tab and refresh
|
||||
`localhost:8080` again. Once you retrigger the actual application behavior,
|
||||
the trace regex will capture a trace starting on the first function that
|
||||
matches the supplied regex, and return an svg. Go back to your first tab, and
|
||||
you should see a relatively uninteresting but super promising svg.
|
||||
|
||||
Let's make the trace more interesting. Add a
|
||||
|
||||
```go
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
```
|
||||
|
||||
to your HandleHTTP method, rebuild, and restart. Load `localhost:8080`, then
|
||||
start a new request to your trace URL, then reload `localhost:8080` again. Flip
|
||||
back to your trace, and you should see that the Proxy method only takes a
|
||||
portion of the time of HandleHTTP!
|
||||
|
||||

|
||||
|
||||
There's multiple ways to select a trace. You can select by regex using the
|
||||
preselect method (default), which first evaluates the regex on all known
|
||||
functions for sanity checking. Sometimes, however, the function you want to
|
||||
trace may not yet be known to monkit, in which case you'll want
|
||||
to turn preselection off. You may have a bad regex, or you may be in this case
|
||||
if you get the error "Bad Request: regex preselect matches 0 functions."
|
||||
|
||||
Another way to select a trace is by providing a trace id, which we'll get to
|
||||
next!
|
||||
|
||||
Make sure to check out what the addition of the time.Sleep call did to the
|
||||
other reports.
|
||||
|
||||
## Plugins
|
||||
|
||||
It's easy to write plugins for monkit! Check out our first one that exports
|
||||
data to [Zipkin](http://zipkin.io/)'s Scribe API:
|
||||
|
||||
* https://github.com/spacemonkeygo/monkit-zipkin
|
||||
|
||||
We plan to have more (for HTrace, OpenTracing, etc, etc), soon!
|
||||
|
||||
## License
|
||||
|
||||
Copyright (C) 2016 Space Monkey, Inc.
|
||||
|
||||
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.
|
48
vendor/github.com/spacemonkeygo/monkit/v3/callers.go
generated
vendored
Normal file
48
vendor/github.com/spacemonkeygo/monkit/v3/callers.go
generated
vendored
Normal file
|
@ -0,0 +1,48 @@
|
|||
// Copyright (C) 2015 Space Monkey, Inc.
|
||||
//
|
||||
// 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 monkit
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func callerPackage(frames int) string {
|
||||
var pc [1]uintptr
|
||||
if runtime.Callers(frames+2, pc[:]) != 1 {
|
||||
return "unknown"
|
||||
}
|
||||
frame, _ := runtime.CallersFrames(pc[:]).Next()
|
||||
if frame.Func == nil {
|
||||
return "unknown"
|
||||
}
|
||||
slash_pieces := strings.Split(frame.Func.Name(), "/")
|
||||
dot_pieces := strings.SplitN(slash_pieces[len(slash_pieces)-1], ".", 2)
|
||||
return strings.Join(slash_pieces[:len(slash_pieces)-1], "/") + "/" + dot_pieces[0]
|
||||
}
|
||||
|
||||
func callerFunc(frames int) string {
|
||||
var pc [1]uintptr
|
||||
if runtime.Callers(frames+3, pc[:]) != 1 {
|
||||
return "unknown"
|
||||
}
|
||||
frame, _ := runtime.CallersFrames(pc[:]).Next()
|
||||
if frame.Function == "" {
|
||||
return "unknown"
|
||||
}
|
||||
slash_pieces := strings.Split(frame.Function, "/")
|
||||
dot_pieces := strings.SplitN(slash_pieces[len(slash_pieces)-1], ".", 2)
|
||||
return dot_pieces[len(dot_pieces)-1]
|
||||
}
|
75
vendor/github.com/spacemonkeygo/monkit/v3/cas_safe.go
generated
vendored
Normal file
75
vendor/github.com/spacemonkeygo/monkit/v3/cas_safe.go
generated
vendored
Normal file
|
@ -0,0 +1,75 @@
|
|||
// Copyright (C) 2015 Space Monkey, Inc.
|
||||
//
|
||||
// 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.
|
||||
|
||||
// +build appengine
|
||||
|
||||
package monkit
|
||||
|
||||
import "sync"
|
||||
|
||||
// TODO(jeff): make this mutex smaller scoped, perhaps based on the arguments
|
||||
// to compare and swap?
|
||||
var bigHonkinMutex sync.Mutex
|
||||
|
||||
func loadFunc(addr **Func) (s *Func) {
|
||||
bigHonkinMutex.Lock()
|
||||
s = *addr
|
||||
bigHonkinMutex.Unlock()
|
||||
return s
|
||||
}
|
||||
|
||||
func compareAndSwapFunc(addr **Func, old, new *Func) bool {
|
||||
bigHonkinMutex.Lock()
|
||||
val := *addr
|
||||
if val == old {
|
||||
*addr = new
|
||||
bigHonkinMutex.Unlock()
|
||||
return true
|
||||
}
|
||||
bigHonkinMutex.Unlock()
|
||||
return false
|
||||
}
|
||||
|
||||
func loadTraceWatcherRef(addr **traceWatcherRef) (val *traceWatcherRef) {
|
||||
bigHonkinMutex.Lock()
|
||||
val = *addr
|
||||
bigHonkinMutex.Unlock()
|
||||
return val
|
||||
}
|
||||
|
||||
func storeTraceWatcherRef(addr **traceWatcherRef, val *traceWatcherRef) {
|
||||
bigHonkinMutex.Lock()
|
||||
*addr = val
|
||||
bigHonkinMutex.Unlock()
|
||||
}
|
||||
|
||||
func compareAndSwapSpanObserverTuple(addr **spanObserverTuple,
|
||||
old, new *spanObserverTuple) bool {
|
||||
bigHonkinMutex.Lock()
|
||||
val := *addr
|
||||
if val == old {
|
||||
*addr = new
|
||||
bigHonkinMutex.Unlock()
|
||||
return true
|
||||
}
|
||||
bigHonkinMutex.Unlock()
|
||||
return false
|
||||
}
|
||||
|
||||
func loadSpanObserverTuple(addr **spanObserverTuple) (val *spanObserverTuple) {
|
||||
bigHonkinMutex.Lock()
|
||||
val = *addr
|
||||
bigHonkinMutex.Unlock()
|
||||
return val
|
||||
}
|
70
vendor/github.com/spacemonkeygo/monkit/v3/cas_unsafe.go
generated
vendored
Normal file
70
vendor/github.com/spacemonkeygo/monkit/v3/cas_unsafe.go
generated
vendored
Normal file
|
@ -0,0 +1,70 @@
|
|||
// Copyright (C) 2015 Space Monkey, Inc.
|
||||
//
|
||||
// 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.
|
||||
|
||||
// +build !appengine
|
||||
|
||||
package monkit
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
//
|
||||
// *Func atomic functions
|
||||
//
|
||||
|
||||
func loadFunc(addr **Func) (val *Func) {
|
||||
return (*Func)(atomic.LoadPointer(
|
||||
(*unsafe.Pointer)(unsafe.Pointer(addr))))
|
||||
}
|
||||
|
||||
func compareAndSwapFunc(addr **Func, old, new *Func) bool {
|
||||
return atomic.CompareAndSwapPointer(
|
||||
(*unsafe.Pointer)(unsafe.Pointer(addr)),
|
||||
unsafe.Pointer(old),
|
||||
unsafe.Pointer(new))
|
||||
}
|
||||
|
||||
//
|
||||
// *traceWatcherRef atomic functions
|
||||
//
|
||||
|
||||
func loadTraceWatcherRef(addr **traceWatcherRef) (val *traceWatcherRef) {
|
||||
return (*traceWatcherRef)(atomic.LoadPointer(
|
||||
(*unsafe.Pointer)(unsafe.Pointer(addr))))
|
||||
}
|
||||
|
||||
func storeTraceWatcherRef(addr **traceWatcherRef, val *traceWatcherRef) {
|
||||
atomic.StorePointer(
|
||||
(*unsafe.Pointer)(unsafe.Pointer(addr)),
|
||||
unsafe.Pointer(val))
|
||||
}
|
||||
|
||||
//
|
||||
// *spanObserverTuple atomic functons
|
||||
//
|
||||
|
||||
func compareAndSwapSpanObserverTuple(addr **spanObserverTuple,
|
||||
old, new *spanObserverTuple) bool {
|
||||
return atomic.CompareAndSwapPointer(
|
||||
(*unsafe.Pointer)(unsafe.Pointer(addr)),
|
||||
unsafe.Pointer(old),
|
||||
unsafe.Pointer(new))
|
||||
}
|
||||
|
||||
func loadSpanObserverTuple(addr **spanObserverTuple) (val *spanObserverTuple) {
|
||||
return (*spanObserverTuple)(atomic.LoadPointer(
|
||||
(*unsafe.Pointer)(unsafe.Pointer(addr))))
|
||||
}
|
129
vendor/github.com/spacemonkeygo/monkit/v3/counter.go
generated
vendored
Normal file
129
vendor/github.com/spacemonkeygo/monkit/v3/counter.go
generated
vendored
Normal file
|
@ -0,0 +1,129 @@
|
|||
// Copyright (C) 2015 Space Monkey, Inc.
|
||||
//
|
||||
// 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 monkit
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Counter keeps track of running totals, along with the highest and lowest
|
||||
// values seen. The overall value can increment or decrement. Counter
|
||||
// implements StatSource. Should be constructed with NewCounter(), though it
|
||||
// may be more convenient to use the Counter accessor on a given Scope.
|
||||
// Expected creation is like:
|
||||
//
|
||||
// var mon = monkit.Package()
|
||||
//
|
||||
// func MyFunc() {
|
||||
// mon.Counter("beans").Inc(1)
|
||||
// }
|
||||
//
|
||||
type Counter struct {
|
||||
mtx sync.Mutex
|
||||
val, low, high int64
|
||||
nonempty bool
|
||||
key SeriesKey
|
||||
}
|
||||
|
||||
// NewCounter constructs a counter
|
||||
func NewCounter(key SeriesKey) *Counter {
|
||||
return &Counter{key: key}
|
||||
}
|
||||
|
||||
func (c *Counter) set(val int64) {
|
||||
c.val = val
|
||||
if !c.nonempty || val < c.low {
|
||||
c.low = val
|
||||
}
|
||||
if !c.nonempty || c.high < val {
|
||||
c.high = val
|
||||
}
|
||||
c.nonempty = true
|
||||
}
|
||||
|
||||
// Set will immediately change the value of the counter to whatever val is. It
|
||||
// will appropriately update the high and low values, and return the former
|
||||
// value.
|
||||
func (c *Counter) Set(val int64) (former int64) {
|
||||
c.mtx.Lock()
|
||||
former = c.val
|
||||
c.set(val)
|
||||
c.mtx.Unlock()
|
||||
return former
|
||||
}
|
||||
|
||||
// Inc will atomically increment the counter by delta and return the new value.
|
||||
func (c *Counter) Inc(delta int64) (current int64) {
|
||||
c.mtx.Lock()
|
||||
c.set(c.val + delta)
|
||||
current = c.val
|
||||
c.mtx.Unlock()
|
||||
return current
|
||||
}
|
||||
|
||||
// Dec will atomically decrement the counter by delta and return the new value.
|
||||
func (c *Counter) Dec(delta int64) (current int64) {
|
||||
return c.Inc(-delta)
|
||||
}
|
||||
|
||||
// High returns the highest value seen since construction or the last reset
|
||||
func (c *Counter) High() (h int64) {
|
||||
c.mtx.Lock()
|
||||
h = c.high
|
||||
c.mtx.Unlock()
|
||||
return h
|
||||
}
|
||||
|
||||
// Low returns the lowest value seen since construction or the last reset
|
||||
func (c *Counter) Low() (l int64) {
|
||||
c.mtx.Lock()
|
||||
l = c.low
|
||||
c.mtx.Unlock()
|
||||
return l
|
||||
}
|
||||
|
||||
// Current returns the current value
|
||||
func (c *Counter) Current() (cur int64) {
|
||||
c.mtx.Lock()
|
||||
cur = c.val
|
||||
c.mtx.Unlock()
|
||||
return cur
|
||||
}
|
||||
|
||||
// Reset resets all values including high/low counters and returns what they
|
||||
// were.
|
||||
func (c *Counter) Reset() (val, low, high int64) {
|
||||
c.mtx.Lock()
|
||||
val, low, high = c.val, c.low, c.high
|
||||
c.val, c.low, c.high, c.nonempty = 0, 0, 0, false
|
||||
c.mtx.Unlock()
|
||||
return val, low, high
|
||||
}
|
||||
|
||||
// Stats implements the StatSource interface
|
||||
func (c *Counter) Stats(cb func(key SeriesKey, field string, val float64)) {
|
||||
c.mtx.Lock()
|
||||
val, low, high, nonempty := c.val, c.low, c.high, c.nonempty
|
||||
c.mtx.Unlock()
|
||||
if nonempty {
|
||||
cb(c.key, "high", float64(high))
|
||||
cb(c.key, "low", float64(low))
|
||||
} else {
|
||||
cb(c.key, "high", math.NaN())
|
||||
cb(c.key, "low", math.NaN())
|
||||
}
|
||||
cb(c.key, "value", float64(val))
|
||||
}
|
363
vendor/github.com/spacemonkeygo/monkit/v3/ctx.go
generated
vendored
Normal file
363
vendor/github.com/spacemonkeygo/monkit/v3/ctx.go
generated
vendored
Normal file
|
@ -0,0 +1,363 @@
|
|||
// Copyright (C) 2016 Space Monkey, Inc.
|
||||
//
|
||||
// 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 monkit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/spacemonkeygo/monkit/v3/monotime"
|
||||
)
|
||||
|
||||
// Span represents a 'span' of execution. A span is analogous to a stack frame.
|
||||
// Spans are constructed as a side-effect of Tasks.
|
||||
type Span struct {
|
||||
// sync/atomic things
|
||||
mtx spinLock
|
||||
|
||||
// immutable things from construction
|
||||
id int64
|
||||
start time.Time
|
||||
f *Func
|
||||
trace *Trace
|
||||
parent *Span
|
||||
args []interface{}
|
||||
context.Context
|
||||
|
||||
// protected by mtx
|
||||
done bool
|
||||
orphaned bool
|
||||
children spanBag
|
||||
annotations []Annotation
|
||||
}
|
||||
|
||||
// SpanFromCtx loads the current Span from the given context. This assumes
|
||||
// the context already had a Span created through a Task.
|
||||
func SpanFromCtx(ctx context.Context) *Span {
|
||||
if s, ok := ctx.(*Span); ok && s != nil {
|
||||
return s
|
||||
} else if s, ok := ctx.Value(spanKey).(*Span); ok && s != nil {
|
||||
return s
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newSpan(ctx context.Context, f *Func, args []interface{},
|
||||
id int64, trace *Trace) (sctx context.Context, exit func(*error)) {
|
||||
|
||||
var s, parent *Span
|
||||
if s, ok := ctx.(*Span); ok && s != nil {
|
||||
ctx = s.Context
|
||||
if trace == nil {
|
||||
parent = s
|
||||
trace = parent.trace
|
||||
}
|
||||
} else if s, ok := ctx.Value(spanKey).(*Span); ok && s != nil {
|
||||
if trace == nil {
|
||||
parent = s
|
||||
trace = parent.trace
|
||||
}
|
||||
} else if trace == nil {
|
||||
trace = NewTrace(id)
|
||||
f.scope.r.observeTrace(trace)
|
||||
}
|
||||
|
||||
observer := trace.getObserver()
|
||||
|
||||
s = &Span{
|
||||
id: id,
|
||||
start: monotime.Now(),
|
||||
f: f,
|
||||
trace: trace,
|
||||
parent: parent,
|
||||
args: args,
|
||||
Context: ctx,
|
||||
}
|
||||
|
||||
trace.incrementSpans()
|
||||
|
||||
if parent != nil {
|
||||
f.start(parent.f)
|
||||
parent.addChild(s)
|
||||
} else {
|
||||
f.start(nil)
|
||||
f.scope.r.rootSpanStart(s)
|
||||
}
|
||||
|
||||
sctx = s
|
||||
if observer != nil {
|
||||
sctx = observer.Start(sctx, s)
|
||||
}
|
||||
|
||||
return sctx, func(errptr *error) {
|
||||
rec := recover()
|
||||
panicked := rec != nil
|
||||
|
||||
finish := monotime.Now()
|
||||
|
||||
var err error
|
||||
if errptr != nil {
|
||||
err = *errptr
|
||||
}
|
||||
s.f.end(err, panicked, finish.Sub(s.start))
|
||||
|
||||
var children []*Span
|
||||
s.mtx.Lock()
|
||||
s.done = true
|
||||
orphaned := s.orphaned
|
||||
s.children.Iterate(func(child *Span) {
|
||||
children = append(children, child)
|
||||
})
|
||||
s.mtx.Unlock()
|
||||
for _, child := range children {
|
||||
child.orphan()
|
||||
}
|
||||
|
||||
if s.parent != nil {
|
||||
s.parent.removeChild(s)
|
||||
if orphaned {
|
||||
s.f.scope.r.orphanEnd(s)
|
||||
}
|
||||
} else {
|
||||
s.f.scope.r.rootSpanEnd(s)
|
||||
}
|
||||
|
||||
trace.decrementSpans()
|
||||
|
||||
// Re-fetch the observer, in case the value has changed since newSpan
|
||||
// was called
|
||||
if observer := trace.getObserver(); observer != nil {
|
||||
observer.Finish(sctx, s, err, panicked, finish)
|
||||
}
|
||||
|
||||
if panicked {
|
||||
panic(rec)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var taskSecret context.Context = &taskSecretT{}
|
||||
|
||||
// Tasks are created (sometimes implicitly) from Funcs. A Task should be called
|
||||
// at the start of a monitored task, and its return value should be called
|
||||
// at the stop of said task.
|
||||
type Task func(ctx *context.Context, args ...interface{}) func(*error)
|
||||
|
||||
// Task returns a new Task for use, creating an associated Func if necessary.
|
||||
// It also adds a new Span to the given ctx during execution. Expected usage
|
||||
// like:
|
||||
//
|
||||
// var mon = monkit.Package()
|
||||
//
|
||||
// func MyFunc(ctx context.Context, arg1, arg2 string) (err error) {
|
||||
// defer mon.Task()(&ctx, arg1, arg2)(&err)
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
// or
|
||||
//
|
||||
// var (
|
||||
// mon = monkit.Package()
|
||||
// funcTask = mon.Task()
|
||||
// )
|
||||
//
|
||||
// func MyFunc(ctx context.Context, arg1, arg2 string) (err error) {
|
||||
// defer funcTask(&ctx, arg1, arg2)(&err)
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
// Task allows you to include SeriesTags. WARNING: Each unique tag key/value
|
||||
// combination creates a unique Func and a unique series. SeriesTags should
|
||||
// only be used for low-cardinality values that you intentionally wish to
|
||||
// result in a unique series. Example:
|
||||
//
|
||||
// func MyFunc(ctx context.Context, arg1, arg2 string) (err error) {
|
||||
// defer mon.Task(monkit.NewSeriesTag("key1", "val1"))(&ctx)(&err)
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
// Task uses runtime.Caller to determine the associated Func name. See
|
||||
// TaskNamed if you want to supply your own name. See Func.Task if you already
|
||||
// have a Func.
|
||||
//
|
||||
// If you want to control Trace creation, see Func.ResetTrace and
|
||||
// Func.RemoteTrace
|
||||
func (s *Scope) Task(tags ...SeriesTag) Task {
|
||||
var initOnce sync.Once
|
||||
var f *Func
|
||||
init := func() {
|
||||
f = s.FuncNamed(callerFunc(3), tags...)
|
||||
}
|
||||
return Task(func(ctx *context.Context,
|
||||
args ...interface{}) func(*error) {
|
||||
ctx = cleanCtx(ctx)
|
||||
if ctx == &taskSecret && taskArgs(f, args) {
|
||||
return nil
|
||||
}
|
||||
initOnce.Do(init)
|
||||
s, exit := newSpan(*ctx, f, args, NewId(), nil)
|
||||
if ctx != &unparented {
|
||||
*ctx = s
|
||||
}
|
||||
return exit
|
||||
})
|
||||
}
|
||||
|
||||
// Task returns a new Task for use on this Func. It also adds a new Span to
|
||||
// the given ctx during execution.
|
||||
//
|
||||
// var mon = monkit.Package()
|
||||
//
|
||||
// func MyFunc(ctx context.Context, arg1, arg2 string) (err error) {
|
||||
// f := mon.Func()
|
||||
// defer f.Task(&ctx, arg1, arg2)(&err)
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
// It's more expected for you to use mon.Task directly. See RemoteTrace or
|
||||
// ResetTrace if you want greater control over creating new traces.
|
||||
func (f *Func) Task(ctx *context.Context, args ...interface{}) func(*error) {
|
||||
ctx = cleanCtx(ctx)
|
||||
if ctx == &taskSecret && taskArgs(f, args) {
|
||||
return nil
|
||||
}
|
||||
s, exit := newSpan(*ctx, f, args, NewId(), nil)
|
||||
if ctx != &unparented {
|
||||
*ctx = s
|
||||
}
|
||||
return exit
|
||||
}
|
||||
|
||||
// RemoteTrace is like Func.Task, except you can specify the trace and span id.
|
||||
// Needed for things like the Zipkin plugin.
|
||||
func (f *Func) RemoteTrace(ctx *context.Context, spanId int64, trace *Trace,
|
||||
args ...interface{}) func(*error) {
|
||||
ctx = cleanCtx(ctx)
|
||||
if trace != nil {
|
||||
f.scope.r.observeTrace(trace)
|
||||
}
|
||||
s, exit := newSpan(*ctx, f, args, spanId, trace)
|
||||
if ctx != &unparented {
|
||||
*ctx = s
|
||||
}
|
||||
return exit
|
||||
}
|
||||
|
||||
// ResetTrace is like Func.Task, except it always creates a new Trace.
|
||||
func (f *Func) ResetTrace(ctx *context.Context,
|
||||
args ...interface{}) func(*error) {
|
||||
ctx = cleanCtx(ctx)
|
||||
if ctx == &taskSecret && taskArgs(f, args) {
|
||||
return nil
|
||||
}
|
||||
trace := NewTrace(NewId())
|
||||
f.scope.r.observeTrace(trace)
|
||||
s, exit := newSpan(*ctx, f, args, trace.Id(), trace)
|
||||
if ctx != &unparented {
|
||||
*ctx = s
|
||||
}
|
||||
return exit
|
||||
}
|
||||
|
||||
var unparented = context.Background()
|
||||
|
||||
func cleanCtx(ctx *context.Context) *context.Context {
|
||||
if ctx == nil {
|
||||
return &unparented
|
||||
}
|
||||
if *ctx == nil {
|
||||
*ctx = context.Background()
|
||||
// possible upshot of what we just did:
|
||||
//
|
||||
// func MyFunc(ctx context.Context) {
|
||||
// // ctx == nil here
|
||||
// defer mon.Task()(&ctx)(nil)
|
||||
// // ctx != nil here
|
||||
// }
|
||||
//
|
||||
// func main() { MyFunc(nil) }
|
||||
//
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
// SpanCtxObserver is the interface plugins must implement if they want to observe
|
||||
// all spans on a given trace as they happen, or add to contexts as they
|
||||
// pass through mon.Task()(&ctx)(&err) calls.
|
||||
type SpanCtxObserver interface {
|
||||
// Start is called when a Span starts. Start should return the context
|
||||
// this span should use going forward. ctx is the context it is currently
|
||||
// using.
|
||||
Start(ctx context.Context, s *Span) context.Context
|
||||
|
||||
// Finish is called when a Span finishes, along with an error if any, whether
|
||||
// or not it panicked, and what time it finished.
|
||||
Finish(ctx context.Context, s *Span, err error, panicked bool, finish time.Time)
|
||||
}
|
||||
|
||||
type spanObserverToSpanCtxObserver struct {
|
||||
observer SpanObserver
|
||||
}
|
||||
|
||||
func (so spanObserverToSpanCtxObserver) Start(ctx context.Context, s *Span) context.Context {
|
||||
so.observer.Start(s)
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (so spanObserverToSpanCtxObserver) Finish(ctx context.Context, s *Span, err error, panicked bool, finish time.Time) {
|
||||
so.observer.Finish(s, err, panicked, finish)
|
||||
}
|
||||
|
||||
type spanObserverTuple struct {
|
||||
// cdr is atomic
|
||||
cdr *spanObserverTuple
|
||||
// car never changes
|
||||
car SpanCtxObserver
|
||||
}
|
||||
|
||||
func (l *spanObserverTuple) Start(ctx context.Context, s *Span) context.Context {
|
||||
ctx = l.car.Start(ctx, s)
|
||||
cdr := loadSpanObserverTuple(&l.cdr)
|
||||
if cdr != nil {
|
||||
ctx = cdr.Start(ctx, s)
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (l *spanObserverTuple) Finish(ctx context.Context, s *Span, err error, panicked bool,
|
||||
finish time.Time) {
|
||||
l.car.Finish(ctx, s, err, panicked, finish)
|
||||
cdr := loadSpanObserverTuple(&l.cdr)
|
||||
if cdr != nil {
|
||||
cdr.Finish(ctx, s, err, panicked, finish)
|
||||
}
|
||||
}
|
||||
|
||||
type resetContext struct {
|
||||
context.Context
|
||||
}
|
||||
|
||||
func (r resetContext) Value(key interface{}) interface{} {
|
||||
if key == spanKey {
|
||||
return nil
|
||||
}
|
||||
return r.Context.Value(key)
|
||||
}
|
||||
|
||||
// ResetContextSpan returns a new context with Span information removed.
|
||||
func ResetContextSpan(ctx context.Context) context.Context {
|
||||
return resetContext{Context: ctx}
|
||||
}
|
61
vendor/github.com/spacemonkeygo/monkit/v3/dist.go
generated
vendored
Normal file
61
vendor/github.com/spacemonkeygo/monkit/v3/dist.go
generated
vendored
Normal file
|
@ -0,0 +1,61 @@
|
|||
// Copyright (C) 2015 Space Monkey, Inc.
|
||||
//
|
||||
// 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 monkit
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
ReservoirSize = 64
|
||||
)
|
||||
|
||||
var (
|
||||
// If Window is > 0, the probability of replacing a datapoint will never
|
||||
// fall below ReservoirSize/Window instead of continuing to fall over time.
|
||||
// Window should be a multiple of ReservoirSize.
|
||||
Window int64 = 1024
|
||||
)
|
||||
|
||||
// ObservedQuantiles is the set of quantiles the internal distribution
|
||||
// measurement logic will try to optimize for, if applicable.
|
||||
var ObservedQuantiles = []float64{0, .1, .25, .5, .75, .9, .95, 1}
|
||||
|
||||
type float32Slice []float32
|
||||
|
||||
func (p float32Slice) Len() int { return len(p) }
|
||||
func (p float32Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||
func (p float32Slice) Less(i, j int) bool {
|
||||
// N.B.: usually, float comparisons should check if either value is NaN, but
|
||||
// in this package's usage, they never are here.
|
||||
return p[i] < p[j]
|
||||
}
|
||||
|
||||
//go:generate sh -c "m4 -D_IMPORT_='\"time\"' -D_NAME_=Duration -D_LOWER_NAME_=duration -D_TYPE_=time.Duration distgen.go.m4 > durdist.go"
|
||||
//go:generate sh -c "m4 -D_IMPORT_= -D_NAME_=Float -D_LOWER_NAME_=float -D_TYPE_=float64 distgen.go.m4 > floatdist.go"
|
||||
//go:generate sh -c "m4 -D_IMPORT_= -D_NAME_=Int -D_LOWER_NAME_=int -D_TYPE_=int64 distgen.go.m4 > intdist.go"
|
||||
//go:generate gofmt -w -s durdist.go floatdist.go intdist.go
|
||||
|
||||
func (d *DurationDist) toFloat64(v time.Duration) float64 {
|
||||
return v.Seconds()
|
||||
}
|
||||
|
||||
func (d *IntDist) toFloat64(v int64) float64 {
|
||||
return float64(v)
|
||||
}
|
||||
|
||||
func (d *FloatDist) toFloat64(v float64) float64 {
|
||||
return v
|
||||
}
|
185
vendor/github.com/spacemonkeygo/monkit/v3/distgen.go.m4
generated
vendored
Normal file
185
vendor/github.com/spacemonkeygo/monkit/v3/distgen.go.m4
generated
vendored
Normal file
|
@ -0,0 +1,185 @@
|
|||
// Copyright (C) 2016 Space Monkey, Inc.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// WARNING: THE NON-M4 VERSIONS OF THIS FILE ARE GENERATED BY GO GENERATE!
|
||||
// ONLY MAKE CHANGES TO THE M4 FILE
|
||||
//
|
||||
|
||||
package monkit
|
||||
|
||||
import (
|
||||
"sort"
|
||||
_IMPORT_
|
||||
)
|
||||
|
||||
// _NAME_`Dist' keeps statistics about values such as
|
||||
// low/high/recent/average/quantiles. Not threadsafe. Construct with
|
||||
// `New'_NAME_`Dist'(). Fields are expected to be read from but not written to.
|
||||
type _NAME_`Dist' struct {
|
||||
// Low and High are the lowest and highest values observed since
|
||||
// construction or the last reset.
|
||||
Low, High _TYPE_
|
||||
|
||||
// Recent is the last observed value.
|
||||
Recent _TYPE_
|
||||
|
||||
// Count is the number of observed values since construction or the last
|
||||
// reset.
|
||||
Count int64
|
||||
|
||||
// Sum is the sum of all the observed values since construction or the last
|
||||
// reset.
|
||||
Sum _TYPE_
|
||||
|
||||
key SeriesKey
|
||||
reservoir [ReservoirSize]float32
|
||||
rng xorshift128
|
||||
sorted bool
|
||||
}
|
||||
|
||||
func `init'_NAME_`Dist'(v *_NAME_`Dist', key SeriesKey) {
|
||||
v.key = key
|
||||
v.rng = newXORShift128()
|
||||
}
|
||||
|
||||
// `New'_NAME_`Dist' creates a distribution of _TYPE_`s'.
|
||||
func `New'_NAME_`Dist'(key SeriesKey) (d *_NAME_`Dist') {
|
||||
d = &_NAME_`Dist'{}
|
||||
`init'_NAME_`Dist'(d, key)
|
||||
return d
|
||||
}
|
||||
|
||||
// Insert adds a value to the distribution, updating appropriate values.
|
||||
func (d *_NAME_`Dist') Insert(val _TYPE_) {
|
||||
if d.Count != 0 {
|
||||
if val < d.Low {
|
||||
d.Low = val
|
||||
}
|
||||
if val > d.High {
|
||||
d.High = val
|
||||
}
|
||||
} else {
|
||||
d.Low = val
|
||||
d.High = val
|
||||
}
|
||||
d.Recent = val
|
||||
d.Sum += val
|
||||
|
||||
index := d.Count
|
||||
d.Count += 1
|
||||
|
||||
if index < ReservoirSize {
|
||||
d.reservoir[index] = float32(val)
|
||||
d.sorted = false
|
||||
} else {
|
||||
window := d.Count
|
||||
// careful, the capitalization of Window is important
|
||||
if Window > 0 && window > Window {
|
||||
window = Window
|
||||
}
|
||||
// fast, but kind of biased. probably okay
|
||||
j := d.rng.Uint64() % uint64(window)
|
||||
if j < ReservoirSize {
|
||||
d.reservoir[int(j)] = float32(val)
|
||||
d.sorted = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FullAverage calculates and returns the average of all inserted values.
|
||||
func (d *_NAME_`Dist') FullAverage() _TYPE_ {
|
||||
if d.Count > 0 {
|
||||
return d.Sum / _TYPE_`(d.Count)'
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// ReservoirAverage calculates the average of the current reservoir.
|
||||
func (d *_NAME_`Dist') ReservoirAverage() _TYPE_ {
|
||||
amount := ReservoirSize
|
||||
if d.Count < int64(amount) {
|
||||
amount = int(d.Count)
|
||||
}
|
||||
if amount <= 0 {
|
||||
return 0
|
||||
}
|
||||
var sum float32
|
||||
for i := 0; i < amount; i++ {
|
||||
sum += d.reservoir[i]
|
||||
}
|
||||
return _TYPE_`(sum / float32(amount))'
|
||||
}
|
||||
|
||||
// Query will return the approximate value at the given quantile from the
|
||||
// reservoir, where 0 <= quantile <= 1.
|
||||
func (d *_NAME_`Dist') Query(quantile float64) _TYPE_ {
|
||||
rlen := int(ReservoirSize)
|
||||
if int64(rlen) > d.Count {
|
||||
rlen = int(d.Count)
|
||||
}
|
||||
|
||||
if rlen < 2 {
|
||||
return _TYPE_`(d.reservoir[0])'
|
||||
}
|
||||
|
||||
reservoir := d.reservoir[:rlen]
|
||||
if !d.sorted {
|
||||
sort.Sort(float32Slice(reservoir))
|
||||
d.sorted = true
|
||||
}
|
||||
|
||||
if quantile <= 0 {
|
||||
return _TYPE_`(reservoir[0])'
|
||||
}
|
||||
if quantile >= 1 {
|
||||
return _TYPE_`(reservoir[rlen-1])'
|
||||
}
|
||||
|
||||
idx_float := quantile * float64(rlen-1)
|
||||
idx := int(idx_float)
|
||||
|
||||
diff := idx_float - float64(idx)
|
||||
prior := float64(reservoir[idx])
|
||||
return _TYPE_`(prior + diff*(float64(reservoir[idx+1])-prior))'
|
||||
}
|
||||
|
||||
// Copy returns a full copy of the entire distribution.
|
||||
func (d *_NAME_`Dist') Copy() *_NAME_`Dist' {
|
||||
cp := *d
|
||||
cp.rng = newXORShift128()
|
||||
return &cp
|
||||
}
|
||||
|
||||
func (d *_NAME_`Dist') Reset() {
|
||||
d.Low, d.High, d.Recent, d.Count, d.Sum = 0, 0, 0, 0, 0
|
||||
// resetting count will reset the quantile reservoir
|
||||
}
|
||||
|
||||
func (d *_NAME_`Dist') Stats(cb func(key SeriesKey, field string, val float64)) {
|
||||
count := d.Count
|
||||
cb(d.key, "count", float64(count))
|
||||
if count > 0 {
|
||||
cb(d.key, "sum", d.toFloat64(d.Sum))
|
||||
cb(d.key, "min", d.toFloat64(d.Low))
|
||||
cb(d.key, "avg", d.toFloat64(d.FullAverage()))
|
||||
cb(d.key, "max", d.toFloat64(d.High))
|
||||
cb(d.key, "rmin", d.toFloat64(d.Query(0)))
|
||||
cb(d.key, "ravg", d.toFloat64(d.ReservoirAverage()))
|
||||
cb(d.key, "r10", d.toFloat64(d.Query(.1)))
|
||||
cb(d.key, "r50", d.toFloat64(d.Query(.5)))
|
||||
cb(d.key, "r90", d.toFloat64(d.Query(.9)))
|
||||
cb(d.key, "rmax", d.toFloat64(d.Query(1)))
|
||||
cb(d.key, "recent", d.toFloat64(d.Recent))
|
||||
}
|
||||
}
|
579
vendor/github.com/spacemonkeygo/monkit/v3/doc.go
generated
vendored
Normal file
579
vendor/github.com/spacemonkeygo/monkit/v3/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,579 @@
|
|||
// Copyright (C) 2015 Space Monkey, Inc.
|
||||
//
|
||||
// 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 monkit is a flexible code instrumenting and data collection library.
|
||||
|
||||
I'm going to try and sell you as fast as I can on this library.
|
||||
|
||||
Example usage
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/spacemonkeygo/monkit/v3"
|
||||
"github.com/spacemonkeygo/monkit/v3/environment"
|
||||
"github.com/spacemonkeygo/monkit/v3/present"
|
||||
)
|
||||
|
||||
var (
|
||||
mon = monkit.Package()
|
||||
)
|
||||
|
||||
func ComputeThing(ctx context.Context, arg1, arg2 int) (res int, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
timer := mon.Timer("subcomputation").Start()
|
||||
res = arg1 + arg2
|
||||
timer.Stop()
|
||||
|
||||
if res == 3 {
|
||||
mon.Event("hit 3")
|
||||
}
|
||||
|
||||
mon.BoolVal("was-4").Observe(res == 4)
|
||||
mon.IntVal("res").Observe(int64(res))
|
||||
mon.Counter("calls").Inc(1)
|
||||
mon.Gauge("arg1", func() float64 { return float64(arg1) })
|
||||
mon.Meter("arg2").Mark(arg2)
|
||||
|
||||
return arg1 + arg2, nil
|
||||
}
|
||||
|
||||
func DoStuff(ctx context.Context) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
result, err := ComputeThing(ctx, 1, 2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(result)
|
||||
return
|
||||
}
|
||||
|
||||
func main() {
|
||||
environment.Register(monkit.Default)
|
||||
go http.ListenAndServe("localhost:9000", present.HTTP(monkit.Default))
|
||||
log.Println(DoStuff(context.Background()))
|
||||
}
|
||||
|
||||
Metrics
|
||||
|
||||
We've got tools that capture distribution information (including quantiles)
|
||||
about int64, float64, and bool types. We have tools that capture data about
|
||||
events (we've got meters for deltas, rates, etc). We have rich tools for
|
||||
capturing information about tasks and functions, and literally anything that
|
||||
can generate a name and a number.
|
||||
|
||||
Almost just as importantly, the amount of boilerplate and code you have to
|
||||
write to get these features is very minimal. Data that's hard to measure
|
||||
probably won't get measured.
|
||||
|
||||
This data can be collected and sent to Graphite (http://graphite.wikidot.com/)
|
||||
or any other time-series database.
|
||||
|
||||
Here's a selection of live stats from one of our storage nodes:
|
||||
|
||||
env.os.fds 120.000000
|
||||
env.os.proc.stat.Minflt 81155.000000
|
||||
env.os.proc.stat.Cminflt 11789.000000
|
||||
env.os.proc.stat.Majflt 10.000000
|
||||
env.os.proc.stat.Cmajflt 6.000000
|
||||
...
|
||||
|
||||
env.process.control 1.000000
|
||||
env.process.crc 3819014369.000000
|
||||
env.process.uptime 163225.292925
|
||||
env.runtime.goroutines 52.000000
|
||||
env.runtime.memory.Alloc 2414080.000000
|
||||
...
|
||||
|
||||
env.rusage.Maxrss 26372.000000
|
||||
...
|
||||
|
||||
sm/flud/csl/client.(*CSLClient).Verify.current 0.000000
|
||||
sm/flud/csl/client.(*CSLClient).Verify.success 788.000000
|
||||
sm/flud/csl/client.(*CSLClient).Verify.error volume missing 91.000000
|
||||
sm/flud/csl/client.(*CSLClient).Verify.error dial error 1.000000
|
||||
sm/flud/csl/client.(*CSLClient).Verify.panics 0.000000
|
||||
sm/flud/csl/client.(*CSLClient).Verify.success times min 0.102214
|
||||
sm/flud/csl/client.(*CSLClient).Verify.success times avg 1.899133
|
||||
sm/flud/csl/client.(*CSLClient).Verify.success times max 8.601230
|
||||
sm/flud/csl/client.(*CSLClient).Verify.success times recent 2.673128
|
||||
sm/flud/csl/client.(*CSLClient).Verify.failure times min 0.682881
|
||||
sm/flud/csl/client.(*CSLClient).Verify.failure times avg 3.936571
|
||||
sm/flud/csl/client.(*CSLClient).Verify.failure times max 6.102318
|
||||
sm/flud/csl/client.(*CSLClient).Verify.failure times recent 2.208020
|
||||
sm/flud/csl/server.store.avg 710800.000000
|
||||
sm/flud/csl/server.store.count 271.000000
|
||||
sm/flud/csl/server.store.max 3354194.000000
|
||||
sm/flud/csl/server.store.min 467.000000
|
||||
sm/flud/csl/server.store.recent 1661376.000000
|
||||
sm/flud/csl/server.store.sum 192626890.000000
|
||||
...
|
||||
|
||||
Call graphs
|
||||
|
||||
This library generates call graphs of your live process for you.
|
||||
|
||||
These call graphs aren't created through sampling. They're full pictures of all
|
||||
of the interesting functions you've annotated, along with quantile information
|
||||
about their successes, failures, how often they panic, return an error (if so
|
||||
instrumented), how many are currently running, etc.
|
||||
|
||||
The data can be returned in dot format, in json, in text, and can be about
|
||||
just the functions that are currently executing, or all the functions the
|
||||
monitoring system has ever seen.
|
||||
|
||||
Here's another example of one of our production nodes:
|
||||
|
||||
https://raw.githubusercontent.com/spacemonkeygo/monkit/master/images/callgraph2.png
|
||||
|
||||
Trace graphs
|
||||
|
||||
This library generates trace graphs of your live process for you directly,
|
||||
without requiring standing up some tracing system such as Zipkin (though you
|
||||
can do that too).
|
||||
|
||||
Inspired by Google's Dapper (http://research.google.com/pubs/pub36356.html)
|
||||
and Twitter's Zipkin (http://zipkin.io), we have process-internal trace
|
||||
graphs, triggerable by a number of different methods.
|
||||
|
||||
You get this trace information for free whenever you use
|
||||
Go contexts (https://blog.golang.org/context) and function monitoring. The
|
||||
output formats are svg and json.
|
||||
|
||||
Additionally, the library supports trace observation plugins, and we've written
|
||||
a plugin that sends this data to Zipkin (http://github.com/spacemonkeygo/monkit-zipkin).
|
||||
|
||||
https://raw.githubusercontent.com/spacemonkeygo/monkit/master/images/trace.png
|
||||
|
||||
History
|
||||
|
||||
Before our crazy Go rewrite of everything (https://www.spacemonkey.com/blog/posts/go-space-monkey)
|
||||
(and before we had even seen Google's Dapper paper), we were a Python shop, and
|
||||
all of our "interesting" functions were decorated with a helper that collected
|
||||
timing information and sent it to Graphite.
|
||||
|
||||
When we transliterated to Go, we wanted to preserve that functionality, so the
|
||||
first version of our monitoring package was born.
|
||||
|
||||
Over time it started to get janky, especially as we found Zipkin and started
|
||||
adding tracing functionality to it. We rewrote all of our Go code to use Google
|
||||
contexts, and then realized we could get call graph information. We decided a
|
||||
refactor and then an all-out rethinking of our monitoring package was best,
|
||||
and so now we have this library.
|
||||
|
||||
Aside about contexts
|
||||
|
||||
Sometimes you really want callstack contextual information without having to
|
||||
pass arguments through everything on the call stack. In other languages, many
|
||||
people implement this with thread-local storage.
|
||||
|
||||
Example: let's say you have written a big system that responds to user
|
||||
requests. All of your libraries log using your log library. During initial
|
||||
development everything is easy to debug, since there's low user load, but now
|
||||
you've scaled and there's OVER TEN USERS and it's kind of hard to tell what log
|
||||
lines were caused by what. Wouldn't it be nice to add request ids to all of the
|
||||
log lines kicked off by that request? Then you could grep for all log lines
|
||||
caused by a specific request id. Geez, it would suck to have to pass all
|
||||
contextual debugging information through all of your callsites.
|
||||
|
||||
Google solved this problem by always passing a context.Context interface
|
||||
through from call to call. A Context is basically just a mapping of arbitrary
|
||||
keys to arbitrary values that users can add new values for. This way if you
|
||||
decide to add a request context, you can add it to your Context and then all
|
||||
callsites that decend from that place will have the new data in their contexts.
|
||||
|
||||
It is admittedly very verbose to add contexts to every function call.
|
||||
Painfully so. I hope to write more about it in the future, but Google also
|
||||
wrote up their thoughts about it (https://blog.golang.org/context), which you
|
||||
can go read. For now, just swallow your disgust and let's keep moving.
|
||||
|
||||
Motivating program
|
||||
|
||||
Let's make a super simple Varnish (https://www.varnish-cache.org/) clone.
|
||||
Open up gedit! (Okay just kidding, open whatever text editor you want.)
|
||||
|
||||
For this motivating program, we won't even add the caching, though there's
|
||||
comments for where to add it if you'd like. For now, let's just make a
|
||||
barebones system that will proxy HTTP requests. We'll call it VLite, but
|
||||
maybe we should call it VReallyLite.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type VLite struct {
|
||||
target *url.URL
|
||||
proxy *httputil.ReverseProxy
|
||||
}
|
||||
|
||||
func NewVLite(target *url.URL) *VLite {
|
||||
return &VLite{
|
||||
target: target,
|
||||
proxy: httputil.NewSingleHostReverseProxy(target),
|
||||
}
|
||||
}
|
||||
|
||||
func (v *VLite) Proxy(w http.ResponseWriter, r *http.Request) {
|
||||
r.Host = v.target.Host // let the proxied server get the right vhost
|
||||
v.proxy.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func (v *VLite) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// here's where you'd put caching logic
|
||||
v.Proxy(w, r)
|
||||
}
|
||||
|
||||
func main() {
|
||||
target := flag.String(
|
||||
"proxy",
|
||||
"http://hasthelargehadroncolliderdestroyedtheworldyet.com/",
|
||||
"server to cache")
|
||||
flag.Parse()
|
||||
targetURL, err := url.Parse(*target)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
panic(http.ListenAndServe(":8080", NewVLite(targetURL)))
|
||||
}
|
||||
|
||||
Run and build this and open localhost:8080 in your browser. If you use the
|
||||
default proxy target, it should inform you that the world hasn't been
|
||||
destroyed yet.
|
||||
|
||||
Adding basic instrumentation
|
||||
|
||||
The first thing you'll want to do is add the small amount of boilerplate to
|
||||
make the instrumentation we're going to add to your process observable later.
|
||||
|
||||
Import the basic monkit packages:
|
||||
|
||||
"github.com/spacemonkeygo/monkit/v3"
|
||||
"github.com/spacemonkeygo/monkit/v3/environment"
|
||||
"github.com/spacemonkeygo/monkit/v3/present"
|
||||
|
||||
and then register environmental statistics and kick off a goroutine in your
|
||||
main method to serve debug requests:
|
||||
|
||||
environment.Register(monkit.Default)
|
||||
go http.ListenAndServe("localhost:9000", present.HTTP(monkit.Default))
|
||||
|
||||
Rebuild, and then check out localhost:9000/stats (or
|
||||
localhost:9000/stats/json, if you prefer) in your browser!
|
||||
|
||||
Request contexts
|
||||
|
||||
Remember what I said about Google's contexts (https://blog.golang.org/context)?
|
||||
It might seem a bit overkill for such a small project, but it's time to add
|
||||
them.
|
||||
|
||||
To help out here, I've created a library that constructs contexts for you
|
||||
for incoming HTTP requests. Nothing that's about to happen requires my
|
||||
webhelp library (https://godoc.org/github.com/jtolds/webhelp), but here is the
|
||||
code now refactored to receive and pass contexts through our two per-request
|
||||
calls.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
|
||||
"github.com/jtolds/webhelp"
|
||||
"github.com/spacemonkeygo/monkit/v3"
|
||||
"github.com/spacemonkeygo/monkit/v3/environment"
|
||||
"github.com/spacemonkeygo/monkit/v3/present"
|
||||
)
|
||||
|
||||
type VLite struct {
|
||||
target *url.URL
|
||||
proxy *httputil.ReverseProxy
|
||||
}
|
||||
|
||||
func NewVLite(target *url.URL) *VLite {
|
||||
return &VLite{
|
||||
target: target,
|
||||
proxy: httputil.NewSingleHostReverseProxy(target),
|
||||
}
|
||||
}
|
||||
|
||||
func (v *VLite) Proxy(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
r.Host = v.target.Host // let the proxied server get the right vhost
|
||||
v.proxy.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func (v *VLite) HandleHTTP(ctx context.Context, w webhelp.ResponseWriter, r *http.Request) error {
|
||||
// here's where you'd put caching logic
|
||||
v.Proxy(ctx, w, r)
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
target := flag.String(
|
||||
"proxy",
|
||||
"http://hasthelargehadroncolliderdestroyedtheworldyet.com/",
|
||||
"server to cache")
|
||||
flag.Parse()
|
||||
targetURL, err := url.Parse(*target)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
environment.Register(monkit.Default)
|
||||
go http.ListenAndServe("localhost:9000", present.HTTP(monkit.Default))
|
||||
panic(webhelp.ListenAndServe(":8080", NewVLite(targetURL)))
|
||||
}
|
||||
|
||||
You can create a new context for a request however you want. One reason to use
|
||||
something like webhelp is that the cancelation feature of Contexts is hooked
|
||||
up to the HTTP request getting canceled.
|
||||
|
||||
Monitor some requests
|
||||
|
||||
Let's start to get statistics about how many requests we receive! First, this
|
||||
package (main) will need to get a monitoring Scope. Add this global definition
|
||||
right after all your imports, much like you'd create a logger with many logging
|
||||
libraries:
|
||||
|
||||
var mon = monkit.Package()
|
||||
|
||||
Now, make the error return value of HandleHTTP named (so, (err error)), and add
|
||||
this defer line as the very first instruction of HandleHTTP:
|
||||
|
||||
func (v *VLite) HandleHTTP(ctx context.Context, w webhelp.ResponseWriter, r *http.Request) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
Let's also add the same line (albeit modified for the lack of error) to
|
||||
Proxy, replacing &err with nil:
|
||||
|
||||
func (v *VLite) Proxy(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
defer mon.Task()(&ctx)(nil)
|
||||
|
||||
You should now have something like:
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
|
||||
"github.com/jtolds/webhelp"
|
||||
"github.com/spacemonkeygo/monkit/v3"
|
||||
"github.com/spacemonkeygo/monkit/v3/environment"
|
||||
"github.com/spacemonkeygo/monkit/v3/present"
|
||||
)
|
||||
|
||||
var mon = monkit.Package()
|
||||
|
||||
type VLite struct {
|
||||
target *url.URL
|
||||
proxy *httputil.ReverseProxy
|
||||
}
|
||||
|
||||
func NewVLite(target *url.URL) *VLite {
|
||||
return &VLite{
|
||||
target: target,
|
||||
proxy: httputil.NewSingleHostReverseProxy(target),
|
||||
}
|
||||
}
|
||||
|
||||
func (v *VLite) Proxy(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
defer mon.Task()(&ctx)(nil)
|
||||
r.Host = v.target.Host // let the proxied server get the right vhost
|
||||
v.proxy.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func (v *VLite) HandleHTTP(ctx context.Context, w webhelp.ResponseWriter, r *http.Request) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
// here's where you'd put caching logic
|
||||
v.Proxy(ctx, w, r)
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
target := flag.String(
|
||||
"proxy",
|
||||
"http://hasthelargehadroncolliderdestroyedtheworldyet.com/",
|
||||
"server to cache")
|
||||
flag.Parse()
|
||||
targetURL, err := url.Parse(*target)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
environment.Register(monkit.Default)
|
||||
go http.ListenAndServe("localhost:9000", present.HTTP(monkit.Default))
|
||||
panic(webhelp.ListenAndServe(":8080", NewVLite(targetURL)))
|
||||
}
|
||||
|
||||
We'll unpack what's going on here, but for now:
|
||||
|
||||
* Rebuild and restart!
|
||||
* Trigger a full refresh at localhost:8080 to make sure your new HTTP
|
||||
handler runs
|
||||
* Visit localhost:9000/stats and then localhost:9000/funcs
|
||||
|
||||
For this new funcs dataset, if you want a graph, you can download a dot
|
||||
graph at localhost:9000/funcs/dot and json information from
|
||||
localhost:9000/funcs/json.
|
||||
|
||||
You should see something like:
|
||||
|
||||
[3693964236144930897] main.(*VLite).HandleHTTP
|
||||
parents: entry
|
||||
current: 0, highwater: 1, success: 2, errors: 0, panics: 0
|
||||
success times:
|
||||
0.00: 63.930436ms
|
||||
0.10: 70.482159ms
|
||||
0.25: 80.309745ms
|
||||
0.50: 96.689054ms
|
||||
0.75: 113.068363ms
|
||||
0.90: 122.895948ms
|
||||
0.95: 126.17181ms
|
||||
1.00: 129.447675ms
|
||||
avg: 96.689055ms
|
||||
failure times:
|
||||
0.00: 0
|
||||
0.10: 0
|
||||
0.25: 0
|
||||
0.50: 0
|
||||
0.75: 0
|
||||
0.90: 0
|
||||
0.95: 0
|
||||
1.00: 0
|
||||
avg: 0
|
||||
|
||||
with a similar report for the Proxy method, or a graph like:
|
||||
|
||||
https://raw.githubusercontent.com/spacemonkeygo/monkit/master/images/handlehttp.png
|
||||
|
||||
This data reports the overall callgraph of execution for known traces, along
|
||||
with how many of each function are currently running, the most running
|
||||
concurrently (the highwater), how many were successful along with quantile
|
||||
timing information, how many errors there were (with quantile timing
|
||||
information if applicable), and how many panics there were. Since the Proxy
|
||||
method isn't capturing a returned err value, and since HandleHTTP always
|
||||
returns nil, this example won't ever have failures.
|
||||
|
||||
If you're wondering about the success count being higher than you expected,
|
||||
keep in mind your browser probably requested a favicon.ico.
|
||||
|
||||
Cool, eh?
|
||||
|
||||
How it works
|
||||
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
is an interesting line of code - there's three function calls. If you look at
|
||||
the Go spec, all of the function calls will run at the time the function starts
|
||||
except for the very last one.
|
||||
|
||||
The first function call, mon.Task(), creates or looks up a wrapper around a
|
||||
Func. You could get this yourself by requesting mon.Func() inside of the
|
||||
appropriate function or mon.FuncNamed(). Both mon.Task() and mon.Func()
|
||||
are inspecting runtime.Caller to determine the name of the function. Because
|
||||
this is a heavy operation, you can actually store the result of mon.Task() and
|
||||
reuse it somehow else if you prefer, so instead of
|
||||
|
||||
func MyFunc(ctx context.Context) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
}
|
||||
|
||||
you could instead use
|
||||
|
||||
var myFuncMon = mon.Task()
|
||||
|
||||
func MyFunc(ctx context.Context) (err error) {
|
||||
defer myFuncMon(&ctx)(&err)
|
||||
}
|
||||
|
||||
which is more performant every time after the first time. runtime.Caller only
|
||||
gets called once.
|
||||
|
||||
Careful! Don't use the same myFuncMon in different functions unless you want to
|
||||
screw up your statistics!
|
||||
|
||||
The second function call starts all the various stop watches and bookkeeping to
|
||||
keep track of the function. It also mutates the context pointer it's given to
|
||||
extend the context with information about what current span (in Zipkin
|
||||
parlance) is active. Notably, you *can* pass nil for the context if you really
|
||||
don't want a context. You just lose callgraph information.
|
||||
|
||||
The last function call stops all the stop watches ad makes a note of any
|
||||
observed errors or panics (it repanics after observing them).
|
||||
|
||||
Tracing
|
||||
|
||||
Turns out, we don't even need to change our program anymore to get rich tracing
|
||||
information!
|
||||
|
||||
Open your browser and go to localhost:9000/trace/svg?regex=HandleHTTP. It
|
||||
won't load, and in fact, it's waiting for you to open another tab and refresh
|
||||
localhost:8080 again. Once you retrigger the actual application behavior,
|
||||
the trace regex will capture a trace starting on the first function that
|
||||
matches the supplied regex, and return an svg. Go back to your first tab, and
|
||||
you should see a relatively uninteresting but super promising svg.
|
||||
|
||||
Let's make the trace more interesting. Add a
|
||||
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
to your HandleHTTP method, rebuild, and restart. Load localhost:8080, then
|
||||
start a new request to your trace URL, then reload localhost:8080 again. Flip
|
||||
back to your trace, and you should see that the Proxy method only takes a
|
||||
portion of the time of HandleHTTP!
|
||||
|
||||
https://cdn.rawgit.com/spacemonkeygo/monkit/master/images/trace.svg
|
||||
|
||||
There's multiple ways to select a trace. You can select by regex using the
|
||||
preselect method (default), which first evaluates the regex on all known
|
||||
functions for sanity checking. Sometimes, however, the function you want to
|
||||
trace may not yet be known to monkit, in which case you'll want
|
||||
to turn preselection off. You may have a bad regex, or you may be in this case
|
||||
if you get the error "Bad Request: regex preselect matches 0 functions."
|
||||
|
||||
Another way to select a trace is by providing a trace id, which we'll get to
|
||||
next!
|
||||
|
||||
Make sure to check out what the addition of the time.Sleep call did to the
|
||||
other reports.
|
||||
|
||||
Plugins
|
||||
|
||||
It's easy to write plugins for monkit! Check out our first one that exports
|
||||
data to Zipkin (http://zipkin.io/)'s Scribe API:
|
||||
|
||||
https://github.com/spacemonkeygo/monkit-zipkin
|
||||
|
||||
We plan to have more (for HTrace, OpenTracing, etc, etc), soon!
|
||||
|
||||
*/
|
||||
package monkit // import "github.com/spacemonkeygo/monkit/v3"
|
185
vendor/github.com/spacemonkeygo/monkit/v3/durdist.go
generated
vendored
Normal file
185
vendor/github.com/spacemonkeygo/monkit/v3/durdist.go
generated
vendored
Normal file
|
@ -0,0 +1,185 @@
|
|||
// Copyright (C) 2016 Space Monkey, Inc.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// WARNING: THE NON-M4 VERSIONS OF THIS FILE ARE GENERATED BY GO GENERATE!
|
||||
// ONLY MAKE CHANGES TO THE M4 FILE
|
||||
//
|
||||
|
||||
package monkit
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DurationDist keeps statistics about values such as
|
||||
// low/high/recent/average/quantiles. Not threadsafe. Construct with
|
||||
// NewDurationDist(). Fields are expected to be read from but not written to.
|
||||
type DurationDist struct {
|
||||
// Low and High are the lowest and highest values observed since
|
||||
// construction or the last reset.
|
||||
Low, High time.Duration
|
||||
|
||||
// Recent is the last observed value.
|
||||
Recent time.Duration
|
||||
|
||||
// Count is the number of observed values since construction or the last
|
||||
// reset.
|
||||
Count int64
|
||||
|
||||
// Sum is the sum of all the observed values since construction or the last
|
||||
// reset.
|
||||
Sum time.Duration
|
||||
|
||||
key SeriesKey
|
||||
reservoir [ReservoirSize]float32
|
||||
rng xorshift128
|
||||
sorted bool
|
||||
}
|
||||
|
||||
func initDurationDist(v *DurationDist, key SeriesKey) {
|
||||
v.key = key
|
||||
v.rng = newXORShift128()
|
||||
}
|
||||
|
||||
// NewDurationDist creates a distribution of time.Durations.
|
||||
func NewDurationDist(key SeriesKey) (d *DurationDist) {
|
||||
d = &DurationDist{}
|
||||
initDurationDist(d, key)
|
||||
return d
|
||||
}
|
||||
|
||||
// Insert adds a value to the distribution, updating appropriate values.
|
||||
func (d *DurationDist) Insert(val time.Duration) {
|
||||
if d.Count != 0 {
|
||||
if val < d.Low {
|
||||
d.Low = val
|
||||
}
|
||||
if val > d.High {
|
||||
d.High = val
|
||||
}
|
||||
} else {
|
||||
d.Low = val
|
||||
d.High = val
|
||||
}
|
||||
d.Recent = val
|
||||
d.Sum += val
|
||||
|
||||
index := d.Count
|
||||
d.Count += 1
|
||||
|
||||
if index < ReservoirSize {
|
||||
d.reservoir[index] = float32(val)
|
||||
d.sorted = false
|
||||
} else {
|
||||
window := d.Count
|
||||
// careful, the capitalization of Window is important
|
||||
if Window > 0 && window > Window {
|
||||
window = Window
|
||||
}
|
||||
// fast, but kind of biased. probably okay
|
||||
j := d.rng.Uint64() % uint64(window)
|
||||
if j < ReservoirSize {
|
||||
d.reservoir[int(j)] = float32(val)
|
||||
d.sorted = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FullAverage calculates and returns the average of all inserted values.
|
||||
func (d *DurationDist) FullAverage() time.Duration {
|
||||
if d.Count > 0 {
|
||||
return d.Sum / time.Duration(d.Count)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// ReservoirAverage calculates the average of the current reservoir.
|
||||
func (d *DurationDist) ReservoirAverage() time.Duration {
|
||||
amount := ReservoirSize
|
||||
if d.Count < int64(amount) {
|
||||
amount = int(d.Count)
|
||||
}
|
||||
if amount <= 0 {
|
||||
return 0
|
||||
}
|
||||
var sum float32
|
||||
for i := 0; i < amount; i++ {
|
||||
sum += d.reservoir[i]
|
||||
}
|
||||
return time.Duration(sum / float32(amount))
|
||||
}
|
||||
|
||||
// Query will return the approximate value at the given quantile from the
|
||||
// reservoir, where 0 <= quantile <= 1.
|
||||
func (d *DurationDist) Query(quantile float64) time.Duration {
|
||||
rlen := int(ReservoirSize)
|
||||
if int64(rlen) > d.Count {
|
||||
rlen = int(d.Count)
|
||||
}
|
||||
|
||||
if rlen < 2 {
|
||||
return time.Duration(d.reservoir[0])
|
||||
}
|
||||
|
||||
reservoir := d.reservoir[:rlen]
|
||||
if !d.sorted {
|
||||
sort.Sort(float32Slice(reservoir))
|
||||
d.sorted = true
|
||||
}
|
||||
|
||||
if quantile <= 0 {
|
||||
return time.Duration(reservoir[0])
|
||||
}
|
||||
if quantile >= 1 {
|
||||
return time.Duration(reservoir[rlen-1])
|
||||
}
|
||||
|
||||
idx_float := quantile * float64(rlen-1)
|
||||
idx := int(idx_float)
|
||||
|
||||
diff := idx_float - float64(idx)
|
||||
prior := float64(reservoir[idx])
|
||||
return time.Duration(prior + diff*(float64(reservoir[idx+1])-prior))
|
||||
}
|
||||
|
||||
// Copy returns a full copy of the entire distribution.
|
||||
func (d *DurationDist) Copy() *DurationDist {
|
||||
cp := *d
|
||||
cp.rng = newXORShift128()
|
||||
return &cp
|
||||
}
|
||||
|
||||
func (d *DurationDist) Reset() {
|
||||
d.Low, d.High, d.Recent, d.Count, d.Sum = 0, 0, 0, 0, 0
|
||||
// resetting count will reset the quantile reservoir
|
||||
}
|
||||
|
||||
func (d *DurationDist) Stats(cb func(key SeriesKey, field string, val float64)) {
|
||||
count := d.Count
|
||||
cb(d.key, "count", float64(count))
|
||||
if count > 0 {
|
||||
cb(d.key, "sum", d.toFloat64(d.Sum))
|
||||
cb(d.key, "min", d.toFloat64(d.Low))
|
||||
cb(d.key, "avg", d.toFloat64(d.FullAverage()))
|
||||
cb(d.key, "max", d.toFloat64(d.High))
|
||||
cb(d.key, "rmin", d.toFloat64(d.Query(0)))
|
||||
cb(d.key, "ravg", d.toFloat64(d.ReservoirAverage()))
|
||||
cb(d.key, "r10", d.toFloat64(d.Query(.1)))
|
||||
cb(d.key, "r50", d.toFloat64(d.Query(.5)))
|
||||
cb(d.key, "r90", d.toFloat64(d.Query(.9)))
|
||||
cb(d.key, "rmax", d.toFloat64(d.Query(1)))
|
||||
cb(d.key, "recent", d.toFloat64(d.Recent))
|
||||
}
|
||||
}
|
114
vendor/github.com/spacemonkeygo/monkit/v3/error_names.go
generated
vendored
Normal file
114
vendor/github.com/spacemonkeygo/monkit/v3/error_names.go
generated
vendored
Normal file
|
@ -0,0 +1,114 @@
|
|||
// Copyright (C) 2017 Space Monkey, Inc.
|
||||
//
|
||||
// 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 monkit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// errorNameHandlers keeps track of the list of error name handlers monkit will
|
||||
// call to give errors good metric names.
|
||||
var errorNameHandlers struct {
|
||||
write_mu sync.Mutex
|
||||
value atomic.Value
|
||||
}
|
||||
|
||||
// AddErrorNameHandler adds an error name handler function that will be
|
||||
// consulted every time an error is captured for a task. The handlers will be
|
||||
// called in the order they were registered with the most recently added
|
||||
// handler first, until a handler returns true for the second return value.
|
||||
// If no handler returns true, the error is checked to see if it implements
|
||||
// an interface that allows it to name itself, and otherwise, monkit attempts
|
||||
// to find a good name for most built in Go standard library errors.
|
||||
func AddErrorNameHandler(f func(error) (string, bool)) {
|
||||
errorNameHandlers.write_mu.Lock()
|
||||
defer errorNameHandlers.write_mu.Unlock()
|
||||
|
||||
handlers, _ := errorNameHandlers.value.Load().([]func(error) (string, bool))
|
||||
handlers = append(handlers, f)
|
||||
errorNameHandlers.value.Store(handlers)
|
||||
}
|
||||
|
||||
// getErrorName implements the logic described in the AddErrorNameHandler
|
||||
// function.
|
||||
func getErrorName(err error) string {
|
||||
// check if any of the handlers will handle it
|
||||
handlers, _ := errorNameHandlers.value.Load().([]func(error) (string, bool))
|
||||
for i := len(handlers) - 1; i >= 0; i-- {
|
||||
if name, ok := handlers[i](err); ok {
|
||||
return name
|
||||
}
|
||||
}
|
||||
|
||||
// check if it knows how to name itself
|
||||
type namer interface {
|
||||
Name() (string, bool)
|
||||
}
|
||||
|
||||
if n, ok := err.(namer); ok {
|
||||
if name, ok := n.Name(); ok {
|
||||
return name
|
||||
}
|
||||
}
|
||||
|
||||
// check if it's a known error that we handle to give good names
|
||||
switch err {
|
||||
case io.EOF:
|
||||
return "EOF"
|
||||
case io.ErrUnexpectedEOF:
|
||||
return "Unexpected EOF Error"
|
||||
case io.ErrClosedPipe:
|
||||
return "Closed Pipe Error"
|
||||
case io.ErrNoProgress:
|
||||
return "No Progress Error"
|
||||
case io.ErrShortBuffer:
|
||||
return "Short Buffer Error"
|
||||
case io.ErrShortWrite:
|
||||
return "Short Write Error"
|
||||
case context.Canceled:
|
||||
return "Canceled"
|
||||
case context.DeadlineExceeded:
|
||||
return "Timeout"
|
||||
}
|
||||
if isErrnoError(err) {
|
||||
return "Errno"
|
||||
}
|
||||
switch err.(type) {
|
||||
case *os.SyscallError:
|
||||
return "Syscall Error"
|
||||
case net.UnknownNetworkError:
|
||||
return "Unknown Network Error"
|
||||
case *net.AddrError:
|
||||
return "Addr Error"
|
||||
case net.InvalidAddrError:
|
||||
return "Invalid Addr Error"
|
||||
case *net.OpError:
|
||||
return "Net Op Error"
|
||||
case *net.ParseError:
|
||||
return "Net Parse Error"
|
||||
case *net.DNSError:
|
||||
return "DNS Error"
|
||||
case *net.DNSConfigError:
|
||||
return "DNS Config Error"
|
||||
case net.Error:
|
||||
return "Network Error"
|
||||
}
|
||||
return "System Error"
|
||||
}
|
21
vendor/github.com/spacemonkeygo/monkit/v3/error_names_ae.go
generated
vendored
Normal file
21
vendor/github.com/spacemonkeygo/monkit/v3/error_names_ae.go
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
// Copyright (C) 2017 Space Monkey, Inc.
|
||||
//
|
||||
// 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.
|
||||
|
||||
// +build appengine
|
||||
|
||||
package monkit
|
||||
|
||||
func isErrnoError(err error) bool {
|
||||
return false
|
||||
}
|
24
vendor/github.com/spacemonkeygo/monkit/v3/error_names_syscall.go
generated
vendored
Normal file
24
vendor/github.com/spacemonkeygo/monkit/v3/error_names_syscall.go
generated
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
// Copyright (C) 2017 Space Monkey, Inc.
|
||||
//
|
||||
// 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.
|
||||
|
||||
// +build !appengine
|
||||
|
||||
package monkit
|
||||
|
||||
import "syscall"
|
||||
|
||||
func isErrnoError(err error) bool {
|
||||
_, ok := err.(syscall.Errno)
|
||||
return ok
|
||||
}
|
184
vendor/github.com/spacemonkeygo/monkit/v3/floatdist.go
generated
vendored
Normal file
184
vendor/github.com/spacemonkeygo/monkit/v3/floatdist.go
generated
vendored
Normal file
|
@ -0,0 +1,184 @@
|
|||
// Copyright (C) 2016 Space Monkey, Inc.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// WARNING: THE NON-M4 VERSIONS OF THIS FILE ARE GENERATED BY GO GENERATE!
|
||||
// ONLY MAKE CHANGES TO THE M4 FILE
|
||||
//
|
||||
|
||||
package monkit
|
||||
|
||||
import (
|
||||
"sort"
|
||||
)
|
||||
|
||||
// FloatDist keeps statistics about values such as
|
||||
// low/high/recent/average/quantiles. Not threadsafe. Construct with
|
||||
// NewFloatDist(). Fields are expected to be read from but not written to.
|
||||
type FloatDist struct {
|
||||
// Low and High are the lowest and highest values observed since
|
||||
// construction or the last reset.
|
||||
Low, High float64
|
||||
|
||||
// Recent is the last observed value.
|
||||
Recent float64
|
||||
|
||||
// Count is the number of observed values since construction or the last
|
||||
// reset.
|
||||
Count int64
|
||||
|
||||
// Sum is the sum of all the observed values since construction or the last
|
||||
// reset.
|
||||
Sum float64
|
||||
|
||||
key SeriesKey
|
||||
reservoir [ReservoirSize]float32
|
||||
rng xorshift128
|
||||
sorted bool
|
||||
}
|
||||
|
||||
func initFloatDist(v *FloatDist, key SeriesKey) {
|
||||
v.key = key
|
||||
v.rng = newXORShift128()
|
||||
}
|
||||
|
||||
// NewFloatDist creates a distribution of float64s.
|
||||
func NewFloatDist(key SeriesKey) (d *FloatDist) {
|
||||
d = &FloatDist{}
|
||||
initFloatDist(d, key)
|
||||
return d
|
||||
}
|
||||
|
||||
// Insert adds a value to the distribution, updating appropriate values.
|
||||
func (d *FloatDist) Insert(val float64) {
|
||||
if d.Count != 0 {
|
||||
if val < d.Low {
|
||||
d.Low = val
|
||||
}
|
||||
if val > d.High {
|
||||
d.High = val
|
||||
}
|
||||
} else {
|
||||
d.Low = val
|
||||
d.High = val
|
||||
}
|
||||
d.Recent = val
|
||||
d.Sum += val
|
||||
|
||||
index := d.Count
|
||||
d.Count += 1
|
||||
|
||||
if index < ReservoirSize {
|
||||
d.reservoir[index] = float32(val)
|
||||
d.sorted = false
|
||||
} else {
|
||||
window := d.Count
|
||||
// careful, the capitalization of Window is important
|
||||
if Window > 0 && window > Window {
|
||||
window = Window
|
||||
}
|
||||
// fast, but kind of biased. probably okay
|
||||
j := d.rng.Uint64() % uint64(window)
|
||||
if j < ReservoirSize {
|
||||
d.reservoir[int(j)] = float32(val)
|
||||
d.sorted = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FullAverage calculates and returns the average of all inserted values.
|
||||
func (d *FloatDist) FullAverage() float64 {
|
||||
if d.Count > 0 {
|
||||
return d.Sum / float64(d.Count)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// ReservoirAverage calculates the average of the current reservoir.
|
||||
func (d *FloatDist) ReservoirAverage() float64 {
|
||||
amount := ReservoirSize
|
||||
if d.Count < int64(amount) {
|
||||
amount = int(d.Count)
|
||||
}
|
||||
if amount <= 0 {
|
||||
return 0
|
||||
}
|
||||
var sum float32
|
||||
for i := 0; i < amount; i++ {
|
||||
sum += d.reservoir[i]
|
||||
}
|
||||
return float64(sum / float32(amount))
|
||||
}
|
||||
|
||||
// Query will return the approximate value at the given quantile from the
|
||||
// reservoir, where 0 <= quantile <= 1.
|
||||
func (d *FloatDist) Query(quantile float64) float64 {
|
||||
rlen := int(ReservoirSize)
|
||||
if int64(rlen) > d.Count {
|
||||
rlen = int(d.Count)
|
||||
}
|
||||
|
||||
if rlen < 2 {
|
||||
return float64(d.reservoir[0])
|
||||
}
|
||||
|
||||
reservoir := d.reservoir[:rlen]
|
||||
if !d.sorted {
|
||||
sort.Sort(float32Slice(reservoir))
|
||||
d.sorted = true
|
||||
}
|
||||
|
||||
if quantile <= 0 {
|
||||
return float64(reservoir[0])
|
||||
}
|
||||
if quantile >= 1 {
|
||||
return float64(reservoir[rlen-1])
|
||||
}
|
||||
|
||||
idx_float := quantile * float64(rlen-1)
|
||||
idx := int(idx_float)
|
||||
|
||||
diff := idx_float - float64(idx)
|
||||
prior := float64(reservoir[idx])
|
||||
return float64(prior + diff*(float64(reservoir[idx+1])-prior))
|
||||
}
|
||||
|
||||
// Copy returns a full copy of the entire distribution.
|
||||
func (d *FloatDist) Copy() *FloatDist {
|
||||
cp := *d
|
||||
cp.rng = newXORShift128()
|
||||
return &cp
|
||||
}
|
||||
|
||||
func (d *FloatDist) Reset() {
|
||||
d.Low, d.High, d.Recent, d.Count, d.Sum = 0, 0, 0, 0, 0
|
||||
// resetting count will reset the quantile reservoir
|
||||
}
|
||||
|
||||
func (d *FloatDist) Stats(cb func(key SeriesKey, field string, val float64)) {
|
||||
count := d.Count
|
||||
cb(d.key, "count", float64(count))
|
||||
if count > 0 {
|
||||
cb(d.key, "sum", d.toFloat64(d.Sum))
|
||||
cb(d.key, "min", d.toFloat64(d.Low))
|
||||
cb(d.key, "avg", d.toFloat64(d.FullAverage()))
|
||||
cb(d.key, "max", d.toFloat64(d.High))
|
||||
cb(d.key, "rmin", d.toFloat64(d.Query(0)))
|
||||
cb(d.key, "ravg", d.toFloat64(d.ReservoirAverage()))
|
||||
cb(d.key, "r10", d.toFloat64(d.Query(.1)))
|
||||
cb(d.key, "r50", d.toFloat64(d.Query(.5)))
|
||||
cb(d.key, "r90", d.toFloat64(d.Query(.9)))
|
||||
cb(d.key, "rmax", d.toFloat64(d.Query(1)))
|
||||
cb(d.key, "recent", d.toFloat64(d.Recent))
|
||||
}
|
||||
}
|
71
vendor/github.com/spacemonkeygo/monkit/v3/func.go
generated
vendored
Normal file
71
vendor/github.com/spacemonkeygo/monkit/v3/func.go
generated
vendored
Normal file
|
@ -0,0 +1,71 @@
|
|||
// Copyright (C) 2015 Space Monkey, Inc.
|
||||
//
|
||||
// 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 monkit
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Func represents a FuncStats bound to a particular function id, scope, and
|
||||
// name. You should create a Func using the Func creation methods
|
||||
// (Func/FuncNamed) on a Scope. If you want to manage installation bookkeeping
|
||||
// yourself, create a FuncStats directly. Expected Func creation like:
|
||||
//
|
||||
// var mon = monkit.Package()
|
||||
//
|
||||
// func MyFunc() {
|
||||
// f := mon.Func()
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
type Func struct {
|
||||
// sync/atomic things
|
||||
FuncStats
|
||||
|
||||
// constructor things
|
||||
id int64
|
||||
scope *Scope
|
||||
key SeriesKey
|
||||
}
|
||||
|
||||
func newFunc(s *Scope, key SeriesKey) (f *Func) {
|
||||
f = &Func{
|
||||
id: NewId(),
|
||||
scope: s,
|
||||
key: key,
|
||||
}
|
||||
initFuncStats(&f.FuncStats, key)
|
||||
return f
|
||||
}
|
||||
|
||||
// ShortName returns the name of the function within the package
|
||||
func (f *Func) ShortName() string { return f.key.Tags.Get("name") }
|
||||
|
||||
// FullName returns the name of the function including the package
|
||||
func (f *Func) FullName() string {
|
||||
return fmt.Sprintf("%s.%s", f.scope.name, f.key.Tags.Get("name"))
|
||||
}
|
||||
|
||||
// Id returns a unique integer referencing this function
|
||||
func (f *Func) Id() int64 { return f.id }
|
||||
|
||||
// Scope references the Scope this Func is bound to
|
||||
func (f *Func) Scope() *Scope { return f.scope }
|
||||
|
||||
// Parents will call the given cb with all of the unique Funcs that so far
|
||||
// have called this Func.
|
||||
func (f *Func) Parents(cb func(f *Func)) {
|
||||
f.FuncStats.parents(cb)
|
||||
}
|
78
vendor/github.com/spacemonkeygo/monkit/v3/funcset.go
generated
vendored
Normal file
78
vendor/github.com/spacemonkeygo/monkit/v3/funcset.go
generated
vendored
Normal file
|
@ -0,0 +1,78 @@
|
|||
// Copyright (C) 2015 Space Monkey, Inc.
|
||||
//
|
||||
// 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 monkit
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// funcSet is a set data structure (keeps track of unique functions). funcSet
|
||||
// has a fast path for dealing with cases where the set only has one element.
|
||||
//
|
||||
// to reduce memory usage for functions, funcSet exposes its mutex for use in
|
||||
// other contexts
|
||||
type funcSet struct {
|
||||
// sync/atomic things
|
||||
first *Func
|
||||
|
||||
// protected by mtx
|
||||
sync.Mutex
|
||||
rest map[*Func]struct{}
|
||||
}
|
||||
|
||||
var (
|
||||
// used to signify that we've specifically added a nil function, since nil is
|
||||
// used internally to specify an empty set.
|
||||
nilFunc = &Func{}
|
||||
)
|
||||
|
||||
func (s *funcSet) Add(f *Func) {
|
||||
if f == nil {
|
||||
f = nilFunc
|
||||
}
|
||||
if loadFunc(&s.first) == f {
|
||||
return
|
||||
}
|
||||
if compareAndSwapFunc(&s.first, nil, f) {
|
||||
return
|
||||
}
|
||||
s.Mutex.Lock()
|
||||
if s.rest == nil {
|
||||
s.rest = map[*Func]struct{}{}
|
||||
}
|
||||
s.rest[f] = struct{}{}
|
||||
s.Mutex.Unlock()
|
||||
}
|
||||
|
||||
// Iterate loops over all unique elements of the set.
|
||||
func (s *funcSet) Iterate(cb func(f *Func)) {
|
||||
s.Mutex.Lock()
|
||||
uniq := make(map[*Func]struct{}, len(s.rest)+1)
|
||||
for f := range s.rest {
|
||||
uniq[f] = struct{}{}
|
||||
}
|
||||
s.Mutex.Unlock()
|
||||
f := loadFunc(&s.first)
|
||||
if f != nil {
|
||||
uniq[f] = struct{}{}
|
||||
}
|
||||
for f := range uniq {
|
||||
if f == nilFunc {
|
||||
cb(nil)
|
||||
} else {
|
||||
cb(f)
|
||||
}
|
||||
}
|
||||
}
|
219
vendor/github.com/spacemonkeygo/monkit/v3/funcstats.go
generated
vendored
Normal file
219
vendor/github.com/spacemonkeygo/monkit/v3/funcstats.go
generated
vendored
Normal file
|
@ -0,0 +1,219 @@
|
|||
// Copyright (C) 2015 Space Monkey, Inc.
|
||||
//
|
||||
// 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 monkit
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/spacemonkeygo/monkit/v3/monotime"
|
||||
)
|
||||
|
||||
// FuncStats keeps track of statistics about a possible function's execution.
|
||||
// Should be created with NewFuncStats, though expected creation is through a
|
||||
// Func object:
|
||||
//
|
||||
// var mon = monkit.Package()
|
||||
//
|
||||
// func MyFunc() {
|
||||
// f := mon.Func()
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
type FuncStats struct {
|
||||
// sync/atomic things
|
||||
current int64
|
||||
highwater int64
|
||||
parentsAndMutex funcSet
|
||||
|
||||
// mutex things (reuses mutex from parents)
|
||||
errors map[string]int64
|
||||
panics int64
|
||||
successTimes DurationDist
|
||||
failureTimes DurationDist
|
||||
key SeriesKey
|
||||
}
|
||||
|
||||
func initFuncStats(f *FuncStats, key SeriesKey) {
|
||||
f.key = key
|
||||
f.errors = map[string]int64{}
|
||||
|
||||
key.Measurement += "_times"
|
||||
initDurationDist(&f.successTimes, key.WithTag("kind", "success"))
|
||||
initDurationDist(&f.failureTimes, key.WithTag("kind", "failure"))
|
||||
}
|
||||
|
||||
// NewFuncStats creates a FuncStats
|
||||
func NewFuncStats(key SeriesKey) (f *FuncStats) {
|
||||
f = &FuncStats{}
|
||||
initFuncStats(f, key)
|
||||
return f
|
||||
}
|
||||
|
||||
// Reset resets all recorded data.
|
||||
func (f *FuncStats) Reset() {
|
||||
atomic.StoreInt64(&f.current, 0)
|
||||
atomic.StoreInt64(&f.highwater, 0)
|
||||
f.parentsAndMutex.Lock()
|
||||
f.errors = make(map[string]int64, len(f.errors))
|
||||
f.panics = 0
|
||||
f.successTimes.Reset()
|
||||
f.failureTimes.Reset()
|
||||
f.parentsAndMutex.Unlock()
|
||||
}
|
||||
|
||||
func (f *FuncStats) start(parent *Func) {
|
||||
f.parentsAndMutex.Add(parent)
|
||||
current := atomic.AddInt64(&f.current, 1)
|
||||
for {
|
||||
highwater := atomic.LoadInt64(&f.highwater)
|
||||
if current <= highwater ||
|
||||
atomic.CompareAndSwapInt64(&f.highwater, highwater, current) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FuncStats) end(err error, panicked bool, duration time.Duration) {
|
||||
atomic.AddInt64(&f.current, -1)
|
||||
f.parentsAndMutex.Lock()
|
||||
if panicked {
|
||||
f.panics += 1
|
||||
f.failureTimes.Insert(duration)
|
||||
f.parentsAndMutex.Unlock()
|
||||
return
|
||||
}
|
||||
if err == nil {
|
||||
f.successTimes.Insert(duration)
|
||||
f.parentsAndMutex.Unlock()
|
||||
return
|
||||
}
|
||||
f.failureTimes.Insert(duration)
|
||||
f.errors[getErrorName(err)] += 1
|
||||
f.parentsAndMutex.Unlock()
|
||||
}
|
||||
|
||||
// Current returns how many concurrent instances of this function are currently
|
||||
// being observed.
|
||||
func (f *FuncStats) Current() int64 { return atomic.LoadInt64(&f.current) }
|
||||
|
||||
// Highwater returns the highest value Current() would ever return.
|
||||
func (f *FuncStats) Highwater() int64 { return atomic.LoadInt64(&f.highwater) }
|
||||
|
||||
// Success returns the number of successes that have been observed
|
||||
func (f *FuncStats) Success() (rv int64) {
|
||||
f.parentsAndMutex.Lock()
|
||||
rv = f.successTimes.Count
|
||||
f.parentsAndMutex.Unlock()
|
||||
return rv
|
||||
}
|
||||
|
||||
// Panics returns the number of panics that have been observed
|
||||
func (f *FuncStats) Panics() (rv int64) {
|
||||
f.parentsAndMutex.Lock()
|
||||
rv = f.panics
|
||||
f.parentsAndMutex.Unlock()
|
||||
return rv
|
||||
}
|
||||
|
||||
// Errors returns the number of errors observed by error type. The error type
|
||||
// is determined by handlers from AddErrorNameHandler, or a default that works
|
||||
// with most error types.
|
||||
func (f *FuncStats) Errors() (rv map[string]int64) {
|
||||
f.parentsAndMutex.Lock()
|
||||
rv = make(map[string]int64, len(f.errors))
|
||||
for errname, count := range f.errors {
|
||||
rv[errname] = count
|
||||
}
|
||||
f.parentsAndMutex.Unlock()
|
||||
return rv
|
||||
}
|
||||
|
||||
func (f *FuncStats) parents(cb func(f *Func)) {
|
||||
f.parentsAndMutex.Iterate(cb)
|
||||
}
|
||||
|
||||
// Stats implements the StatSource interface
|
||||
func (f *FuncStats) Stats(cb func(key SeriesKey, field string, val float64)) {
|
||||
cb(f.key, "current", float64(f.Current()))
|
||||
cb(f.key, "highwater", float64(f.Highwater()))
|
||||
|
||||
f.parentsAndMutex.Lock()
|
||||
panics := f.panics
|
||||
errs := make(map[string]int64, len(f.errors))
|
||||
for errname, count := range f.errors {
|
||||
errs[errname] = count
|
||||
}
|
||||
st := f.successTimes.Copy()
|
||||
ft := f.failureTimes.Copy()
|
||||
f.parentsAndMutex.Unlock()
|
||||
|
||||
cb(f.key, "successes", float64(st.Count))
|
||||
e_count := int64(0)
|
||||
for errname, count := range errs {
|
||||
e_count += count
|
||||
cb(f.key.WithTag("error_name", errname), "count", float64(count))
|
||||
}
|
||||
cb(f.key, "errors", float64(e_count))
|
||||
cb(f.key, "panics", float64(panics))
|
||||
cb(f.key, "failures", float64(e_count+panics))
|
||||
cb(f.key, "total", float64(st.Count+e_count+panics))
|
||||
|
||||
st.Stats(cb)
|
||||
ft.Stats(cb)
|
||||
}
|
||||
|
||||
// SuccessTimes returns a DurationDist of successes
|
||||
func (f *FuncStats) SuccessTimes() *DurationDist {
|
||||
f.parentsAndMutex.Lock()
|
||||
d := f.successTimes.Copy()
|
||||
f.parentsAndMutex.Unlock()
|
||||
return d
|
||||
}
|
||||
|
||||
// FailureTimes returns a DurationDist of failures (includes panics and errors)
|
||||
func (f *FuncStats) FailureTimes() *DurationDist {
|
||||
f.parentsAndMutex.Lock()
|
||||
d := f.failureTimes.Copy()
|
||||
f.parentsAndMutex.Unlock()
|
||||
return d
|
||||
}
|
||||
|
||||
// Observe starts the stopwatch for observing this function and returns a
|
||||
// function to be called at the end of the function execution. Expected usage
|
||||
// like:
|
||||
//
|
||||
// func MyFunc() (err error) {
|
||||
// defer funcStats.Observe()(&err)
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
func (f *FuncStats) Observe() func(errptr *error) {
|
||||
f.start(nil)
|
||||
start := monotime.Now()
|
||||
return func(errptr *error) {
|
||||
rec := recover()
|
||||
panicked := rec != nil
|
||||
finish := monotime.Now()
|
||||
var err error
|
||||
if errptr != nil {
|
||||
err = *errptr
|
||||
}
|
||||
f.end(err, panicked, finish.Sub(start))
|
||||
if panicked {
|
||||
panic(rec)
|
||||
}
|
||||
}
|
||||
}
|
5
vendor/github.com/spacemonkeygo/monkit/v3/go.mod
generated
vendored
Normal file
5
vendor/github.com/spacemonkeygo/monkit/v3/go.mod
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
module github.com/spacemonkeygo/monkit/v3
|
||||
|
||||
require golang.org/x/net v0.0.0-20190923162816-aa69164e4478
|
||||
|
||||
go 1.13
|
5
vendor/github.com/spacemonkeygo/monkit/v3/go.sum
generated
vendored
Normal file
5
vendor/github.com/spacemonkeygo/monkit/v3/go.sum
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
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=
|
48
vendor/github.com/spacemonkeygo/monkit/v3/id.go
generated
vendored
Normal file
48
vendor/github.com/spacemonkeygo/monkit/v3/id.go
generated
vendored
Normal file
|
@ -0,0 +1,48 @@
|
|||
// Copyright (C) 2015 Space Monkey, Inc.
|
||||
//
|
||||
// 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 monkit
|
||||
|
||||
import (
|
||||
crand "crypto/rand"
|
||||
"encoding/binary"
|
||||
"math/rand"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/spacemonkeygo/monkit/v3/monotime"
|
||||
)
|
||||
|
||||
var (
|
||||
idCounter uint64
|
||||
inc uint64
|
||||
)
|
||||
|
||||
func init() {
|
||||
var buf [16]byte
|
||||
if _, err := crand.Read(buf[:]); err == nil {
|
||||
idCounter = binary.BigEndian.Uint64(buf[0:8]) >> 1
|
||||
inc = binary.BigEndian.Uint64(buf[0:8])>>1 | 3
|
||||
} else {
|
||||
rng := rand.New(rand.NewSource(monotime.Now().UnixNano()))
|
||||
idCounter = uint64(rng.Int63())
|
||||
inc = uint64(rng.Int63() | 3)
|
||||
}
|
||||
}
|
||||
|
||||
// NewId returns a random integer intended for use when constructing new
|
||||
// traces. See NewTrace.
|
||||
func NewId() int64 {
|
||||
id := atomic.AddUint64(&idCounter, inc)
|
||||
return int64(id >> 1)
|
||||
}
|
184
vendor/github.com/spacemonkeygo/monkit/v3/intdist.go
generated
vendored
Normal file
184
vendor/github.com/spacemonkeygo/monkit/v3/intdist.go
generated
vendored
Normal file
|
@ -0,0 +1,184 @@
|
|||
// Copyright (C) 2016 Space Monkey, Inc.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// WARNING: THE NON-M4 VERSIONS OF THIS FILE ARE GENERATED BY GO GENERATE!
|
||||
// ONLY MAKE CHANGES TO THE M4 FILE
|
||||
//
|
||||
|
||||
package monkit
|
||||
|
||||
import (
|
||||
"sort"
|
||||
)
|
||||
|
||||
// IntDist keeps statistics about values such as
|
||||
// low/high/recent/average/quantiles. Not threadsafe. Construct with
|
||||
// NewIntDist(). Fields are expected to be read from but not written to.
|
||||
type IntDist struct {
|
||||
// Low and High are the lowest and highest values observed since
|
||||
// construction or the last reset.
|
||||
Low, High int64
|
||||
|
||||
// Recent is the last observed value.
|
||||
Recent int64
|
||||
|
||||
// Count is the number of observed values since construction or the last
|
||||
// reset.
|
||||
Count int64
|
||||
|
||||
// Sum is the sum of all the observed values since construction or the last
|
||||
// reset.
|
||||
Sum int64
|
||||
|
||||
key SeriesKey
|
||||
reservoir [ReservoirSize]float32
|
||||
rng xorshift128
|
||||
sorted bool
|
||||
}
|
||||
|
||||
func initIntDist(v *IntDist, key SeriesKey) {
|
||||
v.key = key
|
||||
v.rng = newXORShift128()
|
||||
}
|
||||
|
||||
// NewIntDist creates a distribution of int64s.
|
||||
func NewIntDist(key SeriesKey) (d *IntDist) {
|
||||
d = &IntDist{}
|
||||
initIntDist(d, key)
|
||||
return d
|
||||
}
|
||||
|
||||
// Insert adds a value to the distribution, updating appropriate values.
|
||||
func (d *IntDist) Insert(val int64) {
|
||||
if d.Count != 0 {
|
||||
if val < d.Low {
|
||||
d.Low = val
|
||||
}
|
||||
if val > d.High {
|
||||
d.High = val
|
||||
}
|
||||
} else {
|
||||
d.Low = val
|
||||
d.High = val
|
||||
}
|
||||
d.Recent = val
|
||||
d.Sum += val
|
||||
|
||||
index := d.Count
|
||||
d.Count += 1
|
||||
|
||||
if index < ReservoirSize {
|
||||
d.reservoir[index] = float32(val)
|
||||
d.sorted = false
|
||||
} else {
|
||||
window := d.Count
|
||||
// careful, the capitalization of Window is important
|
||||
if Window > 0 && window > Window {
|
||||
window = Window
|
||||
}
|
||||
// fast, but kind of biased. probably okay
|
||||
j := d.rng.Uint64() % uint64(window)
|
||||
if j < ReservoirSize {
|
||||
d.reservoir[int(j)] = float32(val)
|
||||
d.sorted = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FullAverage calculates and returns the average of all inserted values.
|
||||
func (d *IntDist) FullAverage() int64 {
|
||||
if d.Count > 0 {
|
||||
return d.Sum / int64(d.Count)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// ReservoirAverage calculates the average of the current reservoir.
|
||||
func (d *IntDist) ReservoirAverage() int64 {
|
||||
amount := ReservoirSize
|
||||
if d.Count < int64(amount) {
|
||||
amount = int(d.Count)
|
||||
}
|
||||
if amount <= 0 {
|
||||
return 0
|
||||
}
|
||||
var sum float32
|
||||
for i := 0; i < amount; i++ {
|
||||
sum += d.reservoir[i]
|
||||
}
|
||||
return int64(sum / float32(amount))
|
||||
}
|
||||
|
||||
// Query will return the approximate value at the given quantile from the
|
||||
// reservoir, where 0 <= quantile <= 1.
|
||||
func (d *IntDist) Query(quantile float64) int64 {
|
||||
rlen := int(ReservoirSize)
|
||||
if int64(rlen) > d.Count {
|
||||
rlen = int(d.Count)
|
||||
}
|
||||
|
||||
if rlen < 2 {
|
||||
return int64(d.reservoir[0])
|
||||
}
|
||||
|
||||
reservoir := d.reservoir[:rlen]
|
||||
if !d.sorted {
|
||||
sort.Sort(float32Slice(reservoir))
|
||||
d.sorted = true
|
||||
}
|
||||
|
||||
if quantile <= 0 {
|
||||
return int64(reservoir[0])
|
||||
}
|
||||
if quantile >= 1 {
|
||||
return int64(reservoir[rlen-1])
|
||||
}
|
||||
|
||||
idx_float := quantile * float64(rlen-1)
|
||||
idx := int(idx_float)
|
||||
|
||||
diff := idx_float - float64(idx)
|
||||
prior := float64(reservoir[idx])
|
||||
return int64(prior + diff*(float64(reservoir[idx+1])-prior))
|
||||
}
|
||||
|
||||
// Copy returns a full copy of the entire distribution.
|
||||
func (d *IntDist) Copy() *IntDist {
|
||||
cp := *d
|
||||
cp.rng = newXORShift128()
|
||||
return &cp
|
||||
}
|
||||
|
||||
func (d *IntDist) Reset() {
|
||||
d.Low, d.High, d.Recent, d.Count, d.Sum = 0, 0, 0, 0, 0
|
||||
// resetting count will reset the quantile reservoir
|
||||
}
|
||||
|
||||
func (d *IntDist) Stats(cb func(key SeriesKey, field string, val float64)) {
|
||||
count := d.Count
|
||||
cb(d.key, "count", float64(count))
|
||||
if count > 0 {
|
||||
cb(d.key, "sum", d.toFloat64(d.Sum))
|
||||
cb(d.key, "min", d.toFloat64(d.Low))
|
||||
cb(d.key, "avg", d.toFloat64(d.FullAverage()))
|
||||
cb(d.key, "max", d.toFloat64(d.High))
|
||||
cb(d.key, "rmin", d.toFloat64(d.Query(0)))
|
||||
cb(d.key, "ravg", d.toFloat64(d.ReservoirAverage()))
|
||||
cb(d.key, "r10", d.toFloat64(d.Query(.1)))
|
||||
cb(d.key, "r50", d.toFloat64(d.Query(.5)))
|
||||
cb(d.key, "r90", d.toFloat64(d.Query(.9)))
|
||||
cb(d.key, "rmax", d.toFloat64(d.Query(1)))
|
||||
cb(d.key, "recent", d.toFloat64(d.Recent))
|
||||
}
|
||||
}
|
212
vendor/github.com/spacemonkeygo/monkit/v3/meter.go
generated
vendored
Normal file
212
vendor/github.com/spacemonkeygo/monkit/v3/meter.go
generated
vendored
Normal file
|
@ -0,0 +1,212 @@
|
|||
// Copyright (C) 2015 Space Monkey, Inc.
|
||||
//
|
||||
// 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 monkit
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/spacemonkeygo/monkit/v3/monotime"
|
||||
)
|
||||
|
||||
const (
|
||||
ticksToKeep = 24
|
||||
timePerTick = 10 * time.Minute
|
||||
)
|
||||
|
||||
var (
|
||||
defaultTicker = ticker{}
|
||||
)
|
||||
|
||||
type meterBucket struct {
|
||||
count int64
|
||||
start time.Time
|
||||
}
|
||||
|
||||
// Meter keeps track of events and their rates over time.
|
||||
// Implements the StatSource interface. You should construct using NewMeter,
|
||||
// though expected usage is like:
|
||||
//
|
||||
// var (
|
||||
// mon = monkit.Package()
|
||||
// meter = mon.Meter("meter")
|
||||
// )
|
||||
//
|
||||
// func MyFunc() {
|
||||
// ...
|
||||
// meter.Mark(4) // 4 things happened
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
type Meter struct {
|
||||
mtx sync.Mutex
|
||||
total int64
|
||||
slices [ticksToKeep]meterBucket
|
||||
key SeriesKey
|
||||
}
|
||||
|
||||
// NewMeter constructs a Meter
|
||||
func NewMeter(key SeriesKey) *Meter {
|
||||
rv := &Meter{key: key}
|
||||
now := monotime.Now()
|
||||
for i := 0; i < ticksToKeep; i++ {
|
||||
rv.slices[i].start = now
|
||||
}
|
||||
defaultTicker.register(rv)
|
||||
return rv
|
||||
}
|
||||
|
||||
// Reset resets all internal state.
|
||||
//
|
||||
// Useful when monitoring a counter that has overflowed.
|
||||
func (e *Meter) Reset(new_total int64) {
|
||||
e.mtx.Lock()
|
||||
e.total = new_total
|
||||
now := monotime.Now()
|
||||
for _, slice := range e.slices {
|
||||
slice.count = 0
|
||||
slice.start = now
|
||||
}
|
||||
e.mtx.Unlock()
|
||||
}
|
||||
|
||||
// SetTotal sets the initial total count of the meter.
|
||||
func (e *Meter) SetTotal(total int64) {
|
||||
e.mtx.Lock()
|
||||
e.total = total
|
||||
e.mtx.Unlock()
|
||||
}
|
||||
|
||||
// Mark marks amount events occurring in the current time window.
|
||||
func (e *Meter) Mark(amount int) {
|
||||
e.mtx.Lock()
|
||||
e.slices[ticksToKeep-1].count += int64(amount)
|
||||
e.mtx.Unlock()
|
||||
}
|
||||
|
||||
// Mark64 marks amount events occurring in the current time window (int64 version).
|
||||
func (e *Meter) Mark64(amount int64) {
|
||||
e.mtx.Lock()
|
||||
e.slices[ticksToKeep-1].count += amount
|
||||
e.mtx.Unlock()
|
||||
}
|
||||
|
||||
func (e *Meter) tick(now time.Time) {
|
||||
e.mtx.Lock()
|
||||
// only advance meter buckets if something happened. otherwise
|
||||
// rare events will always just have zero rates.
|
||||
if e.slices[ticksToKeep-1].count != 0 {
|
||||
e.total += e.slices[0].count
|
||||
copy(e.slices[:], e.slices[1:])
|
||||
e.slices[ticksToKeep-1] = meterBucket{count: 0, start: now}
|
||||
}
|
||||
e.mtx.Unlock()
|
||||
}
|
||||
|
||||
func (e *Meter) stats(now time.Time) (rate float64, total int64) {
|
||||
current := int64(0)
|
||||
e.mtx.Lock()
|
||||
start := e.slices[0].start
|
||||
for i := 0; i < ticksToKeep; i++ {
|
||||
current += e.slices[i].count
|
||||
}
|
||||
total = e.total
|
||||
e.mtx.Unlock()
|
||||
total += current
|
||||
duration := now.Sub(start).Seconds()
|
||||
if duration > 0 {
|
||||
rate = float64(current) / duration
|
||||
} else {
|
||||
rate = 0
|
||||
}
|
||||
return rate, total
|
||||
}
|
||||
|
||||
// Rate returns the rate over the internal sliding window
|
||||
func (e *Meter) Rate() float64 {
|
||||
rate, _ := e.stats(monotime.Now())
|
||||
return rate
|
||||
}
|
||||
|
||||
// Total returns the total over the internal sliding window
|
||||
func (e *Meter) Total() float64 {
|
||||
_, total := e.stats(monotime.Now())
|
||||
return float64(total)
|
||||
}
|
||||
|
||||
// Stats implements the StatSource interface
|
||||
func (e *Meter) Stats(cb func(key SeriesKey, field string, val float64)) {
|
||||
rate, total := e.stats(monotime.Now())
|
||||
cb(e.key, "rate", rate)
|
||||
cb(e.key, "total", float64(total))
|
||||
}
|
||||
|
||||
// DiffMeter is a StatSource that shows the difference between
|
||||
// the rates of two meters. Expected usage like:
|
||||
//
|
||||
// var (
|
||||
// mon = monkit.Package()
|
||||
// herps = mon.Meter("herps")
|
||||
// derps = mon.Meter("derps")
|
||||
// herpToDerp = mon.DiffMeter("herp_to_derp", herps, derps)
|
||||
// )
|
||||
//
|
||||
type DiffMeter struct {
|
||||
meter1, meter2 *Meter
|
||||
key SeriesKey
|
||||
}
|
||||
|
||||
// Constructs a DiffMeter.
|
||||
func NewDiffMeter(key SeriesKey, meter1, meter2 *Meter) *DiffMeter {
|
||||
return &DiffMeter{key: key, meter1: meter1, meter2: meter2}
|
||||
}
|
||||
|
||||
// Stats implements the StatSource interface
|
||||
func (m *DiffMeter) Stats(cb func(key SeriesKey, field string, val float64)) {
|
||||
now := monotime.Now()
|
||||
rate1, total1 := m.meter1.stats(now)
|
||||
rate2, total2 := m.meter2.stats(now)
|
||||
cb(m.key, "rate", rate1-rate2)
|
||||
cb(m.key, "total", float64(total1-total2))
|
||||
}
|
||||
|
||||
type ticker struct {
|
||||
mtx sync.Mutex
|
||||
started bool
|
||||
meters []*Meter
|
||||
}
|
||||
|
||||
func (t *ticker) register(m *Meter) {
|
||||
t.mtx.Lock()
|
||||
if !t.started {
|
||||
t.started = true
|
||||
go t.run()
|
||||
}
|
||||
t.meters = append(t.meters, m)
|
||||
t.mtx.Unlock()
|
||||
}
|
||||
|
||||
func (t *ticker) run() {
|
||||
for {
|
||||
time.Sleep(timePerTick)
|
||||
t.mtx.Lock()
|
||||
meters := t.meters // this is safe since we only use append
|
||||
t.mtx.Unlock()
|
||||
now := monotime.Now()
|
||||
for _, m := range meters {
|
||||
m.tick(now)
|
||||
}
|
||||
}
|
||||
}
|
7
vendor/github.com/spacemonkeygo/monkit/v3/monotime/monotime.go
generated
vendored
Normal file
7
vendor/github.com/spacemonkeygo/monkit/v3/monotime/monotime.go
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
package monotime
|
||||
|
||||
import "time"
|
||||
|
||||
var initTime = time.Now()
|
||||
|
||||
func Now() time.Time { return initTime.Add(elapsed()) }
|
7
vendor/github.com/spacemonkeygo/monkit/v3/monotime/monotime_fallback.go
generated
vendored
Normal file
7
vendor/github.com/spacemonkeygo/monkit/v3/monotime/monotime_fallback.go
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
// +build !windows
|
||||
|
||||
package monotime
|
||||
|
||||
import "time"
|
||||
|
||||
func elapsed() time.Duration { return time.Since(initTime) }
|
27
vendor/github.com/spacemonkeygo/monkit/v3/monotime/monotime_windows.go
generated
vendored
Normal file
27
vendor/github.com/spacemonkeygo/monkit/v3/monotime/monotime_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
package monotime
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var (
|
||||
modkernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
queryPerformanceFrequencyProc = modkernel32.NewProc("QueryPerformanceFrequency")
|
||||
queryPerformanceCounterProc = modkernel32.NewProc("QueryPerformanceCounter")
|
||||
|
||||
qpcFrequency = queryPerformanceFrequency()
|
||||
)
|
||||
|
||||
func elapsed() time.Duration {
|
||||
var elapsed int64
|
||||
syscall.Syscall(queryPerformanceCounterProc.Addr(), 1, uintptr(unsafe.Pointer(&elapsed)), 0, 0)
|
||||
return time.Duration(elapsed) * time.Second / (time.Duration(qpcFrequency) * time.Nanosecond)
|
||||
}
|
||||
|
||||
func queryPerformanceFrequency() int64 {
|
||||
var freq int64
|
||||
syscall.Syscall(queryPerformanceFrequencyProc.Addr(), 1, uintptr(unsafe.Pointer(&freq)), 0, 0)
|
||||
return freq
|
||||
}
|
256
vendor/github.com/spacemonkeygo/monkit/v3/registry.go
generated
vendored
Normal file
256
vendor/github.com/spacemonkeygo/monkit/v3/registry.go
generated
vendored
Normal file
|
@ -0,0 +1,256 @@
|
|||
// Copyright (C) 2015 Space Monkey, Inc.
|
||||
//
|
||||
// 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 monkit
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type traceWatcherRef struct {
|
||||
watcher func(*Trace)
|
||||
}
|
||||
|
||||
// Registry encapsulates all of the top-level state for a monitoring system.
|
||||
// In general, only the Default registry is ever used.
|
||||
type Registry struct {
|
||||
// sync/atomic things
|
||||
traceWatcher *traceWatcherRef
|
||||
|
||||
watcherMtx sync.Mutex
|
||||
watcherCounter int64
|
||||
traceWatchers map[int64]func(*Trace)
|
||||
|
||||
scopeMtx sync.Mutex
|
||||
scopes map[string]*Scope
|
||||
|
||||
spanMtx sync.Mutex
|
||||
spans map[*Span]struct{}
|
||||
|
||||
orphanMtx sync.Mutex
|
||||
orphans map[*Span]struct{}
|
||||
}
|
||||
|
||||
// NewRegistry creates a NewRegistry, though you almost certainly just want
|
||||
// to use Default.
|
||||
func NewRegistry() *Registry {
|
||||
return &Registry{
|
||||
traceWatchers: map[int64]func(*Trace){},
|
||||
scopes: map[string]*Scope{},
|
||||
spans: map[*Span]struct{}{},
|
||||
orphans: map[*Span]struct{}{}}
|
||||
}
|
||||
|
||||
// Package creates a new monitoring Scope, named after the top level package.
|
||||
// It's expected that you'll have something like
|
||||
//
|
||||
// var mon = monkit.Package()
|
||||
//
|
||||
// at the top of each package.
|
||||
func (r *Registry) Package() *Scope {
|
||||
return r.ScopeNamed(callerPackage(1))
|
||||
}
|
||||
|
||||
// ScopeNamed is like Package, but lets you choose the name.
|
||||
func (r *Registry) ScopeNamed(name string) *Scope {
|
||||
r.scopeMtx.Lock()
|
||||
defer r.scopeMtx.Unlock()
|
||||
s, exists := r.scopes[name]
|
||||
if exists {
|
||||
return s
|
||||
}
|
||||
s = newScope(r, name)
|
||||
r.scopes[name] = s
|
||||
return s
|
||||
}
|
||||
|
||||
func (r *Registry) observeTrace(t *Trace) {
|
||||
watcher := loadTraceWatcherRef(&r.traceWatcher)
|
||||
if watcher != nil {
|
||||
watcher.watcher(t)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Registry) updateWatcher() {
|
||||
cbs := make([]func(*Trace), 0, len(r.traceWatchers))
|
||||
for _, cb := range r.traceWatchers {
|
||||
cbs = append(cbs, cb)
|
||||
}
|
||||
switch len(cbs) {
|
||||
case 0:
|
||||
storeTraceWatcherRef(&r.traceWatcher, nil)
|
||||
case 1:
|
||||
storeTraceWatcherRef(&r.traceWatcher,
|
||||
&traceWatcherRef{watcher: cbs[0]})
|
||||
default:
|
||||
storeTraceWatcherRef(&r.traceWatcher,
|
||||
&traceWatcherRef{watcher: func(t *Trace) {
|
||||
for _, cb := range cbs {
|
||||
cb(t)
|
||||
}
|
||||
}})
|
||||
}
|
||||
}
|
||||
|
||||
// ObserveTraces lets you observe all traces flowing through the system.
|
||||
// The passed in callback 'cb' will be called for every new trace as soon as
|
||||
// it starts, until the returned cancel method is called.
|
||||
// Note: this only applies to all new traces. If you want to find existing
|
||||
// or running traces, please pull them off of live RootSpans.
|
||||
func (r *Registry) ObserveTraces(cb func(*Trace)) (cancel func()) {
|
||||
// even though observeTrace doesn't get a mutex, it's only ever loading
|
||||
// the traceWatcher pointer, so we can use this mutex here to safely
|
||||
// coordinate the setting of the traceWatcher pointer.
|
||||
r.watcherMtx.Lock()
|
||||
defer r.watcherMtx.Unlock()
|
||||
|
||||
cbId := r.watcherCounter
|
||||
r.watcherCounter += 1
|
||||
r.traceWatchers[cbId] = cb
|
||||
r.updateWatcher()
|
||||
|
||||
return func() {
|
||||
r.watcherMtx.Lock()
|
||||
defer r.watcherMtx.Unlock()
|
||||
delete(r.traceWatchers, cbId)
|
||||
r.updateWatcher()
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Registry) rootSpanStart(s *Span) {
|
||||
r.spanMtx.Lock()
|
||||
r.spans[s] = struct{}{}
|
||||
r.spanMtx.Unlock()
|
||||
}
|
||||
|
||||
func (r *Registry) rootSpanEnd(s *Span) {
|
||||
r.spanMtx.Lock()
|
||||
delete(r.spans, s)
|
||||
r.spanMtx.Unlock()
|
||||
}
|
||||
|
||||
func (r *Registry) orphanedSpan(s *Span) {
|
||||
r.orphanMtx.Lock()
|
||||
r.orphans[s] = struct{}{}
|
||||
r.orphanMtx.Unlock()
|
||||
}
|
||||
|
||||
func (r *Registry) orphanEnd(s *Span) {
|
||||
r.orphanMtx.Lock()
|
||||
delete(r.orphans, s)
|
||||
r.orphanMtx.Unlock()
|
||||
}
|
||||
|
||||
// RootSpans will call 'cb' on all currently executing Spans with no live or
|
||||
// reachable parent. See also AllSpans.
|
||||
func (r *Registry) RootSpans(cb func(s *Span)) {
|
||||
r.spanMtx.Lock()
|
||||
spans := make([]*Span, 0, len(r.spans))
|
||||
for s := range r.spans {
|
||||
spans = append(spans, s)
|
||||
}
|
||||
r.spanMtx.Unlock()
|
||||
r.orphanMtx.Lock()
|
||||
orphans := make([]*Span, 0, len(r.orphans))
|
||||
for s := range r.orphans {
|
||||
orphans = append(orphans, s)
|
||||
}
|
||||
r.orphanMtx.Unlock()
|
||||
spans = append(spans, orphans...)
|
||||
sort.Sort(spanSorter(spans))
|
||||
for _, s := range spans {
|
||||
cb(s)
|
||||
}
|
||||
}
|
||||
|
||||
func walkSpan(s *Span, cb func(s *Span)) {
|
||||
cb(s)
|
||||
s.Children(func(s *Span) {
|
||||
walkSpan(s, cb)
|
||||
})
|
||||
}
|
||||
|
||||
// AllSpans calls 'cb' on all currently known Spans. See also RootSpans.
|
||||
func (r *Registry) AllSpans(cb func(s *Span)) {
|
||||
r.RootSpans(func(s *Span) { walkSpan(s, cb) })
|
||||
}
|
||||
|
||||
// Scopes calls 'cb' on all currently known Scopes.
|
||||
func (r *Registry) Scopes(cb func(s *Scope)) {
|
||||
r.scopeMtx.Lock()
|
||||
c := make([]*Scope, 0, len(r.scopes))
|
||||
for _, s := range r.scopes {
|
||||
c = append(c, s)
|
||||
}
|
||||
r.scopeMtx.Unlock()
|
||||
sort.Sort(scopeSorter(c))
|
||||
for _, s := range c {
|
||||
cb(s)
|
||||
}
|
||||
}
|
||||
|
||||
// Funcs calls 'cb' on all currently known Funcs.
|
||||
func (r *Registry) Funcs(cb func(f *Func)) {
|
||||
r.Scopes(func(s *Scope) { s.Funcs(cb) })
|
||||
}
|
||||
|
||||
// Stats implements the StatSource interface.
|
||||
func (r *Registry) Stats(cb func(key SeriesKey, field string, val float64)) {
|
||||
r.Scopes(func(s *Scope) {
|
||||
s.Stats(func(key SeriesKey, field string, val float64) {
|
||||
cb(key.WithTag("scope", s.name), field, val)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
var _ StatSource = (*Registry)(nil)
|
||||
|
||||
// Default is the default Registry
|
||||
var Default = NewRegistry()
|
||||
|
||||
// ScopeNamed is just a wrapper around Default.ScopeNamed
|
||||
func ScopeNamed(name string) *Scope { return Default.ScopeNamed(name) }
|
||||
|
||||
// RootSpans is just a wrapper around Default.RootSpans
|
||||
func RootSpans(cb func(s *Span)) { Default.RootSpans(cb) }
|
||||
|
||||
// Scopes is just a wrapper around Default.Scopes
|
||||
func Scopes(cb func(s *Scope)) { Default.Scopes(cb) }
|
||||
|
||||
// Funcs is just a wrapper around Default.Funcs
|
||||
func Funcs(cb func(f *Func)) { Default.Funcs(cb) }
|
||||
|
||||
// Package is just a wrapper around Default.Package
|
||||
func Package() *Scope { return Default.ScopeNamed(callerPackage(1)) }
|
||||
|
||||
// Stats is just a wrapper around Default.Stats
|
||||
func Stats(cb func(key SeriesKey, field string, val float64)) { Default.Stats(cb) }
|
||||
|
||||
type spanSorter []*Span
|
||||
|
||||
func (s spanSorter) Len() int { return len(s) }
|
||||
func (s spanSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
|
||||
func (s spanSorter) Less(i, j int) bool {
|
||||
ispan, jspan := s[i], s[j]
|
||||
iname, jname := ispan.f.FullName(), jspan.f.FullName()
|
||||
return (iname < jname) || (iname == jname && ispan.id < jspan.id)
|
||||
}
|
||||
|
||||
type scopeSorter []*Scope
|
||||
|
||||
func (s scopeSorter) Len() int { return len(s) }
|
||||
func (s scopeSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
func (s scopeSorter) Less(i, j int) bool { return s[i].name < s[j].name }
|
181
vendor/github.com/spacemonkeygo/monkit/v3/rng.go
generated
vendored
Normal file
181
vendor/github.com/spacemonkeygo/monkit/v3/rng.go
generated
vendored
Normal file
|
@ -0,0 +1,181 @@
|
|||
// Copyright (C) 2016 Space Monkey, Inc.
|
||||
|
||||
package monkit
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
// lcg is a simple linear congruential generator based on Knuths MMIX.
|
||||
type lcg uint64
|
||||
|
||||
// Make sure lcg is a rand.Source
|
||||
var _ rand.Source = (*lcg)(nil)
|
||||
|
||||
func newLCG() lcg { return lcg(rand.Int63()) }
|
||||
|
||||
// See Knuth.
|
||||
const (
|
||||
a = 6364136223846793005
|
||||
c = 1442695040888963407
|
||||
h = 0xffffffff00000000
|
||||
)
|
||||
|
||||
// Uint64 returns a uint64.
|
||||
func (l *lcg) Uint64() (ret uint64) {
|
||||
*l = a**l + c
|
||||
ret |= uint64(*l) >> 32
|
||||
*l = a**l + c
|
||||
ret |= uint64(*l) & h
|
||||
return
|
||||
}
|
||||
|
||||
// Int63 returns a positive 63 bit integer in an int64
|
||||
func (l *lcg) Int63() int64 {
|
||||
return int64(l.Uint64() >> 1)
|
||||
}
|
||||
|
||||
// Seed sets the state of the lcg.
|
||||
func (l *lcg) Seed(seed int64) {
|
||||
*l = lcg(seed)
|
||||
}
|
||||
|
||||
//
|
||||
// xorshift family of generators from https://en.wikipedia.org/wiki/Xorshift
|
||||
//
|
||||
// xorshift64 is the xorshift64* generator
|
||||
// xorshift1024 is the xorshift1024* generator
|
||||
// xorshift128 is the xorshift128+ generator
|
||||
//
|
||||
|
||||
type xorshift64 uint64
|
||||
|
||||
var _ rand.Source = (*xorshift64)(nil)
|
||||
|
||||
func newXORShift64() xorshift64 { return xorshift64(rand.Int63()) }
|
||||
|
||||
// Uint64 returns a uint64.
|
||||
func (s *xorshift64) Uint64() (ret uint64) {
|
||||
x := uint64(*s)
|
||||
x ^= x >> 12 // a
|
||||
x ^= x << 25 // b
|
||||
x ^= x >> 27 // c
|
||||
x *= 2685821657736338717
|
||||
*s = xorshift64(x)
|
||||
return x
|
||||
}
|
||||
|
||||
// Int63 returns a positive 63 bit integer in an int64
|
||||
func (s *xorshift64) Int63() int64 {
|
||||
return int64(s.Uint64() >> 1)
|
||||
}
|
||||
|
||||
// Seed sets the state of the lcg.
|
||||
func (s *xorshift64) Seed(seed int64) {
|
||||
*s = xorshift64(seed)
|
||||
}
|
||||
|
||||
type xorshift1024 struct {
|
||||
s [16]uint64
|
||||
p int
|
||||
}
|
||||
|
||||
var _ rand.Source = (*xorshift1024)(nil)
|
||||
|
||||
func newXORShift1024() xorshift1024 {
|
||||
var x xorshift1024
|
||||
x.Seed(rand.Int63())
|
||||
return x
|
||||
}
|
||||
|
||||
// Seed sets the state of the lcg.
|
||||
func (s *xorshift1024) Seed(seed int64) {
|
||||
rng := xorshift64(seed)
|
||||
*s = xorshift1024{
|
||||
s: [16]uint64{
|
||||
rng.Uint64(), rng.Uint64(), rng.Uint64(), rng.Uint64(),
|
||||
rng.Uint64(), rng.Uint64(), rng.Uint64(), rng.Uint64(),
|
||||
rng.Uint64(), rng.Uint64(), rng.Uint64(), rng.Uint64(),
|
||||
rng.Uint64(), rng.Uint64(), rng.Uint64(), rng.Uint64(),
|
||||
},
|
||||
p: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// Int63 returns a positive 63 bit integer in an int64
|
||||
func (s *xorshift1024) Int63() int64 {
|
||||
return int64(s.Uint64() >> 1)
|
||||
}
|
||||
|
||||
// Uint64 returns a uint64.
|
||||
func (s *xorshift1024) Uint64() (ret uint64) {
|
||||
// factoring this out proves to SSA backend that the array checks below
|
||||
// do not need bounds checks
|
||||
p := s.p & 15
|
||||
s0 := s.s[p]
|
||||
p = (p + 1) & 15
|
||||
s.p = p
|
||||
s1 := s.s[p]
|
||||
s1 ^= s1 << 31
|
||||
s.s[p] = s1 ^ s0 ^ (s1 >> 1) ^ (s0 >> 30)
|
||||
return s.s[p] * 1181783497276652981
|
||||
}
|
||||
|
||||
// Jump is used to advance the state 2^512 iterations.
|
||||
func (s *xorshift1024) Jump() {
|
||||
var t [16]uint64
|
||||
for i := 0; i < 16; i++ {
|
||||
for b := uint(0); b < 64; b++ {
|
||||
if (xorshift1024jump[i] & (1 << b)) > 0 {
|
||||
for j := 0; j < 16; j++ {
|
||||
t[j] ^= s.s[(j+s.p)&15]
|
||||
}
|
||||
}
|
||||
_ = s.Uint64()
|
||||
}
|
||||
}
|
||||
for j := 0; j < 16; j++ {
|
||||
s.s[(j+s.p)&15] = t[j]
|
||||
}
|
||||
}
|
||||
|
||||
var xorshift1024jump = [16]uint64{
|
||||
0x84242f96eca9c41d, 0xa3c65b8776f96855, 0x5b34a39f070b5837,
|
||||
0x4489affce4f31a1e, 0x2ffeeb0a48316f40, 0xdc2d9891fe68c022,
|
||||
0x3659132bb12fea70, 0xaac17d8efa43cab8, 0xc4cb815590989b13,
|
||||
0x5ee975283d71c93b, 0x691548c86c1bd540, 0x7910c41d10a1e6a5,
|
||||
0x0b5fc64563b3e2a8, 0x047f7684e9fc949d, 0xb99181f2d8f685ca,
|
||||
0x284600e3f30e38c3,
|
||||
}
|
||||
|
||||
type xorshift128 [2]uint64
|
||||
|
||||
var _ rand.Source = (*xorshift128)(nil)
|
||||
|
||||
func newXORShift128() xorshift128 {
|
||||
var s xorshift128
|
||||
s.Seed(rand.Int63())
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *xorshift128) Seed(seed int64) {
|
||||
rng := xorshift64(seed)
|
||||
*s = xorshift128{
|
||||
rng.Uint64(), rng.Uint64(),
|
||||
}
|
||||
}
|
||||
|
||||
// Int63 returns a positive 63 bit integer in an int64
|
||||
func (s *xorshift128) Int63() int64 {
|
||||
return int64(s.Uint64() >> 1)
|
||||
}
|
||||
|
||||
// Uint64 returns a uint64.
|
||||
func (s *xorshift128) Uint64() (ret uint64) {
|
||||
x := s[0]
|
||||
y := s[1]
|
||||
s[0] = y
|
||||
x ^= x << 23
|
||||
s[1] = x ^ y ^ (x >> 17) ^ (y >> 26)
|
||||
return s[1] + y
|
||||
}
|
301
vendor/github.com/spacemonkeygo/monkit/v3/scope.go
generated
vendored
Normal file
301
vendor/github.com/spacemonkeygo/monkit/v3/scope.go
generated
vendored
Normal file
|
@ -0,0 +1,301 @@
|
|||
// Copyright (C) 2015 Space Monkey, Inc.
|
||||
//
|
||||
// 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 monkit
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Scope represents a named collection of StatSources. Scopes are constructed
|
||||
// through Registries.
|
||||
type Scope struct {
|
||||
r *Registry
|
||||
name string
|
||||
mtx sync.RWMutex
|
||||
sources map[string]StatSource
|
||||
chains []StatSource
|
||||
}
|
||||
|
||||
func newScope(r *Registry, name string) *Scope {
|
||||
return &Scope{
|
||||
r: r,
|
||||
name: name,
|
||||
sources: map[string]StatSource{}}
|
||||
}
|
||||
|
||||
// Func retrieves or creates a Func named after the currently executing
|
||||
// function name (via runtime.Caller. See FuncNamed to choose your own name.
|
||||
func (s *Scope) Func() *Func {
|
||||
return s.FuncNamed(callerFunc(0))
|
||||
}
|
||||
|
||||
func (s *Scope) newSource(name string, constructor func() StatSource) (
|
||||
rv StatSource) {
|
||||
|
||||
s.mtx.RLock()
|
||||
source, exists := s.sources[name]
|
||||
s.mtx.RUnlock()
|
||||
|
||||
if exists {
|
||||
return source
|
||||
}
|
||||
|
||||
s.mtx.Lock()
|
||||
if source, exists := s.sources[name]; exists {
|
||||
s.mtx.Unlock()
|
||||
return source
|
||||
}
|
||||
|
||||
ss := constructor()
|
||||
s.sources[name] = ss
|
||||
s.mtx.Unlock()
|
||||
|
||||
return ss
|
||||
}
|
||||
|
||||
// FuncNamed retrieves or creates a Func named using the given name and
|
||||
// SeriesTags. See Func() for automatic name determination.
|
||||
//
|
||||
// Each unique combination of keys/values in each SeriesTag will result in a
|
||||
// unique Func. SeriesTags are not sorted, so keep the order consistent to avoid
|
||||
// unintentionally creating new unique Funcs.
|
||||
func (s *Scope) FuncNamed(name string, tags ...SeriesTag) *Func {
|
||||
var sourceName strings.Builder
|
||||
sourceName.WriteString("func:")
|
||||
sourceName.WriteString(name)
|
||||
for _, tag := range tags {
|
||||
sourceName.WriteByte(',')
|
||||
sourceName.WriteString(tag.Key)
|
||||
sourceName.WriteByte('=')
|
||||
sourceName.WriteString(tag.Val)
|
||||
}
|
||||
source := s.newSource(sourceName.String(), func() StatSource {
|
||||
key := NewSeriesKey("function").WithTag("name", name)
|
||||
for _, tag := range tags {
|
||||
key = key.WithTag(tag.Key, tag.Val)
|
||||
}
|
||||
return newFunc(s, key)
|
||||
})
|
||||
f, ok := source.(*Func)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("%s already used for another stats source: %#v",
|
||||
name, source))
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
// Funcs calls 'cb' for all Funcs registered on this Scope.
|
||||
func (s *Scope) Funcs(cb func(f *Func)) {
|
||||
s.mtx.Lock()
|
||||
funcs := make(map[*Func]struct{}, len(s.sources))
|
||||
for _, source := range s.sources {
|
||||
if f, ok := source.(*Func); ok {
|
||||
funcs[f] = struct{}{}
|
||||
}
|
||||
}
|
||||
s.mtx.Unlock()
|
||||
for f := range funcs {
|
||||
cb(f)
|
||||
}
|
||||
}
|
||||
|
||||
// Meter retrieves or creates a Meter named after the given name. See Event.
|
||||
func (s *Scope) Meter(name string) *Meter {
|
||||
source := s.newSource(name, func() StatSource { return NewMeter(NewSeriesKey(name)) })
|
||||
m, ok := source.(*Meter)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("%s already used for another stats source: %#v",
|
||||
name, source))
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// Event retrieves or creates a Meter named after the given name and then
|
||||
// calls Mark(1) on that meter.
|
||||
func (s *Scope) Event(name string) {
|
||||
s.Meter(name).Mark(1)
|
||||
}
|
||||
|
||||
// DiffMeter retrieves or creates a DiffMeter after the given name and two
|
||||
// submeters.
|
||||
func (s *Scope) DiffMeter(name string, m1, m2 *Meter) {
|
||||
source := s.newSource(name, func() StatSource {
|
||||
return NewDiffMeter(NewSeriesKey(name), m1, m2)
|
||||
})
|
||||
if _, ok := source.(*DiffMeter); !ok {
|
||||
panic(fmt.Sprintf("%s already used for another stats source: %#v",
|
||||
name, source))
|
||||
}
|
||||
}
|
||||
|
||||
// IntVal retrieves or creates an IntVal after the given name.
|
||||
func (s *Scope) IntVal(name string) *IntVal {
|
||||
source := s.newSource(name, func() StatSource { return NewIntVal(NewSeriesKey(name)) })
|
||||
m, ok := source.(*IntVal)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("%s already used for another stats source: %#v",
|
||||
name, source))
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// IntValf retrieves or creates an IntVal after the given printf-formatted
|
||||
// name.
|
||||
func (s *Scope) IntValf(template string, args ...interface{}) *IntVal {
|
||||
return s.IntVal(fmt.Sprintf(template, args...))
|
||||
}
|
||||
|
||||
// FloatVal retrieves or creates a FloatVal after the given name.
|
||||
func (s *Scope) FloatVal(name string) *FloatVal {
|
||||
source := s.newSource(name, func() StatSource { return NewFloatVal(NewSeriesKey(name)) })
|
||||
m, ok := source.(*FloatVal)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("%s already used for another stats source: %#v",
|
||||
name, source))
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// FloatValf retrieves or creates a FloatVal after the given printf-formatted
|
||||
// name.
|
||||
func (s *Scope) FloatValf(template string, args ...interface{}) *FloatVal {
|
||||
return s.FloatVal(fmt.Sprintf(template, args...))
|
||||
}
|
||||
|
||||
// BoolVal retrieves or creates a BoolVal after the given name.
|
||||
func (s *Scope) BoolVal(name string) *BoolVal {
|
||||
source := s.newSource(name, func() StatSource { return NewBoolVal(NewSeriesKey(name)) })
|
||||
m, ok := source.(*BoolVal)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("%s already used for another stats source: %#v",
|
||||
name, source))
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// BoolValf retrieves or creates a BoolVal after the given printf-formatted
|
||||
// name.
|
||||
func (s *Scope) BoolValf(template string, args ...interface{}) *BoolVal {
|
||||
return s.BoolVal(fmt.Sprintf(template, args...))
|
||||
}
|
||||
|
||||
// StructVal retrieves or creates a StructVal after the given name.
|
||||
func (s *Scope) StructVal(name string) *StructVal {
|
||||
source := s.newSource(name, func() StatSource { return NewStructVal(NewSeriesKey(name)) })
|
||||
m, ok := source.(*StructVal)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("%s already used for another stats source: %#v",
|
||||
name, source))
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// Timer retrieves or creates a Timer after the given name.
|
||||
func (s *Scope) Timer(name string) *Timer {
|
||||
source := s.newSource(name, func() StatSource { return NewTimer(NewSeriesKey(name)) })
|
||||
m, ok := source.(*Timer)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("%s already used for another stats source: %#v",
|
||||
name, source))
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// Counter retrieves or creates a Counter after the given name.
|
||||
func (s *Scope) Counter(name string) *Counter {
|
||||
source := s.newSource(name, func() StatSource { return NewCounter(NewSeriesKey(name)) })
|
||||
m, ok := source.(*Counter)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("%s already used for another stats source: %#v",
|
||||
name, source))
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// Gauge registers a callback that returns a float as the given name in the
|
||||
// Scope's StatSource table.
|
||||
func (s *Scope) Gauge(name string, cb func() float64) {
|
||||
type gauge struct{ StatSource }
|
||||
|
||||
// gauges allow overwriting
|
||||
s.mtx.Lock()
|
||||
defer s.mtx.Unlock()
|
||||
|
||||
if source, exists := s.sources[name]; exists {
|
||||
if _, ok := source.(gauge); !ok {
|
||||
panic(fmt.Sprintf("%s already used for another stats source: %#v",
|
||||
name, source))
|
||||
}
|
||||
}
|
||||
|
||||
s.sources[name] = gauge{StatSource: StatSourceFunc(
|
||||
func(scb func(key SeriesKey, field string, value float64)) {
|
||||
scb(NewSeriesKey(name), "value", cb())
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
// Chain registers a full StatSource as the given name in the Scope's
|
||||
// StatSource table.
|
||||
func (s *Scope) Chain(source StatSource) {
|
||||
// chains allow overwriting
|
||||
s.mtx.Lock()
|
||||
defer s.mtx.Unlock()
|
||||
|
||||
s.chains = append(s.chains, source)
|
||||
}
|
||||
|
||||
func (s *Scope) allNamedSources() (sources []namedSource) {
|
||||
s.mtx.Lock()
|
||||
sources = make([]namedSource, 0, len(s.sources))
|
||||
for name, source := range s.sources {
|
||||
sources = append(sources, namedSource{name: name, source: source})
|
||||
}
|
||||
s.mtx.Unlock()
|
||||
return sources
|
||||
}
|
||||
|
||||
// Stats implements the StatSource interface.
|
||||
func (s *Scope) Stats(cb func(key SeriesKey, field string, val float64)) {
|
||||
for _, namedSource := range s.allNamedSources() {
|
||||
namedSource.source.Stats(cb)
|
||||
}
|
||||
|
||||
s.mtx.Lock()
|
||||
chains := append([]StatSource(nil), s.chains...)
|
||||
s.mtx.Unlock()
|
||||
|
||||
for _, source := range chains {
|
||||
source.Stats(cb)
|
||||
}
|
||||
}
|
||||
|
||||
// Name returns the name of the Scope, often the Package name.
|
||||
func (s *Scope) Name() string { return s.name }
|
||||
|
||||
var _ StatSource = (*Scope)(nil)
|
||||
|
||||
type namedSource struct {
|
||||
name string
|
||||
source StatSource
|
||||
}
|
||||
|
||||
type namedSourceList []namedSource
|
||||
|
||||
func (l namedSourceList) Len() int { return len(l) }
|
||||
func (l namedSourceList) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
|
||||
func (l namedSourceList) Less(i, j int) bool { return l[i].name < l[j].name }
|
146
vendor/github.com/spacemonkeygo/monkit/v3/span.go
generated
vendored
Normal file
146
vendor/github.com/spacemonkeygo/monkit/v3/span.go
generated
vendored
Normal file
|
@ -0,0 +1,146 @@
|
|||
// Copyright (C) 2015 Space Monkey, Inc.
|
||||
//
|
||||
// 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 monkit
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ctxKey int
|
||||
|
||||
const (
|
||||
spanKey ctxKey = iota
|
||||
)
|
||||
|
||||
// Annotation represents an arbitrary name and value string pair
|
||||
type Annotation struct {
|
||||
Name string
|
||||
Value string
|
||||
}
|
||||
|
||||
func (s *Span) addChild(child *Span) {
|
||||
s.mtx.Lock()
|
||||
s.children.Add(child)
|
||||
done := s.done
|
||||
s.mtx.Unlock()
|
||||
if done {
|
||||
child.orphan()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Span) removeChild(child *Span) {
|
||||
s.mtx.Lock()
|
||||
s.children.Remove(child)
|
||||
s.mtx.Unlock()
|
||||
}
|
||||
|
||||
func (s *Span) orphan() {
|
||||
s.mtx.Lock()
|
||||
if !s.done && !s.orphaned {
|
||||
s.orphaned = true
|
||||
s.f.scope.r.orphanedSpan(s)
|
||||
}
|
||||
s.mtx.Unlock()
|
||||
}
|
||||
|
||||
// Duration returns the current amount of time the Span has been running
|
||||
func (s *Span) Duration() time.Duration {
|
||||
return time.Since(s.start)
|
||||
}
|
||||
|
||||
// Start returns the time the Span started.
|
||||
func (s *Span) Start() time.Time {
|
||||
return s.start
|
||||
}
|
||||
|
||||
// Value implements context.Context
|
||||
func (s *Span) Value(key interface{}) interface{} {
|
||||
if key == spanKey {
|
||||
return s
|
||||
}
|
||||
return s.Context.Value(key)
|
||||
}
|
||||
|
||||
// String implements context.Context
|
||||
func (s *Span) String() string {
|
||||
// TODO: for working with Contexts
|
||||
return fmt.Sprintf("%v.WithSpan()", s.Context)
|
||||
}
|
||||
|
||||
// Children returns all known running child Spans.
|
||||
func (s *Span) Children(cb func(s *Span)) {
|
||||
found := map[*Span]bool{}
|
||||
var sorter []*Span
|
||||
s.mtx.Lock()
|
||||
s.children.Iterate(func(s *Span) {
|
||||
if !found[s] {
|
||||
found[s] = true
|
||||
sorter = append(sorter, s)
|
||||
}
|
||||
})
|
||||
s.mtx.Unlock()
|
||||
sort.Sort(spanSorter(sorter))
|
||||
for _, s := range sorter {
|
||||
cb(s)
|
||||
}
|
||||
}
|
||||
|
||||
// Args returns the list of strings associated with the args given to the
|
||||
// Task that created this Span.
|
||||
func (s *Span) Args() (rv []string) {
|
||||
rv = make([]string, 0, len(s.args))
|
||||
for _, arg := range s.args {
|
||||
rv = append(rv, fmt.Sprintf("%#v", arg))
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
// Id returns the Span id.
|
||||
func (s *Span) Id() int64 { return s.id }
|
||||
|
||||
// Func returns the Func that kicked off this Span.
|
||||
func (s *Span) Func() *Func { return s.f }
|
||||
|
||||
// Trace returns the Trace this Span is associated with.
|
||||
func (s *Span) Trace() *Trace { return s.trace }
|
||||
|
||||
// Parent returns the Parent Span.
|
||||
func (s *Span) Parent() *Span { return s.parent }
|
||||
|
||||
// Annotations returns any added annotations created through the Span Annotate
|
||||
// method
|
||||
func (s *Span) Annotations() []Annotation {
|
||||
s.mtx.Lock()
|
||||
annotations := s.annotations // okay cause we only ever append to this slice
|
||||
s.mtx.Unlock()
|
||||
return append([]Annotation(nil), annotations...)
|
||||
}
|
||||
|
||||
// Annotate adds an annotation to the existing Span.
|
||||
func (s *Span) Annotate(name, val string) {
|
||||
s.mtx.Lock()
|
||||
s.annotations = append(s.annotations, Annotation{Name: name, Value: val})
|
||||
s.mtx.Unlock()
|
||||
}
|
||||
|
||||
// Orphaned returns true if the Parent span ended before this Span did.
|
||||
func (s *Span) Orphaned() (rv bool) {
|
||||
s.mtx.Lock()
|
||||
rv = s.orphaned
|
||||
s.mtx.Unlock()
|
||||
return rv
|
||||
}
|
59
vendor/github.com/spacemonkeygo/monkit/v3/spanbag.go
generated
vendored
Normal file
59
vendor/github.com/spacemonkeygo/monkit/v3/spanbag.go
generated
vendored
Normal file
|
@ -0,0 +1,59 @@
|
|||
// Copyright (C) 2015 Space Monkey, Inc.
|
||||
//
|
||||
// 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 monkit
|
||||
|
||||
// spanBag is a bag data structure (can add 0 or more references to a span,
|
||||
// where every add needs to be matched with an equivalent remove). spanBag has
|
||||
// a fast path for dealing with cases where the bag only has one element (the
|
||||
// common case). spanBag is not threadsafe
|
||||
type spanBag struct {
|
||||
first *Span
|
||||
rest map[*Span]int32
|
||||
}
|
||||
|
||||
func (b *spanBag) Add(s *Span) {
|
||||
if b.first == nil {
|
||||
b.first = s
|
||||
return
|
||||
}
|
||||
if b.rest == nil {
|
||||
b.rest = map[*Span]int32{}
|
||||
}
|
||||
b.rest[s] += 1
|
||||
}
|
||||
|
||||
func (b *spanBag) Remove(s *Span) {
|
||||
if b.first == s {
|
||||
b.first = nil
|
||||
return
|
||||
}
|
||||
// okay it must be in b.rest
|
||||
count := b.rest[s]
|
||||
if count <= 1 {
|
||||
delete(b.rest, s)
|
||||
} else {
|
||||
b.rest[s] = count - 1
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate returns all elements
|
||||
func (b *spanBag) Iterate(cb func(*Span)) {
|
||||
if b.first != nil {
|
||||
cb(b.first)
|
||||
}
|
||||
for s := range b.rest {
|
||||
cb(s)
|
||||
}
|
||||
}
|
35
vendor/github.com/spacemonkeygo/monkit/v3/spinlock.go
generated
vendored
Normal file
35
vendor/github.com/spacemonkeygo/monkit/v3/spinlock.go
generated
vendored
Normal file
|
@ -0,0 +1,35 @@
|
|||
// Copyright (C) 2015 Space Monkey, Inc.
|
||||
//
|
||||
// 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 monkit
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type spinLock uint32
|
||||
|
||||
func (s *spinLock) Lock() {
|
||||
for {
|
||||
if atomic.CompareAndSwapUint32((*uint32)(s), 0, 1) {
|
||||
return
|
||||
}
|
||||
runtime.Gosched()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *spinLock) Unlock() {
|
||||
atomic.StoreUint32((*uint32)(s), 0)
|
||||
}
|
77
vendor/github.com/spacemonkeygo/monkit/v3/stats.go
generated
vendored
Normal file
77
vendor/github.com/spacemonkeygo/monkit/v3/stats.go
generated
vendored
Normal file
|
@ -0,0 +1,77 @@
|
|||
// Copyright (C) 2015 Space Monkey, Inc.
|
||||
//
|
||||
// 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 monkit
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SeriesKey represents an individual time series for monkit to output.
|
||||
type SeriesKey struct {
|
||||
Measurement string
|
||||
Tags *TagSet
|
||||
}
|
||||
|
||||
// NewSeriesKey constructs a new series with the minimal fields.
|
||||
func NewSeriesKey(measurement string) SeriesKey {
|
||||
return SeriesKey{Measurement: measurement}
|
||||
}
|
||||
|
||||
// WithTag returns a copy of the SeriesKey with the tag set
|
||||
func (s SeriesKey) WithTag(key, value string) SeriesKey {
|
||||
s.Tags = s.Tags.Set(key, value)
|
||||
return s
|
||||
}
|
||||
|
||||
// String returns a string representation of the series. For example, it returns
|
||||
// something like `measurement,tag0=val0,tag1=val1`.
|
||||
func (s SeriesKey) String() string {
|
||||
var builder strings.Builder
|
||||
writeMeasurement(&builder, s.Measurement)
|
||||
if s.Tags.Len() > 0 {
|
||||
builder.WriteByte(',')
|
||||
builder.WriteString(s.Tags.String())
|
||||
}
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
func (s SeriesKey) WithField(field string) string {
|
||||
var builder strings.Builder
|
||||
builder.WriteString(s.String())
|
||||
builder.WriteByte(' ')
|
||||
writeTag(&builder, field)
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
// StatSource represents anything that can return named floating point values.
|
||||
type StatSource interface {
|
||||
Stats(cb func(key SeriesKey, field string, val float64))
|
||||
}
|
||||
|
||||
type StatSourceFunc func(cb func(key SeriesKey, field string, val float64))
|
||||
|
||||
func (f StatSourceFunc) Stats(cb func(key SeriesKey, field string, val float64)) {
|
||||
f(cb)
|
||||
}
|
||||
|
||||
// Collect takes something that implements the StatSource interface and returns
|
||||
// a key/value map.
|
||||
func Collect(mon StatSource) map[string]float64 {
|
||||
rv := make(map[string]float64)
|
||||
mon.Stats(func(key SeriesKey, field string, val float64) {
|
||||
rv[key.WithField(field)] = val
|
||||
})
|
||||
return rv
|
||||
}
|
59
vendor/github.com/spacemonkeygo/monkit/v3/struct.go
generated
vendored
Normal file
59
vendor/github.com/spacemonkeygo/monkit/v3/struct.go
generated
vendored
Normal file
|
@ -0,0 +1,59 @@
|
|||
// Copyright (C) 2015 Space Monkey, Inc.
|
||||
//
|
||||
// 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 monkit
|
||||
|
||||
import "reflect"
|
||||
|
||||
var f64Type = reflect.TypeOf(float64(0))
|
||||
|
||||
type emptyStatSource struct{}
|
||||
|
||||
func (emptyStatSource) Stats(cb func(key SeriesKey, field string, val float64)) {}
|
||||
|
||||
// StatSourceFromStruct uses the reflect package to implement the Stats call
|
||||
// across all float64-castable fields of the struct.
|
||||
func StatSourceFromStruct(key SeriesKey, structData interface{}) StatSource {
|
||||
val := deref(reflect.ValueOf(structData))
|
||||
|
||||
typ := val.Type()
|
||||
if typ.Kind() != reflect.Struct {
|
||||
return emptyStatSource{}
|
||||
}
|
||||
|
||||
return StatSourceFunc(func(cb func(key SeriesKey, field string, val float64)) {
|
||||
for i := 0; i < typ.NumField(); i++ {
|
||||
field := deref(val.Field(i))
|
||||
field_type := field.Type()
|
||||
|
||||
if field_type.Kind() == reflect.Struct && field.CanInterface() {
|
||||
child_source := StatSourceFromStruct(key, field.Interface())
|
||||
child_source.Stats(func(key SeriesKey, field string, val float64) {
|
||||
cb(key, typ.Field(i).Name+"."+field, val)
|
||||
})
|
||||
|
||||
} else if field_type.ConvertibleTo(f64Type) {
|
||||
cb(key, typ.Field(i).Name, field.Convert(f64Type).Float())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// if val is a pointer, deref until it isn't
|
||||
func deref(val reflect.Value) reflect.Value {
|
||||
for val.Type().Kind() == reflect.Ptr {
|
||||
val = val.Elem()
|
||||
}
|
||||
return val
|
||||
}
|
157
vendor/github.com/spacemonkeygo/monkit/v3/tags.go
generated
vendored
Normal file
157
vendor/github.com/spacemonkeygo/monkit/v3/tags.go
generated
vendored
Normal file
|
@ -0,0 +1,157 @@
|
|||
// Copyright (C) 2015 Space Monkey, Inc.
|
||||
//
|
||||
// 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 monkit
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SeriesTag is a key/value pair. When used with a measurement name, each set
|
||||
// of unique key/value pairs represents a new unique series.
|
||||
type SeriesTag struct {
|
||||
Key, Val string
|
||||
}
|
||||
|
||||
// NewTag creates a new tag
|
||||
func NewSeriesTag(key, val string) SeriesTag {
|
||||
return SeriesTag{key, val}
|
||||
}
|
||||
|
||||
// TagSet is an immutible collection of tag, value pairs.
|
||||
type TagSet struct {
|
||||
all map[string]string
|
||||
str string // cached string form
|
||||
}
|
||||
|
||||
// Get returns the value associated with the key.
|
||||
func (t *TagSet) Get(key string) string {
|
||||
if t == nil || t.all == nil {
|
||||
return ""
|
||||
}
|
||||
return t.all[key]
|
||||
}
|
||||
|
||||
// All returns a map of all the key/value pairs in the tag set. It
|
||||
// should not be modified.
|
||||
func (t *TagSet) All() map[string]string {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
return t.all
|
||||
}
|
||||
|
||||
// Len returns the number of tags in the tag set.
|
||||
func (t *TagSet) Len() int {
|
||||
if t == nil {
|
||||
return 0
|
||||
}
|
||||
return len(t.all)
|
||||
}
|
||||
|
||||
// Set returns a new tag set with the key associated to the value.
|
||||
func (t *TagSet) Set(key, value string) *TagSet {
|
||||
return t.SetAll(map[string]string{key: value})
|
||||
}
|
||||
|
||||
// SetAll returns a new tag set with the key value pairs in the map all set.
|
||||
func (t *TagSet) SetAll(kvs map[string]string) *TagSet {
|
||||
all := make(map[string]string)
|
||||
if t != nil {
|
||||
for key, value := range t.all {
|
||||
all[key] = value
|
||||
}
|
||||
}
|
||||
for key, value := range kvs {
|
||||
all[key] = value
|
||||
}
|
||||
return &TagSet{all: all}
|
||||
}
|
||||
|
||||
// String returns a string form of the tag set suitable for sending to influxdb.
|
||||
func (t *TagSet) String() string {
|
||||
if t == nil {
|
||||
return ""
|
||||
}
|
||||
if t.str == "" {
|
||||
var builder strings.Builder
|
||||
t.writeTags(&builder)
|
||||
t.str = builder.String()
|
||||
}
|
||||
return t.str
|
||||
}
|
||||
|
||||
// writeTags writes the tags in the tag set to the builder.
|
||||
func (t *TagSet) writeTags(builder *strings.Builder) {
|
||||
type kv struct {
|
||||
key string
|
||||
value string
|
||||
}
|
||||
var kvs []kv
|
||||
|
||||
for key, value := range t.all {
|
||||
kvs = append(kvs, kv{key, value})
|
||||
}
|
||||
sort.Slice(kvs, func(i, j int) bool {
|
||||
return kvs[i].key < kvs[j].key
|
||||
})
|
||||
|
||||
for i, kv := range kvs {
|
||||
if i > 0 {
|
||||
builder.WriteByte(',')
|
||||
}
|
||||
writeTag(builder, kv.key)
|
||||
builder.WriteByte('=')
|
||||
writeTag(builder, kv.value)
|
||||
}
|
||||
}
|
||||
|
||||
// writeMeasurement writes a measurement to the builder.
|
||||
func writeMeasurement(builder *strings.Builder, measurement string) {
|
||||
if strings.IndexByte(measurement, ',') == -1 &&
|
||||
strings.IndexByte(measurement, ' ') == -1 {
|
||||
|
||||
builder.WriteString(measurement)
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i < len(measurement); i++ {
|
||||
if measurement[i] == ',' ||
|
||||
measurement[i] == ' ' {
|
||||
builder.WriteByte('\\')
|
||||
}
|
||||
builder.WriteByte(measurement[i])
|
||||
}
|
||||
}
|
||||
|
||||
// writeTag writes a tag key, value, or field key to the builder.
|
||||
func writeTag(builder *strings.Builder, tag string) {
|
||||
if strings.IndexByte(tag, ',') == -1 &&
|
||||
strings.IndexByte(tag, '=') == -1 &&
|
||||
strings.IndexByte(tag, ' ') == -1 {
|
||||
|
||||
builder.WriteString(tag)
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i < len(tag); i++ {
|
||||
if tag[i] == ',' ||
|
||||
tag[i] == '=' ||
|
||||
tag[i] == ' ' {
|
||||
builder.WriteByte('\\')
|
||||
}
|
||||
builder.WriteByte(tag[i])
|
||||
}
|
||||
}
|
74
vendor/github.com/spacemonkeygo/monkit/v3/task.go
generated
vendored
Normal file
74
vendor/github.com/spacemonkeygo/monkit/v3/task.go
generated
vendored
Normal file
|
@ -0,0 +1,74 @@
|
|||
// Copyright (C) 2015 Space Monkey, Inc.
|
||||
//
|
||||
// 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 monkit
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type taskKey int
|
||||
|
||||
const taskGetFunc taskKey = 0
|
||||
|
||||
type taskSecretT struct{}
|
||||
|
||||
func (*taskSecretT) Value(key interface{}) interface{} { return nil }
|
||||
func (*taskSecretT) Done() <-chan struct{} { return nil }
|
||||
func (*taskSecretT) Err() error { return nil }
|
||||
func (*taskSecretT) Deadline() (time.Time, bool) {
|
||||
return time.Time{}, false
|
||||
}
|
||||
|
||||
// Func returns the Func associated with the Task
|
||||
func (f Task) Func() (out *Func) {
|
||||
// we're doing crazy things to make a function have methods that do other
|
||||
// things with internal state. basically, we have a secret argument we can
|
||||
// pass to the function that is only checked if ctx is taskSecret (
|
||||
// which it should never be) that controls what other behavior we want.
|
||||
// in this case, if arg[0] is taskGetFunc, then f will place the func in the
|
||||
// out location.
|
||||
// since someone can cast any function of this signature to a lazy task,
|
||||
// let's make sure we got roughly expected behavior and panic otherwise
|
||||
if f(&taskSecret, taskGetFunc, &out) != nil || out == nil {
|
||||
panic("Func() called on a non-Task function")
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func taskArgs(f *Func, args []interface{}) bool {
|
||||
// this function essentially does method dispatch for Tasks. returns true
|
||||
// if a method got dispatched and normal behavior should be aborted
|
||||
if len(args) != 2 {
|
||||
return false
|
||||
}
|
||||
val, ok := args[0].(taskKey)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
switch val {
|
||||
case taskGetFunc:
|
||||
*(args[1].(**Func)) = f
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// TaskNamed is like Task except you can choose the name of the associated
|
||||
// Func.
|
||||
//
|
||||
// You may also include any SeriesTags which should be included with the Task.
|
||||
func (s *Scope) TaskNamed(name string, tags ...SeriesTag) Task {
|
||||
return s.FuncNamed(name, tags...).Task
|
||||
}
|
96
vendor/github.com/spacemonkeygo/monkit/v3/timer.go
generated
vendored
Normal file
96
vendor/github.com/spacemonkeygo/monkit/v3/timer.go
generated
vendored
Normal file
|
@ -0,0 +1,96 @@
|
|||
// Copyright (C) 2016 Space Monkey, Inc.
|
||||
//
|
||||
// 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 monkit
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/spacemonkeygo/monkit/v3/monotime"
|
||||
)
|
||||
|
||||
// Timer is a threadsafe convenience wrapper around a DurationDist. You should
|
||||
// construct with NewTimer(), though the expected usage is from a Scope like
|
||||
// so:
|
||||
//
|
||||
// var mon = monkit.Package()
|
||||
//
|
||||
// func MyFunc() {
|
||||
// ...
|
||||
// timer := mon.Timer("event")
|
||||
// // perform event
|
||||
// timer.Stop()
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
// Timers implement StatSource.
|
||||
type Timer struct {
|
||||
mtx sync.Mutex
|
||||
times *DurationDist
|
||||
}
|
||||
|
||||
// NewTimer constructs a new Timer.
|
||||
func NewTimer(key SeriesKey) *Timer {
|
||||
return &Timer{times: NewDurationDist(key)}
|
||||
}
|
||||
|
||||
// Start constructs a RunningTimer
|
||||
func (t *Timer) Start() *RunningTimer {
|
||||
return &RunningTimer{
|
||||
start: monotime.Now(),
|
||||
t: t}
|
||||
}
|
||||
|
||||
// RunningTimer should be constructed from a Timer.
|
||||
type RunningTimer struct {
|
||||
start time.Time
|
||||
t *Timer
|
||||
stopped bool
|
||||
}
|
||||
|
||||
// Elapsed just returns the amount of time since the timer started
|
||||
func (r *RunningTimer) Elapsed() time.Duration {
|
||||
return time.Since(r.start)
|
||||
}
|
||||
|
||||
// Stop stops the timer, adds the duration to the statistics information, and
|
||||
// returns the elapsed time.
|
||||
func (r *RunningTimer) Stop() time.Duration {
|
||||
elapsed := r.Elapsed()
|
||||
r.t.mtx.Lock()
|
||||
if !r.stopped {
|
||||
r.t.times.Insert(elapsed)
|
||||
r.stopped = true
|
||||
}
|
||||
r.t.mtx.Unlock()
|
||||
return elapsed
|
||||
}
|
||||
|
||||
// Values returns the main timer values
|
||||
func (t *Timer) Values() *DurationDist {
|
||||
t.mtx.Lock()
|
||||
rv := t.times.Copy()
|
||||
t.mtx.Unlock()
|
||||
return rv
|
||||
}
|
||||
|
||||
// Stats implements the StatSource interface
|
||||
func (t *Timer) Stats(cb func(key SeriesKey, field string, val float64)) {
|
||||
t.mtx.Lock()
|
||||
times := t.times.Copy()
|
||||
t.mtx.Unlock()
|
||||
|
||||
times.Stats(cb)
|
||||
}
|
136
vendor/github.com/spacemonkeygo/monkit/v3/trace.go
generated
vendored
Normal file
136
vendor/github.com/spacemonkeygo/monkit/v3/trace.go
generated
vendored
Normal file
|
@ -0,0 +1,136 @@
|
|||
// Copyright (C) 2015 Space Monkey, Inc.
|
||||
//
|
||||
// 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 monkit
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SpanObserver is the interface plugins must implement if they want to observe
|
||||
// all spans on a given trace as they happen.
|
||||
type SpanObserver interface {
|
||||
// Start is called when a Span starts
|
||||
Start(s *Span)
|
||||
|
||||
// Finish is called when a Span finishes, along with an error if any, whether
|
||||
// or not it panicked, and what time it finished.
|
||||
Finish(s *Span, err error, panicked bool, finish time.Time)
|
||||
}
|
||||
|
||||
// Trace represents a 'trace' of execution. A 'trace' is the collection of all
|
||||
// of the 'spans' kicked off from the same root execution context. A trace is
|
||||
// a concurrency-supporting analog of a stack trace, where a span is somewhat
|
||||
// like a stack frame.
|
||||
type Trace struct {
|
||||
// sync/atomic things
|
||||
spanCount int64
|
||||
spanObservers *spanObserverTuple
|
||||
|
||||
// immutable things from construction
|
||||
id int64
|
||||
|
||||
// protected by mtx
|
||||
mtx sync.Mutex
|
||||
vals map[interface{}]interface{}
|
||||
}
|
||||
|
||||
// NewTrace creates a new Trace.
|
||||
func NewTrace(id int64) *Trace {
|
||||
return &Trace{id: id}
|
||||
}
|
||||
|
||||
func (t *Trace) getObserver() SpanCtxObserver {
|
||||
observers := loadSpanObserverTuple(&t.spanObservers)
|
||||
if observers == nil {
|
||||
return nil
|
||||
}
|
||||
if loadSpanObserverTuple(&observers.cdr) == nil {
|
||||
return observers.car
|
||||
}
|
||||
return observers
|
||||
}
|
||||
|
||||
// ObserveSpans lets you register a SpanObserver for all future Spans on the
|
||||
// Trace. The returned cancel method will remove your observer from the trace.
|
||||
func (t *Trace) ObserveSpans(observer SpanObserver) (cancel func()) {
|
||||
return t.ObserveSpansCtx(spanObserverToSpanCtxObserver{observer: observer})
|
||||
}
|
||||
|
||||
// ObserveSpansCtx lets you register a SpanCtxObserver for all future Spans on the
|
||||
// Trace. The returned cancel method will remove your observer from the trace.
|
||||
func (t *Trace) ObserveSpansCtx(observer SpanCtxObserver) (cancel func()) {
|
||||
for {
|
||||
existing := loadSpanObserverTuple(&t.spanObservers)
|
||||
ref := &spanObserverTuple{car: observer, cdr: existing}
|
||||
if compareAndSwapSpanObserverTuple(&t.spanObservers, existing, ref) {
|
||||
return func() { t.removeObserver(ref) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Trace) removeObserver(ref *spanObserverTuple) {
|
||||
t.mtx.Lock()
|
||||
defer t.mtx.Unlock()
|
||||
for {
|
||||
if removeObserverFrom(&t.spanObservers, ref) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func removeObserverFrom(parent **spanObserverTuple, ref *spanObserverTuple) (
|
||||
success bool) {
|
||||
existing := loadSpanObserverTuple(parent)
|
||||
if existing == nil {
|
||||
return true
|
||||
}
|
||||
if existing != ref {
|
||||
return removeObserverFrom(&existing.cdr, ref)
|
||||
}
|
||||
return compareAndSwapSpanObserverTuple(parent, existing,
|
||||
loadSpanObserverTuple(&existing.cdr))
|
||||
}
|
||||
|
||||
// Id returns the id of the Trace
|
||||
func (t *Trace) Id() int64 { return t.id }
|
||||
|
||||
// Get returns a value associated with a key on a trace. See Set.
|
||||
func (t *Trace) Get(key interface{}) (val interface{}) {
|
||||
t.mtx.Lock()
|
||||
if t.vals != nil {
|
||||
val = t.vals[key]
|
||||
}
|
||||
t.mtx.Unlock()
|
||||
return val
|
||||
}
|
||||
|
||||
// Set sets a value associated with a key on a trace. See Get.
|
||||
func (t *Trace) Set(key, val interface{}) {
|
||||
t.mtx.Lock()
|
||||
if t.vals == nil {
|
||||
t.vals = map[interface{}]interface{}{key: val}
|
||||
} else {
|
||||
t.vals[key] = val
|
||||
}
|
||||
t.mtx.Unlock()
|
||||
}
|
||||
|
||||
func (t *Trace) incrementSpans() { atomic.AddInt64(&t.spanCount, 1) }
|
||||
func (t *Trace) decrementSpans() { atomic.AddInt64(&t.spanCount, -1) }
|
||||
|
||||
// Spans returns the number of spans currently associated with the Trace.
|
||||
func (t *Trace) Spans() int64 { return atomic.LoadInt64(&t.spanCount) }
|
204
vendor/github.com/spacemonkeygo/monkit/v3/val.go
generated
vendored
Normal file
204
vendor/github.com/spacemonkeygo/monkit/v3/val.go
generated
vendored
Normal file
|
@ -0,0 +1,204 @@
|
|||
// Copyright (C) 2015 Space Monkey, Inc.
|
||||
//
|
||||
// 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 monkit
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// IntVal is a convenience wrapper around an IntDist. Constructed using
|
||||
// NewIntVal, though its expected usage is like:
|
||||
//
|
||||
// var mon = monkit.Package()
|
||||
//
|
||||
// func MyFunc() {
|
||||
// ...
|
||||
// mon.IntVal("size").Observe(val)
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
type IntVal struct {
|
||||
mtx sync.Mutex
|
||||
dist IntDist
|
||||
}
|
||||
|
||||
// NewIntVal creates an IntVal
|
||||
func NewIntVal(key SeriesKey) (v *IntVal) {
|
||||
v = &IntVal{}
|
||||
initIntDist(&v.dist, key)
|
||||
return v
|
||||
}
|
||||
|
||||
// Observe observes an integer value
|
||||
func (v *IntVal) Observe(val int64) {
|
||||
v.mtx.Lock()
|
||||
v.dist.Insert(val)
|
||||
v.mtx.Unlock()
|
||||
}
|
||||
|
||||
// Stats implements the StatSource interface.
|
||||
func (v *IntVal) Stats(cb func(key SeriesKey, field string, val float64)) {
|
||||
v.mtx.Lock()
|
||||
vd := v.dist.Copy()
|
||||
v.mtx.Unlock()
|
||||
|
||||
vd.Stats(cb)
|
||||
}
|
||||
|
||||
// Quantile returns an estimate of the requested quantile of observed values.
|
||||
// 0 <= quantile <= 1
|
||||
func (v *IntVal) Quantile(quantile float64) (rv int64) {
|
||||
v.mtx.Lock()
|
||||
rv = v.dist.Query(quantile)
|
||||
v.mtx.Unlock()
|
||||
return rv
|
||||
}
|
||||
|
||||
// FloatVal is a convenience wrapper around an FloatDist. Constructed using
|
||||
// NewFloatVal, though its expected usage is like:
|
||||
//
|
||||
// var mon = monkit.Package()
|
||||
//
|
||||
// func MyFunc() {
|
||||
// ...
|
||||
// mon.FloatVal("size").Observe(val)
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
type FloatVal struct {
|
||||
mtx sync.Mutex
|
||||
dist FloatDist
|
||||
}
|
||||
|
||||
// NewFloatVal creates a FloatVal
|
||||
func NewFloatVal(key SeriesKey) (v *FloatVal) {
|
||||
v = &FloatVal{}
|
||||
initFloatDist(&v.dist, key)
|
||||
return v
|
||||
}
|
||||
|
||||
// Observe observes an floating point value
|
||||
func (v *FloatVal) Observe(val float64) {
|
||||
v.mtx.Lock()
|
||||
v.dist.Insert(val)
|
||||
v.mtx.Unlock()
|
||||
}
|
||||
|
||||
// Stats implements the StatSource interface.
|
||||
func (v *FloatVal) Stats(cb func(key SeriesKey, field string, val float64)) {
|
||||
v.mtx.Lock()
|
||||
vd := v.dist.Copy()
|
||||
v.mtx.Unlock()
|
||||
|
||||
vd.Stats(cb)
|
||||
}
|
||||
|
||||
// Quantile returns an estimate of the requested quantile of observed values.
|
||||
// 0 <= quantile <= 1
|
||||
func (v *FloatVal) Quantile(quantile float64) (rv float64) {
|
||||
v.mtx.Lock()
|
||||
rv = v.dist.Query(quantile)
|
||||
v.mtx.Unlock()
|
||||
return rv
|
||||
}
|
||||
|
||||
// BoolVal keeps statistics about boolean values. It keeps the number of trues,
|
||||
// number of falses, and the disposition (number of trues minus number of
|
||||
// falses). Constructed using NewBoolVal, though its expected usage is like:
|
||||
//
|
||||
// var mon = monkit.Package()
|
||||
//
|
||||
// func MyFunc() {
|
||||
// ...
|
||||
// mon.BoolVal("flipped").Observe(bool)
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
type BoolVal struct {
|
||||
trues int64
|
||||
falses int64
|
||||
recent int32
|
||||
key SeriesKey
|
||||
}
|
||||
|
||||
// NewBoolVal creates a BoolVal
|
||||
func NewBoolVal(key SeriesKey) *BoolVal {
|
||||
return &BoolVal{key: key}
|
||||
}
|
||||
|
||||
// Observe observes a boolean value
|
||||
func (v *BoolVal) Observe(val bool) {
|
||||
if val {
|
||||
atomic.AddInt64(&v.trues, 1)
|
||||
atomic.StoreInt32(&v.recent, 1)
|
||||
} else {
|
||||
atomic.AddInt64(&v.falses, 1)
|
||||
atomic.StoreInt32(&v.recent, 0)
|
||||
}
|
||||
}
|
||||
|
||||
// Stats implements the StatSource interface.
|
||||
func (v *BoolVal) Stats(cb func(key SeriesKey, field string, val float64)) {
|
||||
trues := atomic.LoadInt64(&v.trues)
|
||||
falses := atomic.LoadInt64(&v.falses)
|
||||
recent := atomic.LoadInt32(&v.recent)
|
||||
cb(v.key, "disposition", float64(trues-falses))
|
||||
cb(v.key, "false", float64(falses))
|
||||
cb(v.key, "recent", float64(recent))
|
||||
cb(v.key, "true", float64(trues))
|
||||
}
|
||||
|
||||
// StructVal keeps track of a structure of data. Constructed using
|
||||
// NewStructVal, though its expected usage is like:
|
||||
//
|
||||
// var mon = monkit.Package()
|
||||
//
|
||||
// func MyFunc() {
|
||||
// ...
|
||||
// mon.StructVal("stats").Observe(stats)
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
type StructVal struct {
|
||||
mtx sync.Mutex
|
||||
recent interface{}
|
||||
key SeriesKey
|
||||
}
|
||||
|
||||
// NewStructVal creates a StructVal
|
||||
func NewStructVal(key SeriesKey) *StructVal {
|
||||
return &StructVal{key: key}
|
||||
}
|
||||
|
||||
// Observe observes a struct value. Only the fields convertable to float64 will
|
||||
// be monitored. A reference to the most recently called Observe value is kept
|
||||
// for reading when Stats is called.
|
||||
func (v *StructVal) Observe(val interface{}) {
|
||||
v.mtx.Lock()
|
||||
v.recent = val
|
||||
v.mtx.Unlock()
|
||||
}
|
||||
|
||||
// Stats implements the StatSource interface.
|
||||
func (v *StructVal) Stats(cb func(key SeriesKey, field string, val float64)) {
|
||||
v.mtx.Lock()
|
||||
recent := v.recent
|
||||
v.mtx.Unlock()
|
||||
|
||||
if recent != nil {
|
||||
StatSourceFromStruct(v.key, recent).Stats(cb)
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue