Tardigrade Backend: Dependencies

This commit is contained in:
Caleb Case 2020-05-11 14:57:46 -04:00 committed by Nick Craig-Wood
parent 962fbc8257
commit 03b629064a
544 changed files with 86690 additions and 2 deletions

6
vendor/github.com/spacemonkeygo/errors/.travis.yml generated vendored Normal file
View file

@ -0,0 +1,6 @@
language: go
go:
- 1.7
- 1.8
- tip

191
vendor/github.com/spacemonkeygo/errors/LICENSE generated vendored Normal file
View 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.

19
vendor/github.com/spacemonkeygo/errors/README.md generated vendored Normal file
View file

@ -0,0 +1,19 @@
# errors [![Build Status](https://api.travis-ci.org/spacemonkeygo/errors.svg?branch=master)](https://travis-ci.org/spacemonkeygo/errors)
Please see http://godoc.org/github.com/spacemonkeygo/errors for info
### License
Copyright (C) 2014 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.

24
vendor/github.com/spacemonkeygo/errors/config.go generated vendored Normal file
View file

@ -0,0 +1,24 @@
// Copyright (C) 2014 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 errors
// Config is a configuration struct meant to be used with
// github.com/spacemonkeygo/flagfile/utils.Setup
// but can be set independently.
var Config = struct {
Stacklogsize int `default:"4096" usage:"the max stack trace byte length to log"`
}{
Stacklogsize: 4096,
}

30
vendor/github.com/spacemonkeygo/errors/ctx17.go generated vendored Normal file
View file

@ -0,0 +1,30 @@
// 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.
// +build go1.7
// TODO: remove this build restriction once appengine supports 1.7
// +build !appengine
package errors
import (
"context"
)
var (
contextCanceled = context.Canceled
contextDeadlineExceeded = context.DeadlineExceeded
)

32
vendor/github.com/spacemonkeygo/errors/data_keys.go generated vendored Normal file
View file

@ -0,0 +1,32 @@
// Copyright (C) 2014 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 errors
import (
"sync/atomic"
)
var (
lastId int32 = 0
)
// DataKey's job is to make sure that keys in each error instances namespace
// are lexically scoped, thus helping developers not step on each others' toes
// between large packages. You can only store data on an error using a DataKey,
// and you can only make DataKeys with GenSym().
type DataKey struct{ id int32 }
// GenSym generates a brand new, never-before-seen DataKey
func GenSym() DataKey { return DataKey{id: atomic.AddInt32(&lastId, 1)} }

205
vendor/github.com/spacemonkeygo/errors/doc.go generated vendored Normal file
View file

@ -0,0 +1,205 @@
// Copyright (C) 2014 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 errors is a flexible error support library for Go
Motivation
Go's standard library is intentionally sparse on providing error utilities, and
developers coming from other programming languages may miss some features they
took for granted [1]. This package is an attempt at providing those features in
an idiomatic Go way.
The main features this package provides (in addition to miscellaneous
utilities) are:
* Error hierarchies
* Stack traces
* Arbitrary error values
Error hierarchies
While Go has very deliberately not implemented class hierarchies, a quick
perusal of Go's net and os packages should indicate that sometimes error
hierarchies are useful. Go programmers should be familiar with the net.Error
interface (and the types that fulfill it) as well as the os helper functions
such as os.IsNotExist, os.IsPermission, etc.
Unfortunately, to implement something similar, a developer will have to
implement a struct that matches the error interface as well as any testing
methods or any more detailed interfaces they may choose to export. It's not
hard, but it is friction, and developers tend to use fmt.Errorf instead due
to ease of use, thus missing out on useful features that functions like
os.IsNotExist and friends provide.
The errors package provides reusable components for building similar
features while reducing friction as much as possible. With the errors package,
the os error handling routines can be mimicked as follows:
package osmimic
import (
"github.com/spacemonkeygo/errors"
)
var (
OSError = errors.NewClass("OS Error")
NotExist = OSError.NewClass("Not Exist")
)
func Open(path string) (*File, error) {
// actually do something here
return nil, NotExist.New("path %#v doesn't exist", path)
}
func MyMethod() error {
fh, err := Open(mypath)
if err != nil {
if NotExist.Contains(err) {
// file doesn't exist, do stuff
}
return err
}
// do stuff
}
Stack traces
It doesn't take long during Go development before you may find yourself
wondering where an error came from. In other languages, as soon as an error is
raised, a stack trace is captured and is displayed as part of the language's
error handling. Go error types are simply basic values and no such magic
happens to tell you what line or what stack an error came from.
The errors package fixes this by optionally (but by default) capturing the
stack trace as part of your error. This behavior can be turned off and on for
specific error classes and comes in two flavors. You can have the stack trace
be appended to the error's Error() message, or you can have the stack trace
be logged immediately, every time an error of that type is instantiated.
Every error and error class supports hierarchical settings, in the sense that
if a setting was not explicitly set on that error or error class, setting
resolution traverses the error class hierarchy until it finds a valid setting,
or returns the default.
See CaptureStack()/NoCaptureStack() and LogOnCreation()/NoLogOnCreation() for
how to control this feature.
Arbitrary error values
These hierarchical settings (for whether or not errors captured or logged stack
traces) were so useful, we generalized the system to allow users to extend
the errors system with their own values. A user can tag a specific error with
some value given a statically defined key, or tag a whole error class subtree.
Arbitrary error values can easily handle situtations like net.Error's
Temporary() field, where some errors are temporary and others aren't. This can
be mimicked as follows:
package netmimic
import (
"github.com/spacemonkeygo/errors"
)
var (
NetError = errors.NewClass("Net Error")
OpError = NetError.NewClass("Op Error")
tempErrorKey = errors.GenSym()
)
func SetIsTemporary() errors.ErrorOption {
return errors.SetData(tempErrorKey, true)
}
func IsTemporary(err error) bool {
v, ok := errors.GetData(err, tempErrorKey).(bool)
if !ok {
return false
}
return v
}
func NetworkOp() error {
// actually do something here
return OpError.NewWith("failed operation", SetIsTemporary())
}
func Example() error {
for {
err := NetworkOp()
if err != nil {
if IsTemporary(err) {
// probably should do exponential backoff
continue
}
return err
}
}
}
HTTP handling
Another great example of arbitrary error value functionality is the errhttp
subpackage. See the errhttp source for more examples of how to use
SetData/GetData.
The errhttp package really helped clean up our error code. Take a look to
see if it can help your error handling with HTTP stacks too.
http://godoc.org/github.com/spacemonkeygo/errors/errhttp
Exit recording
So you have stack traces, which tells you how the error was generated, but
perhaps you're interested in keeping track of how the error was handled?
Every time you call errors.Record(err), it adds the current line information
to the error's output. As an example:
func MyFunction() error {
err := Something()
if err != nil {
if IsTemporary(err) {
// manage the temporary error
return errors.Record(err)
} else {
// manage the permanent error
return errors.Record(err)
}
}
}
errors.Record will help you keep track of which error handling branch your
code took.
ErrorGroup
There's a few different types of ErrorGroup utilities in this package, but they
all work the same way. Make sure to check out the ErrorGroup example.
CatchPanic
CatchPanic helps you easily manage functions that you think might panic, and
instead return errors. CatchPanic works by taking a pointer to your named error
return value. Check out the CatchPanic example for more.
Footnotes
[1] This errors package started while porting a large Python codebase to Go.
https://www.spacemonkey.com/blog/posts/go-space-monkey
*/
package errors

648
vendor/github.com/spacemonkeygo/errors/errors.go generated vendored Normal file
View file

@ -0,0 +1,648 @@
// Copyright (C) 2014 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 errors
import (
"errors"
"fmt"
"io"
"net"
"os"
"path/filepath"
"runtime"
"strings"
)
var (
logOnCreation = GenSym()
captureStack = GenSym()
disableInheritance = GenSym()
)
// ErrorClass is the basic hierarchical error type. An ErrorClass generates
// actual errors, but the error class controls properties of the errors it
// generates, such as where those errors are in the hierarchy, whether or not
// they capture the stack on instantiation, and so forth.
type ErrorClass struct {
parent *ErrorClass
name string
data map[DataKey]interface{}
}
var (
// HierarchicalError is the base class for all hierarchical errors generated
// through this class.
HierarchicalError = &ErrorClass{
parent: nil,
name: "Error",
data: map[DataKey]interface{}{captureStack: true}}
// SystemError is the base error class for errors not generated through this
// errors library. It is not expected that anyone would ever generate new
// errors from a SystemError type or make subclasses.
SystemError = &ErrorClass{
parent: nil,
name: "System Error",
data: map[DataKey]interface{}{}}
)
// An ErrorOption is something that controls behavior of specific error
// instances. They can be set on ErrorClasses or errors individually.
type ErrorOption func(map[DataKey]interface{})
// SetData will take the given value and store it with the error or error class
// and its descendents associated with the given DataKey. Be sure to check out
// the example. value can be nil to disable values for subhierarchies.
func SetData(key DataKey, value interface{}) ErrorOption {
return func(m map[DataKey]interface{}) {
m[key] = value
}
}
// LogOnCreation tells the error class and its descendents to log the stack
// whenever an error of this class is created.
func LogOnCreation() ErrorOption {
return SetData(logOnCreation, true)
}
// CaptureStack tells the error class and its descendents to capture the stack
// whenever an error of this class is created, and output it as part of the
// error's Error() method. This is the default.
func CaptureStack() ErrorOption {
return SetData(captureStack, true)
}
// NoLogOnCreation is the opposite of LogOnCreation and applies to the error,
// class, and its descendents. This is the default.
func NoLogOnCreation() ErrorOption {
return SetData(logOnCreation, false)
}
// NoCaptureStack is the opposite of CaptureStack and applies to the error,
// class, and its descendents.
func NoCaptureStack() ErrorOption {
return SetData(captureStack, false)
}
// If DisableInheritance is provided, the error or error class will belong to
// its ancestors, but will not inherit their settings and options. Use with
// caution, and may disappear in future releases.
func DisableInheritance() ErrorOption {
return SetData(disableInheritance, true)
}
func boolWrapper(val interface{}, default_value bool) bool {
rv, ok := val.(bool)
if ok {
return rv
}
return default_value
}
// NewClass creates an error class with the provided name and options. Classes
// generated from this method and not *ErrorClass.NewClass will descend from
// the root HierarchicalError base class.
func NewClass(name string, options ...ErrorOption) *ErrorClass {
return HierarchicalError.NewClass(name, options...)
}
// New is for compatibility with the default Go errors package. It simply
// creates an error from the HierarchicalError root class.
func New(text string) error {
// NewWith doesn't take a format string, even though we have no options.
return HierarchicalError.NewWith(text)
}
// NewClass creates an error class with the provided name and options. The new
// class will descend from the receiver.
func (parent *ErrorClass) NewClass(name string,
options ...ErrorOption) *ErrorClass {
ec := &ErrorClass{
parent: parent,
name: name,
data: make(map[DataKey]interface{})}
for _, option := range options {
option(ec.data)
}
if !boolWrapper(ec.data[disableInheritance], false) {
// hoist options for speed
for key, val := range parent.data {
_, exists := ec.data[key]
if !exists {
ec.data[key] = val
}
}
return ec
} else {
delete(ec.data, disableInheritance)
}
return ec
}
// MustAddData allows adding data key value pairs to error classes after they
// are created. This is useful for allowing external packages add namespaced
// values to errors defined outside of their package. It will panic if the
// key is already set in the error class.
func (e *ErrorClass) MustAddData(key DataKey, value interface{}) {
if _, ex := e.data[key]; ex {
panic("key already exists")
}
e.data[key] = value
}
// GetData will return any data set on the error class for the given key. It
// returns nil if there is no data set for that key.
func (e *ErrorClass) GetData(key DataKey) interface{} {
return e.data[key]
}
// Parent returns this error class' direct ancestor.
func (e *ErrorClass) Parent() *ErrorClass {
return e.parent
}
// String returns this error class' name
func (e *ErrorClass) String() string {
if e == nil {
return "nil"
}
return e.name
}
// Is returns true if the receiver class is or is a descendent of parent.
func (e *ErrorClass) Is(parent *ErrorClass) bool {
for check := e; check != nil; check = check.parent {
if check == parent {
return true
}
}
return false
}
// frame logs the pc at some point during execution.
type frame struct {
pc uintptr
}
// String returns a human readable form of the frame.
func (e frame) String() string {
if e.pc == 0 {
return "unknown.unknown:0"
}
f := runtime.FuncForPC(e.pc)
if f == nil {
return "unknown.unknown:0"
}
file, line := f.FileLine(e.pc)
return fmt.Sprintf("%s:%s:%d", f.Name(), filepath.Base(file), line)
}
// callerState records the pc into an frame for two callers up.
func callerState(depth int) frame {
pc, _, _, ok := runtime.Caller(depth)
if !ok {
return frame{pc: 0}
}
return frame{pc: pc}
}
// record will record the pc at the given depth into the error if it is
// capable of recording it.
func record(err error, depth int) error {
if err == nil {
return nil
}
cast, ok := err.(*Error)
if !ok {
return err
}
cast.exits = append(cast.exits, callerState(depth))
return cast
}
// Record will record the current pc on the given error if possible, adding
// to the error's recorded exits list. Returns the given error argument.
func Record(err error) error {
return record(err, 3)
}
// RecordBefore will record the pc depth frames above the current stack frame
// on the given error if possible, adding to the error's recorded exits list.
// Record(err) is equivalent to RecordBefore(err, 0). Returns the given error
// argument.
func RecordBefore(err error, depth int) error {
return record(err, 3+depth)
}
// Error is the type that represents a specific error instance. It is not
// expected that you will work with *Error classes directly. Instead, you
// should use the 'error' interface and errors package methods that operate
// on errors instances.
type Error struct {
err error
class *ErrorClass
stacks [][]frame
exits []frame
data map[DataKey]interface{}
}
// GetData returns the value associated with the given DataKey on this error
// or any of its ancestors. Please see the example for SetData
func (e *Error) GetData(key DataKey) interface{} {
if e.data != nil {
val, ok := e.data[key]
if ok {
return val
}
if boolWrapper(e.data[disableInheritance], false) {
return nil
}
}
return e.class.data[key]
}
// GetData returns the value associated with the given DataKey on this error
// or any of its ancestors. Please see the example for SetData
func GetData(err error, key DataKey) interface{} {
cast, ok := err.(*Error)
if ok {
return cast.GetData(key)
}
return nil
}
func (e *ErrorClass) wrap(err error, classes []*ErrorClass,
options []ErrorOption) error {
if err == nil {
return nil
}
if ec, ok := err.(*Error); ok {
if ec.Is(e) {
if len(options) == 0 {
return ec
}
// if we have options, we have to wrap it cause we don't want to
// mutate the existing error.
} else {
for _, class := range classes {
if ec.Is(class) {
return err
}
}
}
}
rv := &Error{err: err, class: e}
if len(options) > 0 {
rv.data = make(map[DataKey]interface{})
for _, option := range options {
option(rv.data)
}
}
if boolWrapper(rv.GetData(captureStack), false) {
rv.stacks = [][]frame{getStack(3)}
}
if boolWrapper(rv.GetData(logOnCreation), false) {
LogWithStack(rv.Error())
}
return rv
}
func getStack(depth int) (stack []frame) {
var pcs [256]uintptr
amount := runtime.Callers(depth+1, pcs[:])
stack = make([]frame, amount)
for i := 0; i < amount; i++ {
stack[i] = frame{pcs[i]}
}
return stack
}
// AttachStack adds another stack to the current error's stack trace if it
// exists
func AttachStack(err error) {
if err == nil {
return
}
cast, ok := err.(*Error)
if !ok {
return
}
if len(cast.stacks) < 1 {
// only record stacks if this error was supposed to
return
}
cast.stacks = append(cast.stacks, getStack(2))
}
// WrapUnless wraps the given error in the receiver error class unless the
// error is already an instance of one of the provided error classes.
func (e *ErrorClass) WrapUnless(err error, classes ...*ErrorClass) error {
return e.wrap(err, classes, nil)
}
// Wrap wraps the given error in the receiver error class with the provided
// error-specific options.
func (e *ErrorClass) Wrap(err error, options ...ErrorOption) error {
return e.wrap(err, nil, options)
}
// New makes a new error type. It takes a format string.
func (e *ErrorClass) New(format string, args ...interface{}) error {
return e.wrap(fmt.Errorf(format, args...), nil, nil)
}
// NewWith makes a new error type with the provided error-specific options.
func (e *ErrorClass) NewWith(message string, options ...ErrorOption) error {
return e.wrap(errors.New(message), nil, options)
}
// Error conforms to the error interface. Error will return the backtrace if
// it was captured and any recorded exits.
func (e *Error) Error() string {
message := strings.TrimRight(e.err.Error(), "\n ")
if strings.Contains(message, "\n") {
message = fmt.Sprintf("%s:\n %s", e.class.String(),
strings.Replace(message, "\n", "\n ", -1))
} else {
message = fmt.Sprintf("%s: %s", e.class.String(), message)
}
if stack := e.Stack(); stack != "" {
message = fmt.Sprintf(
"%s\n\"%s\" backtrace:\n%s", message, e.class, stack)
}
if exits := e.Exits(); exits != "" {
message = fmt.Sprintf(
"%s\n\"%s\" exits:\n%s", message, e.class, exits)
}
return message
}
// Message returns just the error message without the backtrace or exits.
func (e *Error) Message() string {
message := strings.TrimRight(GetMessage(e.err), "\n ")
if strings.Contains(message, "\n") {
return fmt.Sprintf("%s:\n %s", e.class.String(),
strings.Replace(message, "\n", "\n ", -1))
}
return fmt.Sprintf("%s: %s", e.class.String(), message)
}
// WrappedErr returns the wrapped error, if the current error is simply
// wrapping some previously returned error or system error. You probably want
// the package-level WrappedErr
func (e *Error) WrappedErr() error {
return e.err
}
// WrappedErr returns the wrapped error, if the current error is simply
// wrapping some previously returned error or system error. If the error isn't
// hierarchical it is just returned.
func WrappedErr(err error) error {
cast, ok := err.(*Error)
if !ok {
return err
}
return cast.WrappedErr()
}
// Class will return the appropriate error class for the given error. You
// probably want the package-level GetClass.
func (e *Error) Class() *ErrorClass {
return e.class
}
// Name returns the name of the error: in this case the name of the class the
// error belongs to.
func (e *Error) Name() (string, bool) {
return e.class.name, true
}
// GetClass will return the appropriate error class for the given error.
// If the error is not nil, GetClass always returns a hierarchical error class,
// and even attempts to determine a class for common system error types.
func GetClass(err error) *ErrorClass {
if err == nil {
return nil
}
cast, ok := err.(*Error)
if !ok {
return findSystemErrorClass(err)
}
return cast.class
}
// Stack will return the stack associated with the error if one is found. You
// probably want the package-level GetStack.
func (e *Error) Stack() string {
if len(e.stacks) > 0 {
var frames []string
for _, stack := range e.stacks {
if frames == nil {
frames = make([]string, 0, len(stack))
} else {
frames = append(frames, "----- attached stack -----")
}
for _, f := range stack {
frames = append(frames, f.String())
}
}
return strings.Join(frames, "\n")
}
return ""
}
// GetStack will return the stack associated with the error if one is found.
func GetStack(err error) string {
if err == nil {
return ""
}
cast, ok := err.(*Error)
if !ok {
return ""
}
return cast.Stack()
}
// Exits will return the exits recorded on the error if any are found. You
// probably want the package-level GetExits.
func (e *Error) Exits() string {
if len(e.exits) > 0 {
exits := make([]string, len(e.exits))
for i, ex := range e.exits {
exits[i] = ex.String()
}
return strings.Join(exits, "\n")
}
return ""
}
// GetExits will return the exits recorded on the error if any are found.
func GetExits(err error) string {
if err == nil {
return ""
}
cast, ok := err.(*Error)
if !ok {
return ""
}
return cast.Exits()
}
// GetMessage returns just the error message without the backtrace or exits.
func GetMessage(err error) string {
if err == nil {
return ""
}
cast, ok := err.(*Error)
if !ok {
return err.Error()
}
return cast.Message()
}
// EquivalenceOption values control behavior of determining whether or not an
// error belongs to a specific class.
type EquivalenceOption int
const (
// If IncludeWrapped is used, wrapped errors are also used for determining
// class membership.
IncludeWrapped EquivalenceOption = 1
)
func combineEquivOpts(opts []EquivalenceOption) (rv EquivalenceOption) {
for _, opt := range opts {
rv |= opt
}
return rv
}
// Is returns whether or not an error belongs to a specific class. Typically
// you should use Contains instead.
func (e *Error) Is(ec *ErrorClass, opts ...EquivalenceOption) bool {
return ec.Contains(e, opts...)
}
// Contains returns whether or not the receiver error class contains the given
// error instance.
func (e *ErrorClass) Contains(err error, opts ...EquivalenceOption) bool {
if err == nil {
return false
}
cast, ok := err.(*Error)
if !ok {
return findSystemErrorClass(err).Is(e)
}
if cast.class.Is(e) {
return true
}
if combineEquivOpts(opts)&IncludeWrapped == 0 {
return false
}
return e.Contains(cast.err, opts...)
}
var (
// Useful error classes
NotImplementedError = NewClass("Not Implemented Error", LogOnCreation())
ProgrammerError = NewClass("Programmer Error", LogOnCreation())
PanicError = NewClass("Panic Error", LogOnCreation())
// The following SystemError descendants are provided such that the GetClass
// method has something to return for standard library error types not
// defined through this class.
//
// It is not expected that anyone would create instances of these classes.
//
// from os
SyscallError = SystemError.NewClass("Syscall Error")
// from syscall
ErrnoError = SystemError.NewClass("Errno Error")
// from net
NetworkError = SystemError.NewClass("Network Error")
UnknownNetworkError = NetworkError.NewClass("Unknown Network Error")
AddrError = NetworkError.NewClass("Addr Error")
InvalidAddrError = AddrError.NewClass("Invalid Addr Error")
NetOpError = NetworkError.NewClass("Network Op Error")
NetParseError = NetworkError.NewClass("Network Parse Error")
DNSError = NetworkError.NewClass("DNS Error")
DNSConfigError = DNSError.NewClass("DNS Config Error")
// from io
IOError = SystemError.NewClass("IO Error")
EOF = IOError.NewClass("EOF")
ClosedPipeError = IOError.NewClass("Closed Pipe Error")
NoProgressError = IOError.NewClass("No Progress Error")
ShortBufferError = IOError.NewClass("Short Buffer Error")
ShortWriteError = IOError.NewClass("Short Write Error")
UnexpectedEOFError = IOError.NewClass("Unexpected EOF Error")
// from context
ContextError = SystemError.NewClass("Context Error")
ContextCanceled = ContextError.NewClass("Canceled")
ContextTimeout = ContextError.NewClass("Timeout")
)
func findSystemErrorClass(err error) *ErrorClass {
switch err {
case io.EOF:
return EOF
case io.ErrUnexpectedEOF:
return UnexpectedEOFError
case io.ErrClosedPipe:
return ClosedPipeError
case io.ErrNoProgress:
return NoProgressError
case io.ErrShortBuffer:
return ShortBufferError
case io.ErrShortWrite:
return ShortWriteError
case contextCanceled:
return ContextCanceled
case contextDeadlineExceeded:
return ContextTimeout
default:
break
}
if isErrnoError(err) {
return ErrnoError
}
switch err.(type) {
case *os.SyscallError:
return SyscallError
case net.UnknownNetworkError:
return UnknownNetworkError
case *net.AddrError:
return AddrError
case net.InvalidAddrError:
return InvalidAddrError
case *net.OpError:
return NetOpError
case *net.ParseError:
return NetParseError
case *net.DNSError:
return DNSError
case *net.DNSConfigError:
return DNSConfigError
case net.Error:
return NetworkError
default:
return SystemError
}
}

26
vendor/github.com/spacemonkeygo/errors/syscall.go generated vendored Normal file
View file

@ -0,0 +1,26 @@
// Copyright (C) 2014 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 errors
import (
"syscall"
)
func isErrnoError(err error) bool {
_, ok := err.(syscall.Errno)
return ok
}

21
vendor/github.com/spacemonkeygo/errors/syscall_ae.go generated vendored Normal file
View file

@ -0,0 +1,21 @@
// Copyright (C) 2014 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 errors
func isErrnoError(err error) bool {
return false
}

155
vendor/github.com/spacemonkeygo/errors/utils.go generated vendored Normal file
View file

@ -0,0 +1,155 @@
// Copyright (C) 2014 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 errors
import (
"fmt"
"log"
"runtime"
"strings"
)
var (
// Change this method if you want errors to log somehow else
LogMethod = log.Printf
ErrorGroupError = NewClass("Error Group Error")
)
// LogWithStack will log the given messages with the current stack
func LogWithStack(messages ...interface{}) {
buf := make([]byte, Config.Stacklogsize)
buf = buf[:runtime.Stack(buf, false)]
LogMethod("%s\n%s", fmt.Sprintln(messages...), buf)
}
// CatchPanic can be used to catch panics and turn them into errors. See the
// example.
func CatchPanic(err_ref *error) {
r := recover()
if r == nil {
return
}
err, ok := r.(error)
if ok {
*err_ref = PanicError.Wrap(err)
return
}
*err_ref = PanicError.New("%v", r)
}
// ErrorGroup is a type for collecting errors from a bunch of independent
// tasks. ErrorGroups are not threadsafe. See the example for usage.
type ErrorGroup struct {
Errors []error
limit int
excess int
}
// NewErrorGroup makes a new ErrorGroup
func NewErrorGroup() *ErrorGroup { return &ErrorGroup{} }
// NewBoundedErrorGroup makes a new ErrorGroup that will not track more than
// limit errors. Once the limit is reached, the ErrorGroup will track
// additional errors as excess.
func NewBoundedErrorGroup(limit int) *ErrorGroup {
return &ErrorGroup{
limit: limit,
}
}
// Add is called with errors. nil errors are ignored.
func (e *ErrorGroup) Add(err error) {
if err == nil {
return
}
if e.limit > 0 && len(e.Errors) == e.limit {
e.excess++
} else {
e.Errors = append(e.Errors, err)
}
}
// Finalize will collate all the found errors. If no errors were found, it will
// return nil. If one error was found, it will be returned directly. Otherwise
// an ErrorGroupError will be returned.
func (e *ErrorGroup) Finalize() error {
if len(e.Errors) == 0 {
return nil
}
if len(e.Errors) == 1 && e.excess == 0 {
return e.Errors[0]
}
msgs := make([]string, 0, len(e.Errors))
for _, err := range e.Errors {
msgs = append(msgs, err.Error())
}
if e.excess > 0 {
msgs = append(msgs, fmt.Sprintf("... and %d more.", e.excess))
e.excess = 0
}
e.Errors = nil
return ErrorGroupError.New(strings.Join(msgs, "\n"))
}
// LoggingErrorGroup is similar to ErrorGroup except that instead of collecting
// all of the errors, it logs the errors immediately and just counts how many
// non-nil errors have been seen. See the ErrorGroup example for usage.
type LoggingErrorGroup struct {
name string
total int
failed int
}
// NewLoggingErrorGroup returns a new LoggingErrorGroup with the given name.
func NewLoggingErrorGroup(name string) *LoggingErrorGroup {
return &LoggingErrorGroup{name: name}
}
// Add will handle a given error. If the error is non-nil, total and failed
// are both incremented and the error is logged. If the error is nil, only
// total is incremented.
func (e *LoggingErrorGroup) Add(err error) {
e.total++
if err != nil {
LogMethod("%s: %s", e.name, err)
e.failed++
}
}
// Finalize returns no error if no failures were observed, otherwise it will
// return an ErrorGroupError with statistics about the observed errors.
func (e *LoggingErrorGroup) Finalize() (err error) {
if e.failed > 0 {
err = ErrorGroupError.New("%s: %d of %d failed.", e.name, e.failed,
e.total)
}
e.total = 0
e.failed = 0
return err
}
type Finalizer interface {
Finalize() error
}
// Finalize takes a group of ErrorGroups and joins them together into one error
func Finalize(finalizers ...Finalizer) error {
var errs ErrorGroup
for _, finalizer := range finalizers {
errs.Add(finalizer.Finalize())
}
return errs.Finalize()
}

26
vendor/github.com/spacemonkeygo/errors/xctx.go generated vendored Normal file
View file

@ -0,0 +1,26 @@
// 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.
// +build !go1.7
package errors
import (
"golang.org/x/net/context"
)
var (
contextCanceled = context.Canceled
contextDeadlineExceeded = context.DeadlineExceeded
)

1
vendor/github.com/spacemonkeygo/monkit/v3/AUTHORS generated vendored Normal file
View file

@ -0,0 +1 @@
Space Monkey, Inc.

191
vendor/github.com/spacemonkeygo/monkit/v3/LICENSE generated vendored Normal file
View 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
View file

@ -0,0 +1,637 @@
# ![monkit](https://raw.githubusercontent.com/spacemonkeygo/monkit/master/images/logo.png)
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:
![callgraph](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).
![trace](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 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:
![handlehttp](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
```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!
![trace](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!
## 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
View 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
View 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
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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))
}
}

View 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"
}

View 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
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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)
}
}
}

View file

@ -0,0 +1,7 @@
package monotime
import "time"
var initTime = time.Now()
func Now() time.Time { return initTime.Add(elapsed()) }

View file

@ -0,0 +1,7 @@
// +build !windows
package monotime
import "time"
func elapsed() time.Duration { return time.Since(initTime) }

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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)
}
}