mirror of https://github.com/dexidp/dex.git
149 changed files with 44922 additions and 2 deletions
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License |
||||
Version 2.0, January 2004 |
||||
http://www.apache.org/licenses/ |
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION |
||||
|
||||
1. Definitions. |
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, |
||||
and distribution as defined by Sections 1 through 9 of this document. |
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by |
||||
the copyright owner that is granting the License. |
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all |
||||
other entities that control, are controlled by, or are under common |
||||
control with that entity. For the purposes of this definition, |
||||
"control" means (i) the power, direct or indirect, to cause the |
||||
direction or management of such entity, whether by contract or |
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the |
||||
outstanding shares, or (iii) beneficial ownership of such entity. |
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity |
||||
exercising permissions granted by this License. |
||||
|
||||
"Source" form shall mean the preferred form for making modifications, |
||||
including but not limited to software source code, documentation |
||||
source, and configuration files. |
||||
|
||||
"Object" form shall mean any form resulting from mechanical |
||||
transformation or translation of a Source form, including but |
||||
not limited to compiled object code, generated documentation, |
||||
and conversions to other media types. |
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or |
||||
Object form, made available under the License, as indicated by a |
||||
copyright notice that is included in or attached to the work |
||||
(an example is provided in the Appendix below). |
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object |
||||
form, that is based on (or derived from) the Work and for which the |
||||
editorial revisions, annotations, elaborations, or other modifications |
||||
represent, as a whole, an original work of authorship. For the purposes |
||||
of this License, Derivative Works shall not include works that remain |
||||
separable from, or merely link (or bind by name) to the interfaces of, |
||||
the Work and Derivative Works thereof. |
||||
|
||||
"Contribution" shall mean any work of authorship, including |
||||
the original version of the Work and any modifications or additions |
||||
to that Work or Derivative Works thereof, that is intentionally |
||||
submitted to Licensor for inclusion in the Work by the copyright owner |
||||
or by an individual or Legal Entity authorized to submit on behalf of |
||||
the copyright owner. For the purposes of this definition, "submitted" |
||||
means any form of electronic, verbal, or written communication sent |
||||
to the Licensor or its representatives, including but not limited to |
||||
communication on electronic mailing lists, source code control systems, |
||||
and issue tracking systems that are managed by, or on behalf of, the |
||||
Licensor for the purpose of discussing and improving the Work, but |
||||
excluding communication that is conspicuously marked or otherwise |
||||
designated in writing by the copyright owner as "Not a Contribution." |
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity |
||||
on behalf of whom a Contribution has been received by Licensor and |
||||
subsequently incorporated within the Work. |
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of |
||||
this License, each Contributor hereby grants to You a perpetual, |
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
||||
copyright license to reproduce, prepare Derivative Works of, |
||||
publicly display, publicly perform, sublicense, and distribute the |
||||
Work and such Derivative Works in Source or Object form. |
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of |
||||
this License, each Contributor hereby grants to You a perpetual, |
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
||||
(except as stated in this section) patent license to make, have made, |
||||
use, offer to sell, sell, import, and otherwise transfer the Work, |
||||
where such license applies only to those patent claims licensable |
||||
by such Contributor that are necessarily infringed by their |
||||
Contribution(s) alone or by combination of their Contribution(s) |
||||
with the Work to which such Contribution(s) was submitted. If You |
||||
institute patent litigation against any entity (including a |
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work |
||||
or a Contribution incorporated within the Work constitutes direct |
||||
or contributory patent infringement, then any patent licenses |
||||
granted to You under this License for that Work shall terminate |
||||
as of the date such litigation is filed. |
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the |
||||
Work or Derivative Works thereof in any medium, with or without |
||||
modifications, and in Source or Object form, provided that You |
||||
meet the following conditions: |
||||
|
||||
(a) You must give any other recipients of the Work or |
||||
Derivative Works a copy of this License; and |
||||
|
||||
(b) You must cause any modified files to carry prominent notices |
||||
stating that You changed the files; and |
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works |
||||
that You distribute, all copyright, patent, trademark, and |
||||
attribution notices from the Source form of the Work, |
||||
excluding those notices that do not pertain to any part of |
||||
the Derivative Works; and |
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its |
||||
distribution, then any Derivative Works that You distribute must |
||||
include a readable copy of the attribution notices contained |
||||
within such NOTICE file, excluding those notices that do not |
||||
pertain to any part of the Derivative Works, in at least one |
||||
of the following places: within a NOTICE text file distributed |
||||
as part of the Derivative Works; within the Source form or |
||||
documentation, if provided along with the Derivative Works; or, |
||||
within a display generated by the Derivative Works, if and |
||||
wherever such third-party notices normally appear. The contents |
||||
of the NOTICE file are for informational purposes only and |
||||
do not modify the License. You may add Your own attribution |
||||
notices within Derivative Works that You distribute, alongside |
||||
or as an addendum to the NOTICE text from the Work, provided |
||||
that such additional attribution notices cannot be construed |
||||
as modifying the License. |
||||
|
||||
You may add Your own copyright statement to Your modifications and |
||||
may provide additional or different license terms and conditions |
||||
for use, reproduction, or distribution of Your modifications, or |
||||
for any such Derivative Works as a whole, provided Your use, |
||||
reproduction, and distribution of the Work otherwise complies with |
||||
the conditions stated in this License. |
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise, |
||||
any Contribution intentionally submitted for inclusion in the Work |
||||
by You to the Licensor shall be under the terms and conditions of |
||||
this License, without any additional terms or conditions. |
||||
Notwithstanding the above, nothing herein shall supersede or modify |
||||
the terms of any separate license agreement you may have executed |
||||
with Licensor regarding such Contributions. |
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade |
||||
names, trademarks, service marks, or product names of the Licensor, |
||||
except as required for reasonable and customary use in describing the |
||||
origin of the Work and reproducing the content of the NOTICE file. |
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or |
||||
agreed to in writing, Licensor provides the Work (and each |
||||
Contributor provides its Contributions) on an "AS IS" BASIS, |
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
||||
implied, including, without limitation, any warranties or conditions |
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A |
||||
PARTICULAR PURPOSE. You are solely responsible for determining the |
||||
appropriateness of using or redistributing the Work and assume any |
||||
risks associated with Your exercise of permissions under this License. |
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory, |
||||
whether in tort (including negligence), contract, or otherwise, |
||||
unless required by applicable law (such as deliberate and grossly |
||||
negligent acts) or agreed to in writing, shall any Contributor be |
||||
liable to You for damages, including any direct, indirect, special, |
||||
incidental, or consequential damages of any character arising as a |
||||
result of this License or out of the use or inability to use the |
||||
Work (including but not limited to damages for loss of goodwill, |
||||
work stoppage, computer failure or malfunction, or any and all |
||||
other commercial damages or losses), even if such Contributor |
||||
has been advised of the possibility of such damages. |
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing |
||||
the Work or Derivative Works thereof, You may choose to offer, |
||||
and charge a fee for, acceptance of support, warranty, indemnity, |
||||
or other liability obligations and/or rights consistent with this |
||||
License. However, in accepting such obligations, You may act only |
||||
on Your own behalf and on Your sole responsibility, not on behalf |
||||
of any other Contributor, and only if You agree to indemnify, |
||||
defend, and hold each Contributor harmless for any liability |
||||
incurred by, or claims asserted against, such Contributor by reason |
||||
of your accepting any such warranty or additional liability. |
||||
|
||||
END OF TERMS AND CONDITIONS |
||||
|
||||
APPENDIX: How to apply the Apache License to your work. |
||||
|
||||
To apply the Apache License to your work, attach the following |
||||
boilerplate notice, with the fields enclosed by brackets "[]" |
||||
replaced with your own identifying information. (Don't include |
||||
the brackets!) The text should be enclosed in the appropriate |
||||
comment syntax for the file format. We also recommend that a |
||||
file or class name and description of purpose be included on the |
||||
same "printed page" as the copyright notice for easier |
||||
identification within third-party archives. |
||||
|
||||
Copyright [yyyy] [name of copyright owner] |
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
you may not use this file except in compliance with the License. |
||||
You may obtain a copy of the License at |
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
||||
Unless required by applicable law or agreed to in writing, software |
||||
distributed under the License is distributed on an "AS IS" BASIS, |
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
See the License for the specific language governing permissions and |
||||
limitations under the License. |
||||
@ -0,0 +1,513 @@
|
||||
// Copyright 2014 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package metadata provides access to Google Compute Engine (GCE)
|
||||
// metadata and API service accounts.
|
||||
//
|
||||
// This package is a wrapper around the GCE metadata service,
|
||||
// as documented at https://developers.google.com/compute/docs/metadata.
|
||||
package metadata // import "cloud.google.com/go/compute/metadata"
|
||||
|
||||
import ( |
||||
"context" |
||||
"encoding/json" |
||||
"fmt" |
||||
"io/ioutil" |
||||
"net" |
||||
"net/http" |
||||
"net/url" |
||||
"os" |
||||
"runtime" |
||||
"strings" |
||||
"sync" |
||||
"time" |
||||
) |
||||
|
||||
const ( |
||||
// metadataIP is the documented metadata server IP address.
|
||||
metadataIP = "169.254.169.254" |
||||
|
||||
// metadataHostEnv is the environment variable specifying the
|
||||
// GCE metadata hostname. If empty, the default value of
|
||||
// metadataIP ("169.254.169.254") is used instead.
|
||||
// This is variable name is not defined by any spec, as far as
|
||||
// I know; it was made up for the Go package.
|
||||
metadataHostEnv = "GCE_METADATA_HOST" |
||||
|
||||
userAgent = "gcloud-golang/0.1" |
||||
) |
||||
|
||||
type cachedValue struct { |
||||
k string |
||||
trim bool |
||||
mu sync.Mutex |
||||
v string |
||||
} |
||||
|
||||
var ( |
||||
projID = &cachedValue{k: "project/project-id", trim: true} |
||||
projNum = &cachedValue{k: "project/numeric-project-id", trim: true} |
||||
instID = &cachedValue{k: "instance/id", trim: true} |
||||
) |
||||
|
||||
var ( |
||||
defaultClient = &Client{hc: &http.Client{ |
||||
Transport: &http.Transport{ |
||||
Dial: (&net.Dialer{ |
||||
Timeout: 2 * time.Second, |
||||
KeepAlive: 30 * time.Second, |
||||
}).Dial, |
||||
ResponseHeaderTimeout: 2 * time.Second, |
||||
}, |
||||
}} |
||||
subscribeClient = &Client{hc: &http.Client{ |
||||
Transport: &http.Transport{ |
||||
Dial: (&net.Dialer{ |
||||
Timeout: 2 * time.Second, |
||||
KeepAlive: 30 * time.Second, |
||||
}).Dial, |
||||
}, |
||||
}} |
||||
) |
||||
|
||||
// NotDefinedError is returned when requested metadata is not defined.
|
||||
//
|
||||
// The underlying string is the suffix after "/computeMetadata/v1/".
|
||||
//
|
||||
// This error is not returned if the value is defined to be the empty
|
||||
// string.
|
||||
type NotDefinedError string |
||||
|
||||
func (suffix NotDefinedError) Error() string { |
||||
return fmt.Sprintf("metadata: GCE metadata %q not defined", string(suffix)) |
||||
} |
||||
|
||||
func (c *cachedValue) get(cl *Client) (v string, err error) { |
||||
defer c.mu.Unlock() |
||||
c.mu.Lock() |
||||
if c.v != "" { |
||||
return c.v, nil |
||||
} |
||||
if c.trim { |
||||
v, err = cl.getTrimmed(c.k) |
||||
} else { |
||||
v, err = cl.Get(c.k) |
||||
} |
||||
if err == nil { |
||||
c.v = v |
||||
} |
||||
return |
||||
} |
||||
|
||||
var ( |
||||
onGCEOnce sync.Once |
||||
onGCE bool |
||||
) |
||||
|
||||
// OnGCE reports whether this process is running on Google Compute Engine.
|
||||
func OnGCE() bool { |
||||
onGCEOnce.Do(initOnGCE) |
||||
return onGCE |
||||
} |
||||
|
||||
func initOnGCE() { |
||||
onGCE = testOnGCE() |
||||
} |
||||
|
||||
func testOnGCE() bool { |
||||
// The user explicitly said they're on GCE, so trust them.
|
||||
if os.Getenv(metadataHostEnv) != "" { |
||||
return true |
||||
} |
||||
|
||||
ctx, cancel := context.WithCancel(context.Background()) |
||||
defer cancel() |
||||
|
||||
resc := make(chan bool, 2) |
||||
|
||||
// Try two strategies in parallel.
|
||||
// See https://github.com/googleapis/google-cloud-go/issues/194
|
||||
go func() { |
||||
req, _ := http.NewRequest("GET", "http://"+metadataIP, nil) |
||||
req.Header.Set("User-Agent", userAgent) |
||||
res, err := defaultClient.hc.Do(req.WithContext(ctx)) |
||||
if err != nil { |
||||
resc <- false |
||||
return |
||||
} |
||||
defer res.Body.Close() |
||||
resc <- res.Header.Get("Metadata-Flavor") == "Google" |
||||
}() |
||||
|
||||
go func() { |
||||
addrs, err := net.LookupHost("metadata.google.internal") |
||||
if err != nil || len(addrs) == 0 { |
||||
resc <- false |
||||
return |
||||
} |
||||
resc <- strsContains(addrs, metadataIP) |
||||
}() |
||||
|
||||
tryHarder := systemInfoSuggestsGCE() |
||||
if tryHarder { |
||||
res := <-resc |
||||
if res { |
||||
// The first strategy succeeded, so let's use it.
|
||||
return true |
||||
} |
||||
// Wait for either the DNS or metadata server probe to
|
||||
// contradict the other one and say we are running on
|
||||
// GCE. Give it a lot of time to do so, since the system
|
||||
// info already suggests we're running on a GCE BIOS.
|
||||
timer := time.NewTimer(5 * time.Second) |
||||
defer timer.Stop() |
||||
select { |
||||
case res = <-resc: |
||||
return res |
||||
case <-timer.C: |
||||
// Too slow. Who knows what this system is.
|
||||
return false |
||||
} |
||||
} |
||||
|
||||
// There's no hint from the system info that we're running on
|
||||
// GCE, so use the first probe's result as truth, whether it's
|
||||
// true or false. The goal here is to optimize for speed for
|
||||
// users who are NOT running on GCE. We can't assume that
|
||||
// either a DNS lookup or an HTTP request to a blackholed IP
|
||||
// address is fast. Worst case this should return when the
|
||||
// metaClient's Transport.ResponseHeaderTimeout or
|
||||
// Transport.Dial.Timeout fires (in two seconds).
|
||||
return <-resc |
||||
} |
||||
|
||||
// systemInfoSuggestsGCE reports whether the local system (without
|
||||
// doing network requests) suggests that we're running on GCE. If this
|
||||
// returns true, testOnGCE tries a bit harder to reach its metadata
|
||||
// server.
|
||||
func systemInfoSuggestsGCE() bool { |
||||
if runtime.GOOS != "linux" { |
||||
// We don't have any non-Linux clues available, at least yet.
|
||||
return false |
||||
} |
||||
slurp, _ := ioutil.ReadFile("/sys/class/dmi/id/product_name") |
||||
name := strings.TrimSpace(string(slurp)) |
||||
return name == "Google" || name == "Google Compute Engine" |
||||
} |
||||
|
||||
// Subscribe calls Client.Subscribe on a client designed for subscribing (one with no
|
||||
// ResponseHeaderTimeout).
|
||||
func Subscribe(suffix string, fn func(v string, ok bool) error) error { |
||||
return subscribeClient.Subscribe(suffix, fn) |
||||
} |
||||
|
||||
// Get calls Client.Get on the default client.
|
||||
func Get(suffix string) (string, error) { return defaultClient.Get(suffix) } |
||||
|
||||
// ProjectID returns the current instance's project ID string.
|
||||
func ProjectID() (string, error) { return defaultClient.ProjectID() } |
||||
|
||||
// NumericProjectID returns the current instance's numeric project ID.
|
||||
func NumericProjectID() (string, error) { return defaultClient.NumericProjectID() } |
||||
|
||||
// InternalIP returns the instance's primary internal IP address.
|
||||
func InternalIP() (string, error) { return defaultClient.InternalIP() } |
||||
|
||||
// ExternalIP returns the instance's primary external (public) IP address.
|
||||
func ExternalIP() (string, error) { return defaultClient.ExternalIP() } |
||||
|
||||
// Hostname returns the instance's hostname. This will be of the form
|
||||
// "<instanceID>.c.<projID>.internal".
|
||||
func Hostname() (string, error) { return defaultClient.Hostname() } |
||||
|
||||
// InstanceTags returns the list of user-defined instance tags,
|
||||
// assigned when initially creating a GCE instance.
|
||||
func InstanceTags() ([]string, error) { return defaultClient.InstanceTags() } |
||||
|
||||
// InstanceID returns the current VM's numeric instance ID.
|
||||
func InstanceID() (string, error) { return defaultClient.InstanceID() } |
||||
|
||||
// InstanceName returns the current VM's instance ID string.
|
||||
func InstanceName() (string, error) { return defaultClient.InstanceName() } |
||||
|
||||
// Zone returns the current VM's zone, such as "us-central1-b".
|
||||
func Zone() (string, error) { return defaultClient.Zone() } |
||||
|
||||
// InstanceAttributes calls Client.InstanceAttributes on the default client.
|
||||
func InstanceAttributes() ([]string, error) { return defaultClient.InstanceAttributes() } |
||||
|
||||
// ProjectAttributes calls Client.ProjectAttributes on the default client.
|
||||
func ProjectAttributes() ([]string, error) { return defaultClient.ProjectAttributes() } |
||||
|
||||
// InstanceAttributeValue calls Client.InstanceAttributeValue on the default client.
|
||||
func InstanceAttributeValue(attr string) (string, error) { |
||||
return defaultClient.InstanceAttributeValue(attr) |
||||
} |
||||
|
||||
// ProjectAttributeValue calls Client.ProjectAttributeValue on the default client.
|
||||
func ProjectAttributeValue(attr string) (string, error) { |
||||
return defaultClient.ProjectAttributeValue(attr) |
||||
} |
||||
|
||||
// Scopes calls Client.Scopes on the default client.
|
||||
func Scopes(serviceAccount string) ([]string, error) { return defaultClient.Scopes(serviceAccount) } |
||||
|
||||
func strsContains(ss []string, s string) bool { |
||||
for _, v := range ss { |
||||
if v == s { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
// A Client provides metadata.
|
||||
type Client struct { |
||||
hc *http.Client |
||||
} |
||||
|
||||
// NewClient returns a Client that can be used to fetch metadata. All HTTP requests
|
||||
// will use the given http.Client instead of the default client.
|
||||
func NewClient(c *http.Client) *Client { |
||||
return &Client{hc: c} |
||||
} |
||||
|
||||
// getETag returns a value from the metadata service as well as the associated ETag.
|
||||
// This func is otherwise equivalent to Get.
|
||||
func (c *Client) getETag(suffix string) (value, etag string, err error) { |
||||
// Using a fixed IP makes it very difficult to spoof the metadata service in
|
||||
// a container, which is an important use-case for local testing of cloud
|
||||
// deployments. To enable spoofing of the metadata service, the environment
|
||||
// variable GCE_METADATA_HOST is first inspected to decide where metadata
|
||||
// requests shall go.
|
||||
host := os.Getenv(metadataHostEnv) |
||||
if host == "" { |
||||
// Using 169.254.169.254 instead of "metadata" here because Go
|
||||
// binaries built with the "netgo" tag and without cgo won't
|
||||
// know the search suffix for "metadata" is
|
||||
// ".google.internal", and this IP address is documented as
|
||||
// being stable anyway.
|
||||
host = metadataIP |
||||
} |
||||
u := "http://" + host + "/computeMetadata/v1/" + suffix |
||||
req, _ := http.NewRequest("GET", u, nil) |
||||
req.Header.Set("Metadata-Flavor", "Google") |
||||
req.Header.Set("User-Agent", userAgent) |
||||
res, err := c.hc.Do(req) |
||||
if err != nil { |
||||
return "", "", err |
||||
} |
||||
defer res.Body.Close() |
||||
if res.StatusCode == http.StatusNotFound { |
||||
return "", "", NotDefinedError(suffix) |
||||
} |
||||
all, err := ioutil.ReadAll(res.Body) |
||||
if err != nil { |
||||
return "", "", err |
||||
} |
||||
if res.StatusCode != 200 { |
||||
return "", "", &Error{Code: res.StatusCode, Message: string(all)} |
||||
} |
||||
return string(all), res.Header.Get("Etag"), nil |
||||
} |
||||
|
||||
// Get returns a value from the metadata service.
|
||||
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
|
||||
//
|
||||
// If the GCE_METADATA_HOST environment variable is not defined, a default of
|
||||
// 169.254.169.254 will be used instead.
|
||||
//
|
||||
// If the requested metadata is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
func (c *Client) Get(suffix string) (string, error) { |
||||
val, _, err := c.getETag(suffix) |
||||
return val, err |
||||
} |
||||
|
||||
func (c *Client) getTrimmed(suffix string) (s string, err error) { |
||||
s, err = c.Get(suffix) |
||||
s = strings.TrimSpace(s) |
||||
return |
||||
} |
||||
|
||||
func (c *Client) lines(suffix string) ([]string, error) { |
||||
j, err := c.Get(suffix) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
s := strings.Split(strings.TrimSpace(j), "\n") |
||||
for i := range s { |
||||
s[i] = strings.TrimSpace(s[i]) |
||||
} |
||||
return s, nil |
||||
} |
||||
|
||||
// ProjectID returns the current instance's project ID string.
|
||||
func (c *Client) ProjectID() (string, error) { return projID.get(c) } |
||||
|
||||
// NumericProjectID returns the current instance's numeric project ID.
|
||||
func (c *Client) NumericProjectID() (string, error) { return projNum.get(c) } |
||||
|
||||
// InstanceID returns the current VM's numeric instance ID.
|
||||
func (c *Client) InstanceID() (string, error) { return instID.get(c) } |
||||
|
||||
// InternalIP returns the instance's primary internal IP address.
|
||||
func (c *Client) InternalIP() (string, error) { |
||||
return c.getTrimmed("instance/network-interfaces/0/ip") |
||||
} |
||||
|
||||
// ExternalIP returns the instance's primary external (public) IP address.
|
||||
func (c *Client) ExternalIP() (string, error) { |
||||
return c.getTrimmed("instance/network-interfaces/0/access-configs/0/external-ip") |
||||
} |
||||
|
||||
// Hostname returns the instance's hostname. This will be of the form
|
||||
// "<instanceID>.c.<projID>.internal".
|
||||
func (c *Client) Hostname() (string, error) { |
||||
return c.getTrimmed("instance/hostname") |
||||
} |
||||
|
||||
// InstanceTags returns the list of user-defined instance tags,
|
||||
// assigned when initially creating a GCE instance.
|
||||
func (c *Client) InstanceTags() ([]string, error) { |
||||
var s []string |
||||
j, err := c.Get("instance/tags") |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
if err := json.NewDecoder(strings.NewReader(j)).Decode(&s); err != nil { |
||||
return nil, err |
||||
} |
||||
return s, nil |
||||
} |
||||
|
||||
// InstanceName returns the current VM's instance ID string.
|
||||
func (c *Client) InstanceName() (string, error) { |
||||
host, err := c.Hostname() |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
return strings.Split(host, ".")[0], nil |
||||
} |
||||
|
||||
// Zone returns the current VM's zone, such as "us-central1-b".
|
||||
func (c *Client) Zone() (string, error) { |
||||
zone, err := c.getTrimmed("instance/zone") |
||||
// zone is of the form "projects/<projNum>/zones/<zoneName>".
|
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
return zone[strings.LastIndex(zone, "/")+1:], nil |
||||
} |
||||
|
||||
// InstanceAttributes returns the list of user-defined attributes,
|
||||
// assigned when initially creating a GCE VM instance. The value of an
|
||||
// attribute can be obtained with InstanceAttributeValue.
|
||||
func (c *Client) InstanceAttributes() ([]string, error) { return c.lines("instance/attributes/") } |
||||
|
||||
// ProjectAttributes returns the list of user-defined attributes
|
||||
// applying to the project as a whole, not just this VM. The value of
|
||||
// an attribute can be obtained with ProjectAttributeValue.
|
||||
func (c *Client) ProjectAttributes() ([]string, error) { return c.lines("project/attributes/") } |
||||
|
||||
// InstanceAttributeValue returns the value of the provided VM
|
||||
// instance attribute.
|
||||
//
|
||||
// If the requested attribute is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
//
|
||||
// InstanceAttributeValue may return ("", nil) if the attribute was
|
||||
// defined to be the empty string.
|
||||
func (c *Client) InstanceAttributeValue(attr string) (string, error) { |
||||
return c.Get("instance/attributes/" + attr) |
||||
} |
||||
|
||||
// ProjectAttributeValue returns the value of the provided
|
||||
// project attribute.
|
||||
//
|
||||
// If the requested attribute is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
//
|
||||
// ProjectAttributeValue may return ("", nil) if the attribute was
|
||||
// defined to be the empty string.
|
||||
func (c *Client) ProjectAttributeValue(attr string) (string, error) { |
||||
return c.Get("project/attributes/" + attr) |
||||
} |
||||
|
||||
// Scopes returns the service account scopes for the given account.
|
||||
// The account may be empty or the string "default" to use the instance's
|
||||
// main account.
|
||||
func (c *Client) Scopes(serviceAccount string) ([]string, error) { |
||||
if serviceAccount == "" { |
||||
serviceAccount = "default" |
||||
} |
||||
return c.lines("instance/service-accounts/" + serviceAccount + "/scopes") |
||||
} |
||||
|
||||
// Subscribe subscribes to a value from the metadata service.
|
||||
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
|
||||
// The suffix may contain query parameters.
|
||||
//
|
||||
// Subscribe calls fn with the latest metadata value indicated by the provided
|
||||
// suffix. If the metadata value is deleted, fn is called with the empty string
|
||||
// and ok false. Subscribe blocks until fn returns a non-nil error or the value
|
||||
// is deleted. Subscribe returns the error value returned from the last call to
|
||||
// fn, which may be nil when ok == false.
|
||||
func (c *Client) Subscribe(suffix string, fn func(v string, ok bool) error) error { |
||||
const failedSubscribeSleep = time.Second * 5 |
||||
|
||||
// First check to see if the metadata value exists at all.
|
||||
val, lastETag, err := c.getETag(suffix) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
if err := fn(val, true); err != nil { |
||||
return err |
||||
} |
||||
|
||||
ok := true |
||||
if strings.ContainsRune(suffix, '?') { |
||||
suffix += "&wait_for_change=true&last_etag=" |
||||
} else { |
||||
suffix += "?wait_for_change=true&last_etag=" |
||||
} |
||||
for { |
||||
val, etag, err := c.getETag(suffix + url.QueryEscape(lastETag)) |
||||
if err != nil { |
||||
if _, deleted := err.(NotDefinedError); !deleted { |
||||
time.Sleep(failedSubscribeSleep) |
||||
continue // Retry on other errors.
|
||||
} |
||||
ok = false |
||||
} |
||||
lastETag = etag |
||||
|
||||
if err := fn(val, ok); err != nil || !ok { |
||||
return err |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Error contains an error response from the server.
|
||||
type Error struct { |
||||
// Code is the HTTP response status code.
|
||||
Code int |
||||
// Message is the server response message.
|
||||
Message string |
||||
} |
||||
|
||||
func (e *Error) Error() string { |
||||
return fmt.Sprintf("compute: Received %d `%s`", e.Code, e.Message) |
||||
} |
||||
@ -0,0 +1,27 @@
|
||||
Copyright 2016, Google Inc. |
||||
All rights reserved. |
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions are |
||||
met: |
||||
|
||||
* Redistributions of source code must retain the above copyright |
||||
notice, this list of conditions and the following disclaimer. |
||||
* Redistributions in binary form must reproduce the above |
||||
copyright notice, this list of conditions and the following disclaimer |
||||
in the documentation and/or other materials provided with the |
||||
distribution. |
||||
* Neither the name of Google Inc. nor the names of its |
||||
contributors may be used to endorse or promote products derived from |
||||
this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
@ -0,0 +1,161 @@
|
||||
// Copyright 2016, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package gax |
||||
|
||||
import ( |
||||
"math/rand" |
||||
"time" |
||||
|
||||
"google.golang.org/grpc" |
||||
"google.golang.org/grpc/codes" |
||||
"google.golang.org/grpc/status" |
||||
) |
||||
|
||||
// CallOption is an option used by Invoke to control behaviors of RPC calls.
|
||||
// CallOption works by modifying relevant fields of CallSettings.
|
||||
type CallOption interface { |
||||
// Resolve applies the option by modifying cs.
|
||||
Resolve(cs *CallSettings) |
||||
} |
||||
|
||||
// Retryer is used by Invoke to determine retry behavior.
|
||||
type Retryer interface { |
||||
// Retry reports whether a request should be retriedand how long to pause before retrying
|
||||
// if the previous attempt returned with err. Invoke never calls Retry with nil error.
|
||||
Retry(err error) (pause time.Duration, shouldRetry bool) |
||||
} |
||||
|
||||
type retryerOption func() Retryer |
||||
|
||||
func (o retryerOption) Resolve(s *CallSettings) { |
||||
s.Retry = o |
||||
} |
||||
|
||||
// WithRetry sets CallSettings.Retry to fn.
|
||||
func WithRetry(fn func() Retryer) CallOption { |
||||
return retryerOption(fn) |
||||
} |
||||
|
||||
// OnCodes returns a Retryer that retries if and only if
|
||||
// the previous attempt returns a GRPC error whose error code is stored in cc.
|
||||
// Pause times between retries are specified by bo.
|
||||
//
|
||||
// bo is only used for its parameters; each Retryer has its own copy.
|
||||
func OnCodes(cc []codes.Code, bo Backoff) Retryer { |
||||
return &boRetryer{ |
||||
backoff: bo, |
||||
codes: append([]codes.Code(nil), cc...), |
||||
} |
||||
} |
||||
|
||||
type boRetryer struct { |
||||
backoff Backoff |
||||
codes []codes.Code |
||||
} |
||||
|
||||
func (r *boRetryer) Retry(err error) (time.Duration, bool) { |
||||
st, ok := status.FromError(err) |
||||
if !ok { |
||||
return 0, false |
||||
} |
||||
c := st.Code() |
||||
for _, rc := range r.codes { |
||||
if c == rc { |
||||
return r.backoff.Pause(), true |
||||
} |
||||
} |
||||
return 0, false |
||||
} |
||||
|
||||
// Backoff implements exponential backoff.
|
||||
// The wait time between retries is a random value between 0 and the "retry envelope".
|
||||
// The envelope starts at Initial and increases by the factor of Multiplier every retry,
|
||||
// but is capped at Max.
|
||||
type Backoff struct { |
||||
// Initial is the initial value of the retry envelope, defaults to 1 second.
|
||||
Initial time.Duration |
||||
|
||||
// Max is the maximum value of the retry envelope, defaults to 30 seconds.
|
||||
Max time.Duration |
||||
|
||||
// Multiplier is the factor by which the retry envelope increases.
|
||||
// It should be greater than 1 and defaults to 2.
|
||||
Multiplier float64 |
||||
|
||||
// cur is the current retry envelope
|
||||
cur time.Duration |
||||
} |
||||
|
||||
// Pause returns the next time.Duration that the caller should use to backoff.
|
||||
func (bo *Backoff) Pause() time.Duration { |
||||
if bo.Initial == 0 { |
||||
bo.Initial = time.Second |
||||
} |
||||
if bo.cur == 0 { |
||||
bo.cur = bo.Initial |
||||
} |
||||
if bo.Max == 0 { |
||||
bo.Max = 30 * time.Second |
||||
} |
||||
if bo.Multiplier < 1 { |
||||
bo.Multiplier = 2 |
||||
} |
||||
// Select a duration between 1ns and the current max. It might seem
|
||||
// counterintuitive to have so much jitter, but
|
||||
// https://www.awsarchitectureblog.com/2015/03/backoff.html argues that
|
||||
// that is the best strategy.
|
||||
d := time.Duration(1 + rand.Int63n(int64(bo.cur))) |
||||
bo.cur = time.Duration(float64(bo.cur) * bo.Multiplier) |
||||
if bo.cur > bo.Max { |
||||
bo.cur = bo.Max |
||||
} |
||||
return d |
||||
} |
||||
|
||||
type grpcOpt []grpc.CallOption |
||||
|
||||
func (o grpcOpt) Resolve(s *CallSettings) { |
||||
s.GRPC = o |
||||
} |
||||
|
||||
// WithGRPCOptions allows passing gRPC call options during client creation.
|
||||
func WithGRPCOptions(opt ...grpc.CallOption) CallOption { |
||||
return grpcOpt(append([]grpc.CallOption(nil), opt...)) |
||||
} |
||||
|
||||
// CallSettings allow fine-grained control over how calls are made.
|
||||
type CallSettings struct { |
||||
// Retry returns a Retryer to be used to control retry logic of a method call.
|
||||
// If Retry is nil or the returned Retryer is nil, the call will not be retried.
|
||||
Retry func() Retryer |
||||
|
||||
// CallOptions to be forwarded to GRPC.
|
||||
GRPC []grpc.CallOption |
||||
} |
||||
@ -0,0 +1,39 @@
|
||||
// Copyright 2016, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Package gax contains a set of modules which aid the development of APIs
|
||||
// for clients and servers based on gRPC and Google API conventions.
|
||||
//
|
||||
// Application code will rarely need to use this library directly.
|
||||
// However, code generated automatically from API definition files can use it
|
||||
// to simplify code generation and to provide more convenient and idiomatic API surfaces.
|
||||
package gax |
||||
|
||||
// Version specifies the gax-go version being used.
|
||||
const Version = "2.0.4" |
||||
@ -0,0 +1,3 @@
|
||||
module github.com/googleapis/gax-go/v2 |
||||
|
||||
require google.golang.org/grpc v1.19.0 |
||||
@ -0,0 +1,25 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= |
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= |
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= |
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= |
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= |
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= |
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= |
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= |
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= |
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d h1:g9qWBGx4puODJTMVyoPrpoxPFgVGd+z1DZwjfRu4d0I= |
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= |
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= |
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= |
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522 h1:Ve1ORMCxvRmSXBwJK+t3Oy+V2vRW2OetUQBq4rJIkZE= |
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= |
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= |
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= |
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= |
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= |
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= |
||||
google.golang.org/grpc v1.19.0 h1:cfg4PD8YEdSFnm7qLV4++93WcmhH2nIUhMjhdCvl3j8= |
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= |
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= |
||||
@ -0,0 +1,53 @@
|
||||
// Copyright 2018, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package gax |
||||
|
||||
import "bytes" |
||||
|
||||
// XGoogHeader is for use by the Google Cloud Libraries only.
|
||||
//
|
||||
// XGoogHeader formats key-value pairs.
|
||||
// The resulting string is suitable for x-goog-api-client header.
|
||||
func XGoogHeader(keyval ...string) string { |
||||
if len(keyval) == 0 { |
||||
return "" |
||||
} |
||||
if len(keyval)%2 != 0 { |
||||
panic("gax.Header: odd argument count") |
||||
} |
||||
var buf bytes.Buffer |
||||
for i := 0; i < len(keyval); i += 2 { |
||||
buf.WriteByte(' ') |
||||
buf.WriteString(keyval[i]) |
||||
buf.WriteByte('/') |
||||
buf.WriteString(keyval[i+1]) |
||||
} |
||||
return buf.String()[1:] |
||||
} |
||||
@ -0,0 +1,99 @@
|
||||
// Copyright 2016, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package gax |
||||
|
||||
import ( |
||||
"context" |
||||
"strings" |
||||
"time" |
||||
) |
||||
|
||||
// APICall is a user defined call stub.
|
||||
type APICall func(context.Context, CallSettings) error |
||||
|
||||
// Invoke calls the given APICall,
|
||||
// performing retries as specified by opts, if any.
|
||||
func Invoke(ctx context.Context, call APICall, opts ...CallOption) error { |
||||
var settings CallSettings |
||||
for _, opt := range opts { |
||||
opt.Resolve(&settings) |
||||
} |
||||
return invoke(ctx, call, settings, Sleep) |
||||
} |
||||
|
||||
// Sleep is similar to time.Sleep, but it can be interrupted by ctx.Done() closing.
|
||||
// If interrupted, Sleep returns ctx.Err().
|
||||
func Sleep(ctx context.Context, d time.Duration) error { |
||||
t := time.NewTimer(d) |
||||
select { |
||||
case <-ctx.Done(): |
||||
t.Stop() |
||||
return ctx.Err() |
||||
case <-t.C: |
||||
return nil |
||||
} |
||||
} |
||||
|
||||
type sleeper func(ctx context.Context, d time.Duration) error |
||||
|
||||
// invoke implements Invoke, taking an additional sleeper argument for testing.
|
||||
func invoke(ctx context.Context, call APICall, settings CallSettings, sp sleeper) error { |
||||
var retryer Retryer |
||||
for { |
||||
err := call(ctx, settings) |
||||
if err == nil { |
||||
return nil |
||||
} |
||||
if settings.Retry == nil { |
||||
return err |
||||
} |
||||
// Never retry permanent certificate errors. (e.x. if ca-certificates
|
||||
// are not installed). We should only make very few, targeted
|
||||
// exceptions: many (other) status=Unavailable should be retried, such
|
||||
// as if there's a network hiccup, or the internet goes out for a
|
||||
// minute. This is also why here we are doing string parsing instead of
|
||||
// simply making Unavailable a non-retried code elsewhere.
|
||||
if strings.Contains(err.Error(), "x509: certificate signed by unknown authority") { |
||||
return err |
||||
} |
||||
if retryer == nil { |
||||
if r := settings.Retry(); r != nil { |
||||
retryer = r |
||||
} else { |
||||
return err |
||||
} |
||||
} |
||||
if d, ok := retryer.Retry(err); !ok { |
||||
return err |
||||
} else if err = sp(ctx, d); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,362 @@
|
||||
Mozilla Public License, version 2.0 |
||||
|
||||
1. Definitions |
||||
|
||||
1.1. "Contributor" |
||||
|
||||
means each individual or legal entity that creates, contributes to the |
||||
creation of, or owns Covered Software. |
||||
|
||||
1.2. "Contributor Version" |
||||
|
||||
means the combination of the Contributions of others (if any) used by a |
||||
Contributor and that particular Contributor's Contribution. |
||||
|
||||
1.3. "Contribution" |
||||
|
||||
means Covered Software of a particular Contributor. |
||||
|
||||
1.4. "Covered Software" |
||||
|
||||
means Source Code Form to which the initial Contributor has attached the |
||||
notice in Exhibit A, the Executable Form of such Source Code Form, and |
||||
Modifications of such Source Code Form, in each case including portions |
||||
thereof. |
||||
|
||||
1.5. "Incompatible With Secondary Licenses" |
||||
means |
||||
|
||||
a. that the initial Contributor has attached the notice described in |
||||
Exhibit B to the Covered Software; or |
||||
|
||||
b. that the Covered Software was made available under the terms of |
||||
version 1.1 or earlier of the License, but not also under the terms of |
||||
a Secondary License. |
||||
|
||||
1.6. "Executable Form" |
||||
|
||||
means any form of the work other than Source Code Form. |
||||
|
||||
1.7. "Larger Work" |
||||
|
||||
means a work that combines Covered Software with other material, in a |
||||
separate file or files, that is not Covered Software. |
||||
|
||||
1.8. "License" |
||||
|
||||
means this document. |
||||
|
||||
1.9. "Licensable" |
||||
|
||||
means having the right to grant, to the maximum extent possible, whether |
||||
at the time of the initial grant or subsequently, any and all of the |
||||
rights conveyed by this License. |
||||
|
||||
1.10. "Modifications" |
||||
|
||||
means any of the following: |
||||
|
||||
a. any file in Source Code Form that results from an addition to, |
||||
deletion from, or modification of the contents of Covered Software; or |
||||
|
||||
b. any new file in Source Code Form that contains any Covered Software. |
||||
|
||||
1.11. "Patent Claims" of a Contributor |
||||
|
||||
means any patent claim(s), including without limitation, method, |
||||
process, and apparatus claims, in any patent Licensable by such |
||||
Contributor that would be infringed, but for the grant of the License, |
||||
by the making, using, selling, offering for sale, having made, import, |
||||
or transfer of either its Contributions or its Contributor Version. |
||||
|
||||
1.12. "Secondary License" |
||||
|
||||
means either the GNU General Public License, Version 2.0, the GNU Lesser |
||||
General Public License, Version 2.1, the GNU Affero General Public |
||||
License, Version 3.0, or any later versions of those licenses. |
||||
|
||||
1.13. "Source Code Form" |
||||
|
||||
means the form of the work preferred for making modifications. |
||||
|
||||
1.14. "You" (or "Your") |
||||
|
||||
means an individual or a legal entity exercising rights under this |
||||
License. For legal entities, "You" includes any entity that controls, is |
||||
controlled by, or is under common control with You. For purposes of this |
||||
definition, "control" means (a) the power, direct or indirect, to cause |
||||
the direction or management of such entity, whether by contract or |
||||
otherwise, or (b) ownership of more than fifty percent (50%) of the |
||||
outstanding shares or beneficial ownership of such entity. |
||||
|
||||
|
||||
2. License Grants and Conditions |
||||
|
||||
2.1. Grants |
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free, |
||||
non-exclusive license: |
||||
|
||||
a. under intellectual property rights (other than patent or trademark) |
||||
Licensable by such Contributor to use, reproduce, make available, |
||||
modify, display, perform, distribute, and otherwise exploit its |
||||
Contributions, either on an unmodified basis, with Modifications, or |
||||
as part of a Larger Work; and |
||||
|
||||
b. under Patent Claims of such Contributor to make, use, sell, offer for |
||||
sale, have made, import, and otherwise transfer either its |
||||
Contributions or its Contributor Version. |
||||
|
||||
2.2. Effective Date |
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution |
||||
become effective for each Contribution on the date the Contributor first |
||||
distributes such Contribution. |
||||
|
||||
2.3. Limitations on Grant Scope |
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under |
||||
this License. No additional rights or licenses will be implied from the |
||||
distribution or licensing of Covered Software under this License. |
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a |
||||
Contributor: |
||||
|
||||
a. for any code that a Contributor has removed from Covered Software; or |
||||
|
||||
b. for infringements caused by: (i) Your and any other third party's |
||||
modifications of Covered Software, or (ii) the combination of its |
||||
Contributions with other software (except as part of its Contributor |
||||
Version); or |
||||
|
||||
c. under Patent Claims infringed by Covered Software in the absence of |
||||
its Contributions. |
||||
|
||||
This License does not grant any rights in the trademarks, service marks, |
||||
or logos of any Contributor (except as may be necessary to comply with |
||||
the notice requirements in Section 3.4). |
||||
|
||||
2.4. Subsequent Licenses |
||||
|
||||
No Contributor makes additional grants as a result of Your choice to |
||||
distribute the Covered Software under a subsequent version of this |
||||
License (see Section 10.2) or under the terms of a Secondary License (if |
||||
permitted under the terms of Section 3.3). |
||||
|
||||
2.5. Representation |
||||
|
||||
Each Contributor represents that the Contributor believes its |
||||
Contributions are its original creation(s) or it has sufficient rights to |
||||
grant the rights to its Contributions conveyed by this License. |
||||
|
||||
2.6. Fair Use |
||||
|
||||
This License is not intended to limit any rights You have under |
||||
applicable copyright doctrines of fair use, fair dealing, or other |
||||
equivalents. |
||||
|
||||
2.7. Conditions |
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in |
||||
Section 2.1. |
||||
|
||||
|
||||
3. Responsibilities |
||||
|
||||
3.1. Distribution of Source Form |
||||
|
||||
All distribution of Covered Software in Source Code Form, including any |
||||
Modifications that You create or to which You contribute, must be under |
||||
the terms of this License. You must inform recipients that the Source |
||||
Code Form of the Covered Software is governed by the terms of this |
||||
License, and how they can obtain a copy of this License. You may not |
||||
attempt to alter or restrict the recipients' rights in the Source Code |
||||
Form. |
||||
|
||||
3.2. Distribution of Executable Form |
||||
|
||||
If You distribute Covered Software in Executable Form then: |
||||
|
||||
a. such Covered Software must also be made available in Source Code Form, |
||||
as described in Section 3.1, and You must inform recipients of the |
||||
Executable Form how they can obtain a copy of such Source Code Form by |
||||
reasonable means in a timely manner, at a charge no more than the cost |
||||
of distribution to the recipient; and |
||||
|
||||
b. You may distribute such Executable Form under the terms of this |
||||
License, or sublicense it under different terms, provided that the |
||||
license for the Executable Form does not attempt to limit or alter the |
||||
recipients' rights in the Source Code Form under this License. |
||||
|
||||
3.3. Distribution of a Larger Work |
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice, |
||||
provided that You also comply with the requirements of this License for |
||||
the Covered Software. If the Larger Work is a combination of Covered |
||||
Software with a work governed by one or more Secondary Licenses, and the |
||||
Covered Software is not Incompatible With Secondary Licenses, this |
||||
License permits You to additionally distribute such Covered Software |
||||
under the terms of such Secondary License(s), so that the recipient of |
||||
the Larger Work may, at their option, further distribute the Covered |
||||
Software under the terms of either this License or such Secondary |
||||
License(s). |
||||
|
||||
3.4. Notices |
||||
|
||||
You may not remove or alter the substance of any license notices |
||||
(including copyright notices, patent notices, disclaimers of warranty, or |
||||
limitations of liability) contained within the Source Code Form of the |
||||
Covered Software, except that You may alter any license notices to the |
||||
extent required to remedy known factual inaccuracies. |
||||
|
||||
3.5. Application of Additional Terms |
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support, |
||||
indemnity or liability obligations to one or more recipients of Covered |
||||
Software. However, You may do so only on Your own behalf, and not on |
||||
behalf of any Contributor. You must make it absolutely clear that any |
||||
such warranty, support, indemnity, or liability obligation is offered by |
||||
You alone, and You hereby agree to indemnify every Contributor for any |
||||
liability incurred by such Contributor as a result of warranty, support, |
||||
indemnity or liability terms You offer. You may include additional |
||||
disclaimers of warranty and limitations of liability specific to any |
||||
jurisdiction. |
||||
|
||||
4. Inability to Comply Due to Statute or Regulation |
||||
|
||||
If it is impossible for You to comply with any of the terms of this License |
||||
with respect to some or all of the Covered Software due to statute, |
||||
judicial order, or regulation then You must: (a) comply with the terms of |
||||
this License to the maximum extent possible; and (b) describe the |
||||
limitations and the code they affect. Such description must be placed in a |
||||
text file included with all distributions of the Covered Software under |
||||
this License. Except to the extent prohibited by statute or regulation, |
||||
such description must be sufficiently detailed for a recipient of ordinary |
||||
skill to be able to understand it. |
||||
|
||||
5. Termination |
||||
|
||||
5.1. The rights granted under this License will terminate automatically if You |
||||
fail to comply with any of its terms. However, if You become compliant, |
||||
then the rights granted under this License from a particular Contributor |
||||
are reinstated (a) provisionally, unless and until such Contributor |
||||
explicitly and finally terminates Your grants, and (b) on an ongoing |
||||
basis, if such Contributor fails to notify You of the non-compliance by |
||||
some reasonable means prior to 60 days after You have come back into |
||||
compliance. Moreover, Your grants from a particular Contributor are |
||||
reinstated on an ongoing basis if such Contributor notifies You of the |
||||
non-compliance by some reasonable means, this is the first time You have |
||||
received notice of non-compliance with this License from such |
||||
Contributor, and You become compliant prior to 30 days after Your receipt |
||||
of the notice. |
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent |
||||
infringement claim (excluding declaratory judgment actions, |
||||
counter-claims, and cross-claims) alleging that a Contributor Version |
||||
directly or indirectly infringes any patent, then the rights granted to |
||||
You by any and all Contributors for the Covered Software under Section |
||||
2.1 of this License shall terminate. |
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user |
||||
license agreements (excluding distributors and resellers) which have been |
||||
validly granted by You or Your distributors under this License prior to |
||||
termination shall survive termination. |
||||
|
||||
6. Disclaimer of Warranty |
||||
|
||||
Covered Software is provided under this License on an "as is" basis, |
||||
without warranty of any kind, either expressed, implied, or statutory, |
||||
including, without limitation, warranties that the Covered Software is free |
||||
of defects, merchantable, fit for a particular purpose or non-infringing. |
||||
The entire risk as to the quality and performance of the Covered Software |
||||
is with You. Should any Covered Software prove defective in any respect, |
||||
You (not any Contributor) assume the cost of any necessary servicing, |
||||
repair, or correction. This disclaimer of warranty constitutes an essential |
||||
part of this License. No use of any Covered Software is authorized under |
||||
this License except under this disclaimer. |
||||
|
||||
7. Limitation of Liability |
||||
|
||||
Under no circumstances and under no legal theory, whether tort (including |
||||
negligence), contract, or otherwise, shall any Contributor, or anyone who |
||||
distributes Covered Software as permitted above, be liable to You for any |
||||
direct, indirect, special, incidental, or consequential damages of any |
||||
character including, without limitation, damages for lost profits, loss of |
||||
goodwill, work stoppage, computer failure or malfunction, or any and all |
||||
other commercial damages or losses, even if such party shall have been |
||||
informed of the possibility of such damages. This limitation of liability |
||||
shall not apply to liability for death or personal injury resulting from |
||||
such party's negligence to the extent applicable law prohibits such |
||||
limitation. Some jurisdictions do not allow the exclusion or limitation of |
||||
incidental or consequential damages, so this exclusion and limitation may |
||||
not apply to You. |
||||
|
||||
8. Litigation |
||||
|
||||
Any litigation relating to this License may be brought only in the courts |
||||
of a jurisdiction where the defendant maintains its principal place of |
||||
business and such litigation shall be governed by laws of that |
||||
jurisdiction, without reference to its conflict-of-law provisions. Nothing |
||||
in this Section shall prevent a party's ability to bring cross-claims or |
||||
counter-claims. |
||||
|
||||
9. Miscellaneous |
||||
|
||||
This License represents the complete agreement concerning the subject |
||||
matter hereof. If any provision of this License is held to be |
||||
unenforceable, such provision shall be reformed only to the extent |
||||
necessary to make it enforceable. Any law or regulation which provides that |
||||
the language of a contract shall be construed against the drafter shall not |
||||
be used to construe this License against a Contributor. |
||||
|
||||
|
||||
10. Versions of the License |
||||
|
||||
10.1. New Versions |
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section |
||||
10.3, no one other than the license steward has the right to modify or |
||||
publish new versions of this License. Each version will be given a |
||||
distinguishing version number. |
||||
|
||||
10.2. Effect of New Versions |
||||
|
||||
You may distribute the Covered Software under the terms of the version |
||||
of the License under which You originally received the Covered Software, |
||||
or under the terms of any subsequent version published by the license |
||||
steward. |
||||
|
||||
10.3. Modified Versions |
||||
|
||||
If you create software not governed by this License, and you want to |
||||
create a new license for such software, you may create and use a |
||||
modified version of this License if you rename the license and remove |
||||
any references to the name of the license steward (except to note that |
||||
such modified license differs from this License). |
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary |
||||
Licenses If You choose to distribute Source Code Form that is |
||||
Incompatible With Secondary Licenses under the terms of this version of |
||||
the License, the notice described in Exhibit B of this License must be |
||||
attached. |
||||
|
||||
Exhibit A - Source Code Form License Notice |
||||
|
||||
This Source Code Form is subject to the |
||||
terms of the Mozilla Public License, v. |
||||
2.0. If a copy of the MPL was not |
||||
distributed with this file, You can |
||||
obtain one at |
||||
http://mozilla.org/MPL/2.0/. |
||||
|
||||
If it is not possible or desirable to put the notice in a particular file, |
||||
then You may include the notice in a location (such as a LICENSE file in a |
||||
relevant directory) where a recipient would be likely to look for such a |
||||
notice. |
||||
|
||||
You may add additional accurate notices of copyright ownership. |
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice |
||||
|
||||
This Source Code Form is "Incompatible |
||||
With Secondary Licenses", as defined by |
||||
the Mozilla Public License, v. 2.0. |
||||
@ -0,0 +1,161 @@
|
||||
package simplelru |
||||
|
||||
import ( |
||||
"container/list" |
||||
"errors" |
||||
) |
||||
|
||||
// EvictCallback is used to get a callback when a cache entry is evicted
|
||||
type EvictCallback func(key interface{}, value interface{}) |
||||
|
||||
// LRU implements a non-thread safe fixed size LRU cache
|
||||
type LRU struct { |
||||
size int |
||||
evictList *list.List |
||||
items map[interface{}]*list.Element |
||||
onEvict EvictCallback |
||||
} |
||||
|
||||
// entry is used to hold a value in the evictList
|
||||
type entry struct { |
||||
key interface{} |
||||
value interface{} |
||||
} |
||||
|
||||
// NewLRU constructs an LRU of the given size
|
||||
func NewLRU(size int, onEvict EvictCallback) (*LRU, error) { |
||||
if size <= 0 { |
||||
return nil, errors.New("Must provide a positive size") |
||||
} |
||||
c := &LRU{ |
||||
size: size, |
||||
evictList: list.New(), |
||||
items: make(map[interface{}]*list.Element), |
||||
onEvict: onEvict, |
||||
} |
||||
return c, nil |
||||
} |
||||
|
||||
// Purge is used to completely clear the cache.
|
||||
func (c *LRU) Purge() { |
||||
for k, v := range c.items { |
||||
if c.onEvict != nil { |
||||
c.onEvict(k, v.Value.(*entry).value) |
||||
} |
||||
delete(c.items, k) |
||||
} |
||||
c.evictList.Init() |
||||
} |
||||
|
||||
// Add adds a value to the cache. Returns true if an eviction occurred.
|
||||
func (c *LRU) Add(key, value interface{}) (evicted bool) { |
||||
// Check for existing item
|
||||
if ent, ok := c.items[key]; ok { |
||||
c.evictList.MoveToFront(ent) |
||||
ent.Value.(*entry).value = value |
||||
return false |
||||
} |
||||
|
||||
// Add new item
|
||||
ent := &entry{key, value} |
||||
entry := c.evictList.PushFront(ent) |
||||
c.items[key] = entry |
||||
|
||||
evict := c.evictList.Len() > c.size |
||||
// Verify size not exceeded
|
||||
if evict { |
||||
c.removeOldest() |
||||
} |
||||
return evict |
||||
} |
||||
|
||||
// Get looks up a key's value from the cache.
|
||||
func (c *LRU) Get(key interface{}) (value interface{}, ok bool) { |
||||
if ent, ok := c.items[key]; ok { |
||||
c.evictList.MoveToFront(ent) |
||||
return ent.Value.(*entry).value, true |
||||
} |
||||
return |
||||
} |
||||
|
||||
// Contains checks if a key is in the cache, without updating the recent-ness
|
||||
// or deleting it for being stale.
|
||||
func (c *LRU) Contains(key interface{}) (ok bool) { |
||||
_, ok = c.items[key] |
||||
return ok |
||||
} |
||||
|
||||
// Peek returns the key value (or undefined if not found) without updating
|
||||
// the "recently used"-ness of the key.
|
||||
func (c *LRU) Peek(key interface{}) (value interface{}, ok bool) { |
||||
var ent *list.Element |
||||
if ent, ok = c.items[key]; ok { |
||||
return ent.Value.(*entry).value, true |
||||
} |
||||
return nil, ok |
||||
} |
||||
|
||||
// Remove removes the provided key from the cache, returning if the
|
||||
// key was contained.
|
||||
func (c *LRU) Remove(key interface{}) (present bool) { |
||||
if ent, ok := c.items[key]; ok { |
||||
c.removeElement(ent) |
||||
return true |
||||
} |
||||
return false |
||||
} |
||||
|
||||
// RemoveOldest removes the oldest item from the cache.
|
||||
func (c *LRU) RemoveOldest() (key interface{}, value interface{}, ok bool) { |
||||
ent := c.evictList.Back() |
||||
if ent != nil { |
||||
c.removeElement(ent) |
||||
kv := ent.Value.(*entry) |
||||
return kv.key, kv.value, true |
||||
} |
||||
return nil, nil, false |
||||
} |
||||
|
||||
// GetOldest returns the oldest entry
|
||||
func (c *LRU) GetOldest() (key interface{}, value interface{}, ok bool) { |
||||
ent := c.evictList.Back() |
||||
if ent != nil { |
||||
kv := ent.Value.(*entry) |
||||
return kv.key, kv.value, true |
||||
} |
||||
return nil, nil, false |
||||
} |
||||
|
||||
// Keys returns a slice of the keys in the cache, from oldest to newest.
|
||||
func (c *LRU) Keys() []interface{} { |
||||
keys := make([]interface{}, len(c.items)) |
||||
i := 0 |
||||
for ent := c.evictList.Back(); ent != nil; ent = ent.Prev() { |
||||
keys[i] = ent.Value.(*entry).key |
||||
i++ |
||||
} |
||||
return keys |
||||
} |
||||
|
||||
// Len returns the number of items in the cache.
|
||||
func (c *LRU) Len() int { |
||||
return c.evictList.Len() |
||||
} |
||||
|
||||
// removeOldest removes the oldest item from the cache.
|
||||
func (c *LRU) removeOldest() { |
||||
ent := c.evictList.Back() |
||||
if ent != nil { |
||||
c.removeElement(ent) |
||||
} |
||||
} |
||||
|
||||
// removeElement is used to remove a given list element from the cache
|
||||
func (c *LRU) removeElement(e *list.Element) { |
||||
c.evictList.Remove(e) |
||||
kv := e.Value.(*entry) |
||||
delete(c.items, kv.key) |
||||
if c.onEvict != nil { |
||||
c.onEvict(kv.key, kv.value) |
||||
} |
||||
} |
||||
@ -0,0 +1,36 @@
|
||||
package simplelru |
||||
|
||||
// LRUCache is the interface for simple LRU cache.
|
||||
type LRUCache interface { |
||||
// Adds a value to the cache, returns true if an eviction occurred and
|
||||
// updates the "recently used"-ness of the key.
|
||||
Add(key, value interface{}) bool |
||||
|
||||
// Returns key's value from the cache and
|
||||
// updates the "recently used"-ness of the key. #value, isFound
|
||||
Get(key interface{}) (value interface{}, ok bool) |
||||
|
||||
// Check if a key exsists in cache without updating the recent-ness.
|
||||
Contains(key interface{}) (ok bool) |
||||
|
||||
// Returns key's value without updating the "recently used"-ness of the key.
|
||||
Peek(key interface{}) (value interface{}, ok bool) |
||||
|
||||
// Removes a key from the cache.
|
||||
Remove(key interface{}) bool |
||||
|
||||
// Removes the oldest entry from cache.
|
||||
RemoveOldest() (interface{}, interface{}, bool) |
||||
|
||||
// Returns the oldest entry from the cache. #key, value, isFound
|
||||
GetOldest() (interface{}, interface{}, bool) |
||||
|
||||
// Returns a slice of the keys in the cache, from oldest to newest.
|
||||
Keys() []interface{} |
||||
|
||||
// Returns the number of items in the cache.
|
||||
Len() int |
||||
|
||||
// Clear all cache entries
|
||||
Purge() |
||||
} |
||||
@ -0,0 +1,9 @@
|
||||
/.idea/ |
||||
|
||||
# go.opencensus.io/exporter/aws |
||||
/exporter/aws/ |
||||
|
||||
# Exclude vendor, use dep ensure after checkout: |
||||
/vendor/github.com/ |
||||
/vendor/golang.org/ |
||||
/vendor/google.golang.org/ |
||||
@ -0,0 +1,17 @@
|
||||
language: go |
||||
|
||||
go_import_path: go.opencensus.io |
||||
|
||||
go: |
||||
- 1.11.x |
||||
|
||||
env: |
||||
global: |
||||
GO111MODULE=on |
||||
|
||||
before_script: |
||||
- make install-tools |
||||
|
||||
script: |
||||
- make travis-ci |
||||
- go run internal/check/version.go # TODO move this to makefile |
||||
@ -0,0 +1,63 @@
|
||||
# How to contribute |
||||
|
||||
We'd love to accept your patches and contributions to this project. There are |
||||
just a few small guidelines you need to follow. |
||||
|
||||
## Contributor License Agreement |
||||
|
||||
Contributions to this project must be accompanied by a Contributor License |
||||
Agreement. You (or your employer) retain the copyright to your contribution, |
||||
this simply gives us permission to use and redistribute your contributions as |
||||
part of the project. Head over to <https://cla.developers.google.com/> to see |
||||
your current agreements on file or to sign a new one. |
||||
|
||||
You generally only need to submit a CLA once, so if you've already submitted one |
||||
(even if it was for a different project), you probably don't need to do it |
||||
again. |
||||
|
||||
## Code reviews |
||||
|
||||
All submissions, including submissions by project members, require review. We |
||||
use GitHub pull requests for this purpose. Consult [GitHub Help] for more |
||||
information on using pull requests. |
||||
|
||||
[GitHub Help]: https://help.github.com/articles/about-pull-requests/ |
||||
|
||||
## Instructions |
||||
|
||||
Fork the repo, checkout the upstream repo to your GOPATH by: |
||||
|
||||
``` |
||||
$ go get -d go.opencensus.io |
||||
``` |
||||
|
||||
Add your fork as an origin: |
||||
|
||||
``` |
||||
cd $(go env GOPATH)/src/go.opencensus.io |
||||
git remote add fork git@github.com:YOUR_GITHUB_USERNAME/opencensus-go.git |
||||
``` |
||||
|
||||
Run tests: |
||||
|
||||
``` |
||||
$ make install-tools # Only first time. |
||||
$ make |
||||
``` |
||||
|
||||
Checkout a new branch, make modifications and push the branch to your fork: |
||||
|
||||
``` |
||||
$ git checkout -b feature |
||||
# edit files |
||||
$ git commit |
||||
$ git push fork feature |
||||
``` |
||||
|
||||
Open a pull request against the main opencensus-go repo. |
||||
|
||||
## General Notes |
||||
This project uses Appveyor and Travis for CI. |
||||
|
||||
The dependencies are managed with `go mod` if you work with the sources under your |
||||
`$GOPATH` you need to set the environment variable `GO111MODULE=on`. |
||||
@ -0,0 +1,231 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. |
||||
|
||||
|
||||
[[projects]] |
||||
branch = "master" |
||||
digest = "1:eee9386329f4fcdf8d6c0def0c9771b634bdd5ba460d888aa98c17d59b37a76c" |
||||
name = "git.apache.org/thrift.git" |
||||
packages = ["lib/go/thrift"] |
||||
pruneopts = "UT" |
||||
revision = "6e67faa92827ece022380b211c2caaadd6145bf5" |
||||
source = "github.com/apache/thrift" |
||||
|
||||
[[projects]] |
||||
branch = "master" |
||||
digest = "1:d6afaeed1502aa28e80a4ed0981d570ad91b2579193404256ce672ed0a609e0d" |
||||
name = "github.com/beorn7/perks" |
||||
packages = ["quantile"] |
||||
pruneopts = "UT" |
||||
revision = "3a771d992973f24aa725d07868b467d1ddfceafb" |
||||
|
||||
[[projects]] |
||||
digest = "1:4c0989ca0bcd10799064318923b9bc2db6b4d6338dd75f3f2d86c3511aaaf5cf" |
||||
name = "github.com/golang/protobuf" |
||||
packages = [ |
||||
"proto", |
||||
"ptypes", |
||||
"ptypes/any", |
||||
"ptypes/duration", |
||||
"ptypes/timestamp", |
||||
] |
||||
pruneopts = "UT" |
||||
revision = "aa810b61a9c79d51363740d207bb46cf8e620ed5" |
||||
version = "v1.2.0" |
||||
|
||||
[[projects]] |
||||
digest = "1:ff5ebae34cfbf047d505ee150de27e60570e8c394b3b8fdbb720ff6ac71985fc" |
||||
name = "github.com/matttproud/golang_protobuf_extensions" |
||||
packages = ["pbutil"] |
||||
pruneopts = "UT" |
||||
revision = "c12348ce28de40eed0136aa2b644d0ee0650e56c" |
||||
version = "v1.0.1" |
||||
|
||||
[[projects]] |
||||
digest = "1:824c8f3aa4c5f23928fa84ebbd5ed2e9443b3f0cb958a40c1f2fbed5cf5e64b1" |
||||
name = "github.com/openzipkin/zipkin-go" |
||||
packages = [ |
||||
".", |
||||
"idgenerator", |
||||
"model", |
||||
"propagation", |
||||
"reporter", |
||||
"reporter/http", |
||||
] |
||||
pruneopts = "UT" |
||||
revision = "d455a5674050831c1e187644faa4046d653433c2" |
||||
version = "v0.1.1" |
||||
|
||||
[[projects]] |
||||
digest = "1:d14a5f4bfecf017cb780bdde1b6483e5deb87e12c332544d2c430eda58734bcb" |
||||
name = "github.com/prometheus/client_golang" |
||||
packages = [ |
||||
"prometheus", |
||||
"prometheus/promhttp", |
||||
] |
||||
pruneopts = "UT" |
||||
revision = "c5b7fccd204277076155f10851dad72b76a49317" |
||||
version = "v0.8.0" |
||||
|
||||
[[projects]] |
||||
branch = "master" |
||||
digest = "1:2d5cd61daa5565187e1d96bae64dbbc6080dacf741448e9629c64fd93203b0d4" |
||||
name = "github.com/prometheus/client_model" |
||||
packages = ["go"] |
||||
pruneopts = "UT" |
||||
revision = "5c3871d89910bfb32f5fcab2aa4b9ec68e65a99f" |
||||
|
||||
[[projects]] |
||||
branch = "master" |
||||
digest = "1:63b68062b8968092eb86bedc4e68894bd096ea6b24920faca8b9dcf451f54bb5" |
||||
name = "github.com/prometheus/common" |
||||
packages = [ |
||||
"expfmt", |
||||
"internal/bitbucket.org/ww/goautoneg", |
||||
"model", |
||||
] |
||||
pruneopts = "UT" |
||||
revision = "c7de2306084e37d54b8be01f3541a8464345e9a5" |
||||
|
||||
[[projects]] |
||||
branch = "master" |
||||
digest = "1:8c49953a1414305f2ff5465147ee576dd705487c35b15918fcd4efdc0cb7a290" |
||||
name = "github.com/prometheus/procfs" |
||||
packages = [ |
||||
".", |
||||
"internal/util", |
||||
"nfs", |
||||
"xfs", |
||||
] |
||||
pruneopts = "UT" |
||||
revision = "05ee40e3a273f7245e8777337fc7b46e533a9a92" |
||||
|
||||
[[projects]] |
||||
branch = "master" |
||||
digest = "1:deafe4ab271911fec7de5b693d7faae3f38796d9eb8622e2b9e7df42bb3dfea9" |
||||
name = "golang.org/x/net" |
||||
packages = [ |
||||
"context", |
||||
"http/httpguts", |
||||
"http2", |
||||
"http2/hpack", |
||||
"idna", |
||||
"internal/timeseries", |
||||
"trace", |
||||
] |
||||
pruneopts = "UT" |
||||
revision = "922f4815f713f213882e8ef45e0d315b164d705c" |
||||
|
||||
[[projects]] |
||||
branch = "master" |
||||
digest = "1:e0140c0c868c6e0f01c0380865194592c011fe521d6e12d78bfd33e756fe018a" |
||||
name = "golang.org/x/sync" |
||||
packages = ["semaphore"] |
||||
pruneopts = "UT" |
||||
revision = "1d60e4601c6fd243af51cc01ddf169918a5407ca" |
||||
|
||||
[[projects]] |
||||
branch = "master" |
||||
digest = "1:a3f00ac457c955fe86a41e1495e8f4c54cb5399d609374c5cc26aa7d72e542c8" |
||||
name = "golang.org/x/sys" |
||||
packages = ["unix"] |
||||
pruneopts = "UT" |
||||
revision = "3b58ed4ad3395d483fc92d5d14123ce2c3581fec" |
||||
|
||||
[[projects]] |
||||
digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18" |
||||
name = "golang.org/x/text" |
||||
packages = [ |
||||
"collate", |
||||
"collate/build", |
||||
"internal/colltab", |
||||
"internal/gen", |
||||
"internal/tag", |
||||
"internal/triegen", |
||||
"internal/ucd", |
||||
"language", |
||||
"secure/bidirule", |
||||
"transform", |
||||
"unicode/bidi", |
||||
"unicode/cldr", |
||||
"unicode/norm", |
||||
"unicode/rangetable", |
||||
] |
||||
pruneopts = "UT" |
||||
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0" |
||||
version = "v0.3.0" |
||||
|
||||
[[projects]] |
||||
branch = "master" |
||||
digest = "1:c0c17c94fe8bc1ab34e7f586a4a8b788c5e1f4f9f750ff23395b8b2f5a523530" |
||||
name = "google.golang.org/api" |
||||
packages = ["support/bundler"] |
||||
pruneopts = "UT" |
||||
revision = "e21acd801f91da814261b938941d193bb036441a" |
||||
|
||||
[[projects]] |
||||
branch = "master" |
||||
digest = "1:077c1c599507b3b3e9156d17d36e1e61928ee9b53a5b420f10f28ebd4a0b275c" |
||||
name = "google.golang.org/genproto" |
||||
packages = ["googleapis/rpc/status"] |
||||
pruneopts = "UT" |
||||
revision = "c66870c02cf823ceb633bcd05be3c7cda29976f4" |
||||
|
||||
[[projects]] |
||||
digest = "1:3dd7996ce6bf52dec6a2f69fa43e7c4cefea1d4dfa3c8ab7a5f8a9f7434e239d" |
||||
name = "google.golang.org/grpc" |
||||
packages = [ |
||||
".", |
||||
"balancer", |
||||
"balancer/base", |
||||
"balancer/roundrobin", |
||||
"codes", |
||||
"connectivity", |
||||
"credentials", |
||||
"encoding", |
||||
"encoding/proto", |
||||
"grpclog", |
||||
"internal", |
||||
"internal/backoff", |
||||
"internal/channelz", |
||||
"internal/envconfig", |
||||
"internal/grpcrand", |
||||
"internal/transport", |
||||
"keepalive", |
||||
"metadata", |
||||
"naming", |
||||
"peer", |
||||
"resolver", |
||||
"resolver/dns", |
||||
"resolver/passthrough", |
||||
"stats", |
||||
"status", |
||||
"tap", |
||||
] |
||||
pruneopts = "UT" |
||||
revision = "32fb0ac620c32ba40a4626ddf94d90d12cce3455" |
||||
version = "v1.14.0" |
||||
|
||||
[solve-meta] |
||||
analyzer-name = "dep" |
||||
analyzer-version = 1 |
||||
input-imports = [ |
||||
"git.apache.org/thrift.git/lib/go/thrift", |
||||
"github.com/golang/protobuf/proto", |
||||
"github.com/openzipkin/zipkin-go", |
||||
"github.com/openzipkin/zipkin-go/model", |
||||
"github.com/openzipkin/zipkin-go/reporter", |
||||
"github.com/openzipkin/zipkin-go/reporter/http", |
||||
"github.com/prometheus/client_golang/prometheus", |
||||
"github.com/prometheus/client_golang/prometheus/promhttp", |
||||
"golang.org/x/net/context", |
||||
"golang.org/x/net/http2", |
||||
"google.golang.org/api/support/bundler", |
||||
"google.golang.org/grpc", |
||||
"google.golang.org/grpc/codes", |
||||
"google.golang.org/grpc/grpclog", |
||||
"google.golang.org/grpc/metadata", |
||||
"google.golang.org/grpc/stats", |
||||
"google.golang.org/grpc/status", |
||||
] |
||||
solver-name = "gps-cdcl" |
||||
solver-version = 1 |
||||
@ -0,0 +1,36 @@
|
||||
# For v0.x.y dependencies, prefer adding a constraints of the form: version=">= 0.x.y" |
||||
# to avoid locking to a particular minor version which can cause dep to not be |
||||
# able to find a satisfying dependency graph. |
||||
|
||||
[[constraint]] |
||||
branch = "master" |
||||
name = "git.apache.org/thrift.git" |
||||
source = "github.com/apache/thrift" |
||||
|
||||
[[constraint]] |
||||
name = "github.com/golang/protobuf" |
||||
version = "1.0.0" |
||||
|
||||
[[constraint]] |
||||
name = "github.com/openzipkin/zipkin-go" |
||||
version = ">=0.1.0" |
||||
|
||||
[[constraint]] |
||||
name = "github.com/prometheus/client_golang" |
||||
version = ">=0.8.0" |
||||
|
||||
[[constraint]] |
||||
branch = "master" |
||||
name = "golang.org/x/net" |
||||
|
||||
[[constraint]] |
||||
branch = "master" |
||||
name = "google.golang.org/api" |
||||
|
||||
[[constraint]] |
||||
name = "google.golang.org/grpc" |
||||
version = "1.11.3" |
||||
|
||||
[prune] |
||||
go-tests = true |
||||
unused-packages = true |
||||
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License |
||||
Version 2.0, January 2004 |
||||
http://www.apache.org/licenses/ |
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION |
||||
|
||||
1. Definitions. |
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, |
||||
and distribution as defined by Sections 1 through 9 of this document. |
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by |
||||
the copyright owner that is granting the License. |
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all |
||||
other entities that control, are controlled by, or are under common |
||||
control with that entity. For the purposes of this definition, |
||||
"control" means (i) the power, direct or indirect, to cause the |
||||
direction or management of such entity, whether by contract or |
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the |
||||
outstanding shares, or (iii) beneficial ownership of such entity. |
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity |
||||
exercising permissions granted by this License. |
||||
|
||||
"Source" form shall mean the preferred form for making modifications, |
||||
including but not limited to software source code, documentation |
||||
source, and configuration files. |
||||
|
||||
"Object" form shall mean any form resulting from mechanical |
||||
transformation or translation of a Source form, including but |
||||
not limited to compiled object code, generated documentation, |
||||
and conversions to other media types. |
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or |
||||
Object form, made available under the License, as indicated by a |
||||
copyright notice that is included in or attached to the work |
||||
(an example is provided in the Appendix below). |
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object |
||||
form, that is based on (or derived from) the Work and for which the |
||||
editorial revisions, annotations, elaborations, or other modifications |
||||
represent, as a whole, an original work of authorship. For the purposes |
||||
of this License, Derivative Works shall not include works that remain |
||||
separable from, or merely link (or bind by name) to the interfaces of, |
||||
the Work and Derivative Works thereof. |
||||
|
||||
"Contribution" shall mean any work of authorship, including |
||||
the original version of the Work and any modifications or additions |
||||
to that Work or Derivative Works thereof, that is intentionally |
||||
submitted to Licensor for inclusion in the Work by the copyright owner |
||||
or by an individual or Legal Entity authorized to submit on behalf of |
||||
the copyright owner. For the purposes of this definition, "submitted" |
||||
means any form of electronic, verbal, or written communication sent |
||||
to the Licensor or its representatives, including but not limited to |
||||
communication on electronic mailing lists, source code control systems, |
||||
and issue tracking systems that are managed by, or on behalf of, the |
||||
Licensor for the purpose of discussing and improving the Work, but |
||||
excluding communication that is conspicuously marked or otherwise |
||||
designated in writing by the copyright owner as "Not a Contribution." |
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity |
||||
on behalf of whom a Contribution has been received by Licensor and |
||||
subsequently incorporated within the Work. |
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of |
||||
this License, each Contributor hereby grants to You a perpetual, |
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
||||
copyright license to reproduce, prepare Derivative Works of, |
||||
publicly display, publicly perform, sublicense, and distribute the |
||||
Work and such Derivative Works in Source or Object form. |
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of |
||||
this License, each Contributor hereby grants to You a perpetual, |
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
||||
(except as stated in this section) patent license to make, have made, |
||||
use, offer to sell, sell, import, and otherwise transfer the Work, |
||||
where such license applies only to those patent claims licensable |
||||
by such Contributor that are necessarily infringed by their |
||||
Contribution(s) alone or by combination of their Contribution(s) |
||||
with the Work to which such Contribution(s) was submitted. If You |
||||
institute patent litigation against any entity (including a |
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work |
||||
or a Contribution incorporated within the Work constitutes direct |
||||
or contributory patent infringement, then any patent licenses |
||||
granted to You under this License for that Work shall terminate |
||||
as of the date such litigation is filed. |
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the |
||||
Work or Derivative Works thereof in any medium, with or without |
||||
modifications, and in Source or Object form, provided that You |
||||
meet the following conditions: |
||||
|
||||
(a) You must give any other recipients of the Work or |
||||
Derivative Works a copy of this License; and |
||||
|
||||
(b) You must cause any modified files to carry prominent notices |
||||
stating that You changed the files; and |
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works |
||||
that You distribute, all copyright, patent, trademark, and |
||||
attribution notices from the Source form of the Work, |
||||
excluding those notices that do not pertain to any part of |
||||
the Derivative Works; and |
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its |
||||
distribution, then any Derivative Works that You distribute must |
||||
include a readable copy of the attribution notices contained |
||||
within such NOTICE file, excluding those notices that do not |
||||
pertain to any part of the Derivative Works, in at least one |
||||
of the following places: within a NOTICE text file distributed |
||||
as part of the Derivative Works; within the Source form or |
||||
documentation, if provided along with the Derivative Works; or, |
||||
within a display generated by the Derivative Works, if and |
||||
wherever such third-party notices normally appear. The contents |
||||
of the NOTICE file are for informational purposes only and |
||||
do not modify the License. You may add Your own attribution |
||||
notices within Derivative Works that You distribute, alongside |
||||
or as an addendum to the NOTICE text from the Work, provided |
||||
that such additional attribution notices cannot be construed |
||||
as modifying the License. |
||||
|
||||
You may add Your own copyright statement to Your modifications and |
||||
may provide additional or different license terms and conditions |
||||
for use, reproduction, or distribution of Your modifications, or |
||||
for any such Derivative Works as a whole, provided Your use, |
||||
reproduction, and distribution of the Work otherwise complies with |
||||
the conditions stated in this License. |
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise, |
||||
any Contribution intentionally submitted for inclusion in the Work |
||||
by You to the Licensor shall be under the terms and conditions of |
||||
this License, without any additional terms or conditions. |
||||
Notwithstanding the above, nothing herein shall supersede or modify |
||||
the terms of any separate license agreement you may have executed |
||||
with Licensor regarding such Contributions. |
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade |
||||
names, trademarks, service marks, or product names of the Licensor, |
||||
except as required for reasonable and customary use in describing the |
||||
origin of the Work and reproducing the content of the NOTICE file. |
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or |
||||
agreed to in writing, Licensor provides the Work (and each |
||||
Contributor provides its Contributions) on an "AS IS" BASIS, |
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
||||
implied, including, without limitation, any warranties or conditions |
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A |
||||
PARTICULAR PURPOSE. You are solely responsible for determining the |
||||
appropriateness of using or redistributing the Work and assume any |
||||
risks associated with Your exercise of permissions under this License. |
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory, |
||||
whether in tort (including negligence), contract, or otherwise, |
||||
unless required by applicable law (such as deliberate and grossly |
||||
negligent acts) or agreed to in writing, shall any Contributor be |
||||
liable to You for damages, including any direct, indirect, special, |
||||
incidental, or consequential damages of any character arising as a |
||||
result of this License or out of the use or inability to use the |
||||
Work (including but not limited to damages for loss of goodwill, |
||||
work stoppage, computer failure or malfunction, or any and all |
||||
other commercial damages or losses), even if such Contributor |
||||
has been advised of the possibility of such damages. |
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing |
||||
the Work or Derivative Works thereof, You may choose to offer, |
||||
and charge a fee for, acceptance of support, warranty, indemnity, |
||||
or other liability obligations and/or rights consistent with this |
||||
License. However, in accepting such obligations, You may act only |
||||
on Your own behalf and on Your sole responsibility, not on behalf |
||||
of any other Contributor, and only if You agree to indemnify, |
||||
defend, and hold each Contributor harmless for any liability |
||||
incurred by, or claims asserted against, such Contributor by reason |
||||
of your accepting any such warranty or additional liability. |
||||
|
||||
END OF TERMS AND CONDITIONS |
||||
|
||||
APPENDIX: How to apply the Apache License to your work. |
||||
|
||||
To apply the Apache License to your work, attach the following |
||||
boilerplate notice, with the fields enclosed by brackets "[]" |
||||
replaced with your own identifying information. (Don't include |
||||
the brackets!) The text should be enclosed in the appropriate |
||||
comment syntax for the file format. We also recommend that a |
||||
file or class name and description of purpose be included on the |
||||
same "printed page" as the copyright notice for easier |
||||
identification within third-party archives. |
||||
|
||||
Copyright [yyyy] [name of copyright owner] |
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
you may not use this file except in compliance with the License. |
||||
You may obtain a copy of the License at |
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
||||
Unless required by applicable law or agreed to in writing, software |
||||
distributed under the License is distributed on an "AS IS" BASIS, |
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
See the License for the specific language governing permissions and |
||||
limitations under the License. |
||||
@ -0,0 +1,96 @@
|
||||
# TODO: Fix this on windows.
|
||||
ALL_SRC := $(shell find . -name '*.go' \
|
||||
-not -path './vendor/*' \
|
||||
-not -path '*/gen-go/*' \
|
||||
-type f | sort)
|
||||
ALL_PKGS := $(shell go list $(sort $(dir $(ALL_SRC))))
|
||||
|
||||
GOTEST_OPT?=-v -race -timeout 30s
|
||||
GOTEST_OPT_WITH_COVERAGE = $(GOTEST_OPT) -coverprofile=coverage.txt -covermode=atomic
|
||||
GOTEST=go test
|
||||
GOFMT=gofmt
|
||||
GOLINT=golint
|
||||
GOVET=go vet
|
||||
EMBEDMD=embedmd
|
||||
# TODO decide if we need to change these names.
|
||||
TRACE_ID_LINT_EXCEPTION="type name will be used as trace.TraceID by other packages"
|
||||
TRACE_OPTION_LINT_EXCEPTION="type name will be used as trace.TraceOptions by other packages"
|
||||
README_FILES := $(shell find . -name '*README.md' | sort | tr '\n' ' ')
|
||||
|
||||
.DEFAULT_GOAL := fmt-lint-vet-embedmd-test
|
||||
|
||||
.PHONY: fmt-lint-vet-embedmd-test |
||||
fmt-lint-vet-embedmd-test: fmt lint vet embedmd test |
||||
|
||||
# TODO enable test-with-coverage in tavis
|
||||
.PHONY: travis-ci |
||||
travis-ci: fmt lint vet embedmd test test-386 |
||||
|
||||
all-pkgs: |
||||
@echo $(ALL_PKGS) | tr ' ' '\n' | sort
|
||||
|
||||
all-srcs: |
||||
@echo $(ALL_SRC) | tr ' ' '\n' | sort
|
||||
|
||||
.PHONY: test |
||||
test: |
||||
$(GOTEST) $(GOTEST_OPT) $(ALL_PKGS)
|
||||
|
||||
.PHONY: test-386 |
||||
test-386: |
||||
GOARCH=386 $(GOTEST) -v -timeout 30s $(ALL_PKGS)
|
||||
|
||||
.PHONY: test-with-coverage |
||||
test-with-coverage: |
||||
$(GOTEST) $(GOTEST_OPT_WITH_COVERAGE) $(ALL_PKGS)
|
||||
|
||||
.PHONY: fmt |
||||
fmt: |
||||
@FMTOUT=`$(GOFMT) -s -l $(ALL_SRC) 2>&1`; \
|
||||
if [ "$$FMTOUT" ]; then \
|
||||
echo "$(GOFMT) FAILED => gofmt the following files:\n"; \
|
||||
echo "$$FMTOUT\n"; \
|
||||
exit 1; \
|
||||
else \
|
||||
echo "Fmt finished successfully"; \
|
||||
fi
|
||||
|
||||
.PHONY: lint |
||||
lint: |
||||
@LINTOUT=`$(GOLINT) $(ALL_PKGS) | grep -v $(TRACE_ID_LINT_EXCEPTION) | grep -v $(TRACE_OPTION_LINT_EXCEPTION) 2>&1`; \
|
||||
if [ "$$LINTOUT" ]; then \
|
||||
echo "$(GOLINT) FAILED => clean the following lint errors:\n"; \
|
||||
echo "$$LINTOUT\n"; \
|
||||
exit 1; \
|
||||
else \
|
||||
echo "Lint finished successfully"; \
|
||||
fi
|
||||
|
||||
.PHONY: vet |
||||
vet: |
||||
# TODO: Understand why go vet downloads "github.com/google/go-cmp v0.2.0"
|
||||
@VETOUT=`$(GOVET) ./... | grep -v "go: downloading" 2>&1`; \
|
||||
if [ "$$VETOUT" ]; then \
|
||||
echo "$(GOVET) FAILED => go vet the following files:\n"; \
|
||||
echo "$$VETOUT\n"; \
|
||||
exit 1; \
|
||||
else \
|
||||
echo "Vet finished successfully"; \
|
||||
fi
|
||||
|
||||
.PHONY: embedmd |
||||
embedmd: |
||||
@EMBEDMDOUT=`$(EMBEDMD) -d $(README_FILES) 2>&1`; \
|
||||
if [ "$$EMBEDMDOUT" ]; then \
|
||||
echo "$(EMBEDMD) FAILED => embedmd the following files:\n"; \
|
||||
echo "$$EMBEDMDOUT\n"; \
|
||||
exit 1; \
|
||||
else \
|
||||
echo "Embedmd finished successfully"; \
|
||||
fi
|
||||
|
||||
.PHONY: install-tools |
||||
install-tools: |
||||
go get -u golang.org/x/tools/cmd/cover
|
||||
go get -u golang.org/x/lint/golint
|
||||
go get -u github.com/rakyll/embedmd
|
||||
@ -0,0 +1,263 @@
|
||||
# OpenCensus Libraries for Go |
||||
|
||||
[![Build Status][travis-image]][travis-url] |
||||
[![Windows Build Status][appveyor-image]][appveyor-url] |
||||
[![GoDoc][godoc-image]][godoc-url] |
||||
[![Gitter chat][gitter-image]][gitter-url] |
||||
|
||||
OpenCensus Go is a Go implementation of OpenCensus, a toolkit for |
||||
collecting application performance and behavior monitoring data. |
||||
Currently it consists of three major components: tags, stats and tracing. |
||||
|
||||
## Installation |
||||
|
||||
``` |
||||
$ go get -u go.opencensus.io |
||||
``` |
||||
|
||||
The API of this project is still evolving, see: [Deprecation Policy](#deprecation-policy). |
||||
The use of vendoring or a dependency management tool is recommended. |
||||
|
||||
## Prerequisites |
||||
|
||||
OpenCensus Go libraries require Go 1.8 or later. |
||||
|
||||
## Getting Started |
||||
|
||||
The easiest way to get started using OpenCensus in your application is to use an existing |
||||
integration with your RPC framework: |
||||
|
||||
* [net/http](https://godoc.org/go.opencensus.io/plugin/ochttp) |
||||
* [gRPC](https://godoc.org/go.opencensus.io/plugin/ocgrpc) |
||||
* [database/sql](https://godoc.org/github.com/opencensus-integrations/ocsql) |
||||
* [Go kit](https://godoc.org/github.com/go-kit/kit/tracing/opencensus) |
||||
* [Groupcache](https://godoc.org/github.com/orijtech/groupcache) |
||||
* [Caddy webserver](https://godoc.org/github.com/orijtech/caddy) |
||||
* [MongoDB](https://godoc.org/github.com/orijtech/mongo-go-driver) |
||||
* [Redis gomodule/redigo](https://godoc.org/github.com/orijtech/redigo) |
||||
* [Redis goredis/redis](https://godoc.org/github.com/orijtech/redis) |
||||
* [Memcache](https://godoc.org/github.com/orijtech/gomemcache) |
||||
|
||||
If you're using a framework not listed here, you could either implement your own middleware for your |
||||
framework or use [custom stats](#stats) and [spans](#spans) directly in your application. |
||||
|
||||
## Exporters |
||||
|
||||
OpenCensus can export instrumentation data to various backends. |
||||
OpenCensus has exporter implementations for the following, users |
||||
can implement their own exporters by implementing the exporter interfaces |
||||
([stats](https://godoc.org/go.opencensus.io/stats/view#Exporter), |
||||
[trace](https://godoc.org/go.opencensus.io/trace#Exporter)): |
||||
|
||||
* [Prometheus][exporter-prom] for stats |
||||
* [OpenZipkin][exporter-zipkin] for traces |
||||
* [Stackdriver][exporter-stackdriver] Monitoring for stats and Trace for traces |
||||
* [Jaeger][exporter-jaeger] for traces |
||||
* [AWS X-Ray][exporter-xray] for traces |
||||
* [Datadog][exporter-datadog] for stats and traces |
||||
* [Graphite][exporter-graphite] for stats |
||||
* [Honeycomb][exporter-honeycomb] for traces |
||||
|
||||
## Overview |
||||
|
||||
 |
||||
|
||||
In a microservices environment, a user request may go through |
||||
multiple services until there is a response. OpenCensus allows |
||||
you to instrument your services and collect diagnostics data all |
||||
through your services end-to-end. |
||||
|
||||
## Tags |
||||
|
||||
Tags represent propagated key-value pairs. They are propagated using `context.Context` |
||||
in the same process or can be encoded to be transmitted on the wire. Usually, this will |
||||
be handled by an integration plugin, e.g. `ocgrpc.ServerHandler` and `ocgrpc.ClientHandler` |
||||
for gRPC. |
||||
|
||||
Package `tag` allows adding or modifying tags in the current context. |
||||
|
||||
[embedmd]:# (internal/readme/tags.go new) |
||||
```go |
||||
ctx, err = tag.New(ctx, |
||||
tag.Insert(osKey, "macOS-10.12.5"), |
||||
tag.Upsert(userIDKey, "cde36753ed"), |
||||
) |
||||
if err != nil { |
||||
log.Fatal(err) |
||||
} |
||||
``` |
||||
|
||||
## Stats |
||||
|
||||
OpenCensus is a low-overhead framework even if instrumentation is always enabled. |
||||
In order to be so, it is optimized to make recording of data points fast |
||||
and separate from the data aggregation. |
||||
|
||||
OpenCensus stats collection happens in two stages: |
||||
|
||||
* Definition of measures and recording of data points |
||||
* Definition of views and aggregation of the recorded data |
||||
|
||||
### Recording |
||||
|
||||
Measurements are data points associated with a measure. |
||||
Recording implicitly tags the set of Measurements with the tags from the |
||||
provided context: |
||||
|
||||
[embedmd]:# (internal/readme/stats.go record) |
||||
```go |
||||
stats.Record(ctx, videoSize.M(102478)) |
||||
``` |
||||
|
||||
### Views |
||||
|
||||
Views are how Measures are aggregated. You can think of them as queries over the |
||||
set of recorded data points (measurements). |
||||
|
||||
Views have two parts: the tags to group by and the aggregation type used. |
||||
|
||||
Currently three types of aggregations are supported: |
||||
* CountAggregation is used to count the number of times a sample was recorded. |
||||
* DistributionAggregation is used to provide a histogram of the values of the samples. |
||||
* SumAggregation is used to sum up all sample values. |
||||
|
||||
[embedmd]:# (internal/readme/stats.go aggs) |
||||
```go |
||||
distAgg := view.Distribution(1<<32, 2<<32, 3<<32) |
||||
countAgg := view.Count() |
||||
sumAgg := view.Sum() |
||||
``` |
||||
|
||||
Here we create a view with the DistributionAggregation over our measure. |
||||
|
||||
[embedmd]:# (internal/readme/stats.go view) |
||||
```go |
||||
if err := view.Register(&view.View{ |
||||
Name: "example.com/video_size_distribution", |
||||
Description: "distribution of processed video size over time", |
||||
Measure: videoSize, |
||||
Aggregation: view.Distribution(1<<32, 2<<32, 3<<32), |
||||
}); err != nil { |
||||
log.Fatalf("Failed to register view: %v", err) |
||||
} |
||||
``` |
||||
|
||||
Register begins collecting data for the view. Registered views' data will be |
||||
exported via the registered exporters. |
||||
|
||||
## Traces |
||||
|
||||
A distributed trace tracks the progression of a single user request as |
||||
it is handled by the services and processes that make up an application. |
||||
Each step is called a span in the trace. Spans include metadata about the step, |
||||
including especially the time spent in the step, called the span’s latency. |
||||
|
||||
Below you see a trace and several spans underneath it. |
||||
|
||||
 |
||||
|
||||
### Spans |
||||
|
||||
Span is the unit step in a trace. Each span has a name, latency, status and |
||||
additional metadata. |
||||
|
||||
Below we are starting a span for a cache read and ending it |
||||
when we are done: |
||||
|
||||
[embedmd]:# (internal/readme/trace.go startend) |
||||
```go |
||||
ctx, span := trace.StartSpan(ctx, "cache.Get") |
||||
defer span.End() |
||||
|
||||
// Do work to get from cache. |
||||
``` |
||||
|
||||
### Propagation |
||||
|
||||
Spans can have parents or can be root spans if they don't have any parents. |
||||
The current span is propagated in-process and across the network to allow associating |
||||
new child spans with the parent. |
||||
|
||||
In the same process, `context.Context` is used to propagate spans. |
||||
`trace.StartSpan` creates a new span as a root if the current context |
||||
doesn't contain a span. Or, it creates a child of the span that is |
||||
already in current context. The returned context can be used to keep |
||||
propagating the newly created span in the current context. |
||||
|
||||
[embedmd]:# (internal/readme/trace.go startend) |
||||
```go |
||||
ctx, span := trace.StartSpan(ctx, "cache.Get") |
||||
defer span.End() |
||||
|
||||
// Do work to get from cache. |
||||
``` |
||||
|
||||
Across the network, OpenCensus provides different propagation |
||||
methods for different protocols. |
||||
|
||||
* gRPC integrations use the OpenCensus' [binary propagation format](https://godoc.org/go.opencensus.io/trace/propagation). |
||||
* HTTP integrations use Zipkin's [B3](https://github.com/openzipkin/b3-propagation) |
||||
by default but can be configured to use a custom propagation method by setting another |
||||
[propagation.HTTPFormat](https://godoc.org/go.opencensus.io/trace/propagation#HTTPFormat). |
||||
|
||||
## Execution Tracer |
||||
|
||||
With Go 1.11, OpenCensus Go will support integration with the Go execution tracer. |
||||
See [Debugging Latency in Go](https://medium.com/observability/debugging-latency-in-go-1-11-9f97a7910d68) |
||||
for an example of their mutual use. |
||||
|
||||
## Profiles |
||||
|
||||
OpenCensus tags can be applied as profiler labels |
||||
for users who are on Go 1.9 and above. |
||||
|
||||
[embedmd]:# (internal/readme/tags.go profiler) |
||||
```go |
||||
ctx, err = tag.New(ctx, |
||||
tag.Insert(osKey, "macOS-10.12.5"), |
||||
tag.Insert(userIDKey, "fff0989878"), |
||||
) |
||||
if err != nil { |
||||
log.Fatal(err) |
||||
} |
||||
tag.Do(ctx, func(ctx context.Context) { |
||||
// Do work. |
||||
// When profiling is on, samples will be |
||||
// recorded with the key/values from the tag map. |
||||
}) |
||||
``` |
||||
|
||||
A screenshot of the CPU profile from the program above: |
||||
|
||||
 |
||||
|
||||
## Deprecation Policy |
||||
|
||||
Before version 1.0.0, the following deprecation policy will be observed: |
||||
|
||||
No backwards-incompatible changes will be made except for the removal of symbols that have |
||||
been marked as *Deprecated* for at least one minor release (e.g. 0.9.0 to 0.10.0). A release |
||||
removing the *Deprecated* functionality will be made no sooner than 28 days after the first |
||||
release in which the functionality was marked *Deprecated*. |
||||
|
||||
[travis-image]: https://travis-ci.org/census-instrumentation/opencensus-go.svg?branch=master |
||||
[travis-url]: https://travis-ci.org/census-instrumentation/opencensus-go |
||||
[appveyor-image]: https://ci.appveyor.com/api/projects/status/vgtt29ps1783ig38?svg=true |
||||
[appveyor-url]: https://ci.appveyor.com/project/opencensusgoteam/opencensus-go/branch/master |
||||
[godoc-image]: https://godoc.org/go.opencensus.io?status.svg |
||||
[godoc-url]: https://godoc.org/go.opencensus.io |
||||
[gitter-image]: https://badges.gitter.im/census-instrumentation/lobby.svg |
||||
[gitter-url]: https://gitter.im/census-instrumentation/lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge |
||||
|
||||
|
||||
[new-ex]: https://godoc.org/go.opencensus.io/tag#example-NewMap |
||||
[new-replace-ex]: https://godoc.org/go.opencensus.io/tag#example-NewMap--Replace |
||||
|
||||
[exporter-prom]: https://godoc.org/go.opencensus.io/exporter/prometheus |
||||
[exporter-stackdriver]: https://godoc.org/contrib.go.opencensus.io/exporter/stackdriver |
||||
[exporter-zipkin]: https://godoc.org/go.opencensus.io/exporter/zipkin |
||||
[exporter-jaeger]: https://godoc.org/go.opencensus.io/exporter/jaeger |
||||
[exporter-xray]: https://github.com/census-ecosystem/opencensus-go-exporter-aws |
||||
[exporter-datadog]: https://github.com/DataDog/opencensus-go-exporter-datadog |
||||
[exporter-graphite]: https://github.com/census-ecosystem/opencensus-go-exporter-graphite |
||||
[exporter-honeycomb]: https://github.com/honeycombio/opencensus-exporter |
||||
@ -0,0 +1,25 @@
|
||||
version: "{build}" |
||||
|
||||
platform: x64 |
||||
|
||||
clone_folder: c:\gopath\src\go.opencensus.io |
||||
|
||||
environment: |
||||
GOPATH: 'c:\gopath' |
||||
GOVERSION: '1.11' |
||||
GO111MODULE: 'on' |
||||
CGO_ENABLED: '0' # See: https://github.com/appveyor/ci/issues/2613 |
||||
|
||||
install: |
||||
- set PATH=%GOPATH%\bin;c:\go\bin;%PATH% |
||||
- choco upgrade golang --version 1.11.5 # Temporary fix because of a go.sum bug in 1.11 |
||||
- go version |
||||
- go env |
||||
|
||||
build: false |
||||
deploy: false |
||||
|
||||
test_script: |
||||
- cd %APPVEYOR_BUILD_FOLDER% |
||||
- go build -v .\... |
||||
- go test -v .\... # No -race because cgo is disabled |
||||
@ -0,0 +1,10 @@
|
||||
module go.opencensus.io |
||||
|
||||
require ( |
||||
github.com/golang/protobuf v1.2.0 |
||||
github.com/google/go-cmp v0.2.0 |
||||
github.com/hashicorp/golang-lru v0.5.0 |
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a |
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19 // indirect |
||||
google.golang.org/grpc v1.19.0 |
||||
) |
||||
@ -0,0 +1,50 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= |
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= |
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= |
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= |
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= |
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= |
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= |
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= |
||||
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= |
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= |
||||
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= |
||||
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= |
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= |
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= |
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= |
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= |
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= |
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= |
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= |
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= |
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= |
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= |
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= |
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= |
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= |
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= |
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 h1:bjcUS9ztw9kFmmIxJInhon/0Is3p+EHBKNgquIzo1OI= |
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= |
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= |
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= |
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= |
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= |
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= |
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= |
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= |
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= |
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= |
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= |
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19 h1:Lj2SnHtxkRGJDqnGaSjo+CCdIieEnwVazbOXILwQemk= |
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= |
||||
google.golang.org/grpc v1.19.0 h1:cfg4PD8YEdSFnm7qLV4++93WcmhH2nIUhMjhdCvl3j8= |
||||
google.golang.org/grpc v1.19.0 h1:cfg4PD8YEdSFnm7qLV4++93WcmhH2nIUhMjhdCvl3j8= |
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= |
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= |
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= |
||||
@ -0,0 +1,37 @@
|
||||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// 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 internal // import "go.opencensus.io/internal"
|
||||
|
||||
import ( |
||||
"fmt" |
||||
"time" |
||||
|
||||
opencensus "go.opencensus.io" |
||||
) |
||||
|
||||
// UserAgent is the user agent to be added to the outgoing
|
||||
// requests from the exporters.
|
||||
var UserAgent = fmt.Sprintf("opencensus-go/%s", opencensus.Version()) |
||||
|
||||
// MonotonicEndTime returns the end time at present
|
||||
// but offset from start, monotonically.
|
||||
//
|
||||
// The monotonic clock is used in subtractions hence
|
||||
// the duration since start added back to start gives
|
||||
// end as a monotonic time.
|
||||
// See https://golang.org/pkg/time/#hdr-Monotonic_Clocks
|
||||
func MonotonicEndTime(start time.Time) time.Time { |
||||
return start.Add(time.Now().Sub(start)) |
||||
} |
||||
@ -0,0 +1,50 @@
|
||||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// 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 internal |
||||
|
||||
import ( |
||||
"strings" |
||||
"unicode" |
||||
) |
||||
|
||||
const labelKeySizeLimit = 100 |
||||
|
||||
// Sanitize returns a string that is trunacated to 100 characters if it's too
|
||||
// long, and replaces non-alphanumeric characters to underscores.
|
||||
func Sanitize(s string) string { |
||||
if len(s) == 0 { |
||||
return s |
||||
} |
||||
if len(s) > labelKeySizeLimit { |
||||
s = s[:labelKeySizeLimit] |
||||
} |
||||
s = strings.Map(sanitizeRune, s) |
||||
if unicode.IsDigit(rune(s[0])) { |
||||
s = "key_" + s |
||||
} |
||||
if s[0] == '_' { |
||||
s = "key" + s |
||||
} |
||||
return s |
||||
} |
||||
|
||||
// converts anything that is not a letter or digit to an underscore
|
||||
func sanitizeRune(r rune) rune { |
||||
if unicode.IsLetter(r) || unicode.IsDigit(r) { |
||||
return r |
||||
} |
||||
// Everything else turns into an underscore
|
||||
return '_' |
||||
} |
||||
@ -0,0 +1,75 @@
|
||||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// 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 tagencoding contains the tag encoding
|
||||
// used interally by the stats collector.
|
||||
package tagencoding // import "go.opencensus.io/internal/tagencoding"
|
||||
|
||||
// Values represent the encoded buffer for the values.
|
||||
type Values struct { |
||||
Buffer []byte |
||||
WriteIndex int |
||||
ReadIndex int |
||||
} |
||||
|
||||
func (vb *Values) growIfRequired(expected int) { |
||||
if len(vb.Buffer)-vb.WriteIndex < expected { |
||||
tmp := make([]byte, 2*(len(vb.Buffer)+1)+expected) |
||||
copy(tmp, vb.Buffer) |
||||
vb.Buffer = tmp |
||||
} |
||||
} |
||||
|
||||
// WriteValue is the helper method to encode Values from map[Key][]byte.
|
||||
func (vb *Values) WriteValue(v []byte) { |
||||
length := len(v) & 0xff |
||||
vb.growIfRequired(1 + length) |
||||
|
||||
// writing length of v
|
||||
vb.Buffer[vb.WriteIndex] = byte(length) |
||||
vb.WriteIndex++ |
||||
|
||||
if length == 0 { |
||||
// No value was encoded for this key
|
||||
return |
||||
} |
||||
|
||||
// writing v
|
||||
copy(vb.Buffer[vb.WriteIndex:], v[:length]) |
||||
vb.WriteIndex += length |
||||
} |
||||
|
||||
// ReadValue is the helper method to decode Values to a map[Key][]byte.
|
||||
func (vb *Values) ReadValue() []byte { |
||||
// read length of v
|
||||
length := int(vb.Buffer[vb.ReadIndex]) |
||||
vb.ReadIndex++ |
||||
if length == 0 { |
||||
// No value was encoded for this key
|
||||
return nil |
||||
} |
||||
|
||||
// read value of v
|
||||
v := make([]byte, length) |
||||
endIdx := vb.ReadIndex + length |
||||
copy(v, vb.Buffer[vb.ReadIndex:endIdx]) |
||||
vb.ReadIndex = endIdx |
||||
return v |
||||
} |
||||
|
||||
// Bytes returns a reference to already written bytes in the Buffer.
|
||||
func (vb *Values) Bytes() []byte { |
||||
return vb.Buffer[:vb.WriteIndex] |
||||
} |
||||
@ -0,0 +1,53 @@
|
||||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// 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 internal |
||||
|
||||
import ( |
||||
"time" |
||||
) |
||||
|
||||
// Trace allows internal access to some trace functionality.
|
||||
// TODO(#412): remove this
|
||||
var Trace interface{} |
||||
|
||||
// LocalSpanStoreEnabled true if the local span store is enabled.
|
||||
var LocalSpanStoreEnabled bool |
||||
|
||||
// BucketConfiguration stores the number of samples to store for span buckets
|
||||
// for successful and failed spans for a particular span name.
|
||||
type BucketConfiguration struct { |
||||
Name string |
||||
MaxRequestsSucceeded int |
||||
MaxRequestsErrors int |
||||
} |
||||
|
||||
// PerMethodSummary is a summary of the spans stored for a single span name.
|
||||
type PerMethodSummary struct { |
||||
Active int |
||||
LatencyBuckets []LatencyBucketSummary |
||||
ErrorBuckets []ErrorBucketSummary |
||||
} |
||||
|
||||
// LatencyBucketSummary is a summary of a latency bucket.
|
||||
type LatencyBucketSummary struct { |
||||
MinLatency, MaxLatency time.Duration |
||||
Size int |
||||
} |
||||
|
||||
// ErrorBucketSummary is a summary of an error bucket.
|
||||
type ErrorBucketSummary struct { |
||||
ErrorCode int32 |
||||
Size int |
||||
} |
||||
@ -0,0 +1,19 @@
|
||||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// 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 metricdata contains the metrics data model.
|
||||
//
|
||||
// This is an EXPERIMENTAL package, and may change in arbitrary ways without
|
||||
// notice.
|
||||
package metricdata // import "go.opencensus.io/metric/metricdata"
|
||||
@ -0,0 +1,38 @@
|
||||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// 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 metricdata |
||||
|
||||
import ( |
||||
"time" |
||||
) |
||||
|
||||
// Exemplars keys.
|
||||
const ( |
||||
AttachmentKeySpanContext = "SpanContext" |
||||
) |
||||
|
||||
// Exemplar is an example data point associated with each bucket of a
|
||||
// distribution type aggregation.
|
||||
//
|
||||
// Their purpose is to provide an example of the kind of thing
|
||||
// (request, RPC, trace span, etc.) that resulted in that measurement.
|
||||
type Exemplar struct { |
||||
Value float64 // the value that was recorded
|
||||
Timestamp time.Time // the time the value was recorded
|
||||
Attachments Attachments // attachments (if any)
|
||||
} |
||||
|
||||
// Attachments is a map of extra values associated with a recorded data point.
|
||||
type Attachments map[string]interface{} |
||||
@ -0,0 +1,35 @@
|
||||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// 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 metricdata |
||||
|
||||
// LabelKey represents key of a label. It has optional
|
||||
// description attribute.
|
||||
type LabelKey struct { |
||||
Key string |
||||
Description string |
||||
} |
||||
|
||||
// LabelValue represents the value of a label.
|
||||
// The zero value represents a missing label value, which may be treated
|
||||
// differently to an empty string value by some back ends.
|
||||
type LabelValue struct { |
||||
Value string // string value of the label
|
||||
Present bool // flag that indicated whether a value is present or not
|
||||
} |
||||
|
||||
// NewLabelValue creates a new non-nil LabelValue that represents the given string.
|
||||
func NewLabelValue(val string) LabelValue { |
||||
return LabelValue{Value: val, Present: true} |
||||
} |
||||
@ -0,0 +1,46 @@
|
||||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// 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 metricdata |
||||
|
||||
import ( |
||||
"time" |
||||
|
||||
"go.opencensus.io/resource" |
||||
) |
||||
|
||||
// Descriptor holds metadata about a metric.
|
||||
type Descriptor struct { |
||||
Name string // full name of the metric
|
||||
Description string // human-readable description
|
||||
Unit Unit // units for the measure
|
||||
Type Type // type of measure
|
||||
LabelKeys []LabelKey // label keys
|
||||
} |
||||
|
||||
// Metric represents a quantity measured against a resource with different
|
||||
// label value combinations.
|
||||
type Metric struct { |
||||
Descriptor Descriptor // metric descriptor
|
||||
Resource *resource.Resource // resource against which this was measured
|
||||
TimeSeries []*TimeSeries // one time series for each combination of label values
|
||||
} |
||||
|
||||
// TimeSeries is a sequence of points associated with a combination of label
|
||||
// values.
|
||||
type TimeSeries struct { |
||||
LabelValues []LabelValue // label values, same order as keys in the metric descriptor
|
||||
Points []Point // points sequence
|
||||
StartTime time.Time // time we started recording this time series
|
||||
} |
||||
@ -0,0 +1,193 @@
|
||||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// 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 metricdata |
||||
|
||||
import ( |
||||
"time" |
||||
) |
||||
|
||||
// Point is a single data point of a time series.
|
||||
type Point struct { |
||||
// Time is the point in time that this point represents in a time series.
|
||||
Time time.Time |
||||
// Value is the value of this point. Prefer using ReadValue to switching on
|
||||
// the value type, since new value types might be added.
|
||||
Value interface{} |
||||
} |
||||
|
||||
//go:generate stringer -type ValueType
|
||||
|
||||
// NewFloat64Point creates a new Point holding a float64 value.
|
||||
func NewFloat64Point(t time.Time, val float64) Point { |
||||
return Point{ |
||||
Value: val, |
||||
Time: t, |
||||
} |
||||
} |
||||
|
||||
// NewInt64Point creates a new Point holding an int64 value.
|
||||
func NewInt64Point(t time.Time, val int64) Point { |
||||
return Point{ |
||||
Value: val, |
||||
Time: t, |
||||
} |
||||
} |
||||
|
||||
// NewDistributionPoint creates a new Point holding a Distribution value.
|
||||
func NewDistributionPoint(t time.Time, val *Distribution) Point { |
||||
return Point{ |
||||
Value: val, |
||||
Time: t, |
||||
} |
||||
} |
||||
|
||||
// NewSummaryPoint creates a new Point holding a Summary value.
|
||||
func NewSummaryPoint(t time.Time, val *Summary) Point { |
||||
return Point{ |
||||
Value: val, |
||||
Time: t, |
||||
} |
||||
} |
||||
|
||||
// ValueVisitor allows reading the value of a point.
|
||||
type ValueVisitor interface { |
||||
VisitFloat64Value(float64) |
||||
VisitInt64Value(int64) |
||||
VisitDistributionValue(*Distribution) |
||||
VisitSummaryValue(*Summary) |
||||
} |
||||
|
||||
// ReadValue accepts a ValueVisitor and calls the appropriate method with the
|
||||
// value of this point.
|
||||
// Consumers of Point should use this in preference to switching on the type
|
||||
// of the value directly, since new value types may be added.
|
||||
func (p Point) ReadValue(vv ValueVisitor) { |
||||
switch v := p.Value.(type) { |
||||
case int64: |
||||
vv.VisitInt64Value(v) |
||||
case float64: |
||||
vv.VisitFloat64Value(v) |
||||
case *Distribution: |
||||
vv.VisitDistributionValue(v) |
||||
case *Summary: |
||||
vv.VisitSummaryValue(v) |
||||
default: |
||||
panic("unexpected value type") |
||||
} |
||||
} |
||||
|
||||
// Distribution contains summary statistics for a population of values. It
|
||||
// optionally contains a histogram representing the distribution of those
|
||||
// values across a set of buckets.
|
||||
type Distribution struct { |
||||
// Count is the number of values in the population. Must be non-negative. This value
|
||||
// must equal the sum of the values in bucket_counts if a histogram is
|
||||
// provided.
|
||||
Count int64 |
||||
// Sum is the sum of the values in the population. If count is zero then this field
|
||||
// must be zero.
|
||||
Sum float64 |
||||
// SumOfSquaredDeviation is the sum of squared deviations from the mean of the values in the
|
||||
// population. For values x_i this is:
|
||||
//
|
||||
// Sum[i=1..n]((x_i - mean)^2)
|
||||
//
|
||||
// Knuth, "The Art of Computer Programming", Vol. 2, page 323, 3rd edition
|
||||
// describes Welford's method for accumulating this sum in one pass.
|
||||
//
|
||||
// If count is zero then this field must be zero.
|
||||
SumOfSquaredDeviation float64 |
||||
// BucketOptions describes the bounds of the histogram buckets in this
|
||||
// distribution.
|
||||
//
|
||||
// A Distribution may optionally contain a histogram of the values in the
|
||||
// population.
|
||||
//
|
||||
// If nil, there is no associated histogram.
|
||||
BucketOptions *BucketOptions |
||||
// Bucket If the distribution does not have a histogram, then omit this field.
|
||||
// If there is a histogram, then the sum of the values in the Bucket counts
|
||||
// must equal the value in the count field of the distribution.
|
||||
Buckets []Bucket |
||||
} |
||||
|
||||
// BucketOptions describes the bounds of the histogram buckets in this
|
||||
// distribution.
|
||||
type BucketOptions struct { |
||||
// Bounds specifies a set of bucket upper bounds.
|
||||
// This defines len(bounds) + 1 (= N) buckets. The boundaries for bucket
|
||||
// index i are:
|
||||
//
|
||||
// [0, Bounds[i]) for i == 0
|
||||
// [Bounds[i-1], Bounds[i]) for 0 < i < N-1
|
||||
// [Bounds[i-1], +infinity) for i == N-1
|
||||
Bounds []float64 |
||||
} |
||||
|
||||
// Bucket represents a single bucket (value range) in a distribution.
|
||||
type Bucket struct { |
||||
// Count is the number of values in each bucket of the histogram, as described in
|
||||
// bucket_bounds.
|
||||
Count int64 |
||||
// Exemplar associated with this bucket (if any).
|
||||
Exemplar *Exemplar |
||||
} |
||||
|
||||
// Summary is a representation of percentiles.
|
||||
type Summary struct { |
||||
// Count is the cumulative count (if available).
|
||||
Count int64 |
||||
// Sum is the cumulative sum of values (if available).
|
||||
Sum float64 |
||||
// HasCountAndSum is true if Count and Sum are available.
|
||||
HasCountAndSum bool |
||||
// Snapshot represents percentiles calculated over an arbitrary time window.
|
||||
// The values in this struct can be reset at arbitrary unknown times, with
|
||||
// the requirement that all of them are reset at the same time.
|
||||
Snapshot Snapshot |
||||
} |
||||
|
||||
// Snapshot represents percentiles over an arbitrary time.
|
||||
// The values in this struct can be reset at arbitrary unknown times, with
|
||||
// the requirement that all of them are reset at the same time.
|
||||
type Snapshot struct { |
||||
// Count is the number of values in the snapshot. Optional since some systems don't
|
||||
// expose this. Set to 0 if not available.
|
||||
Count int64 |
||||
// Sum is the sum of values in the snapshot. Optional since some systems don't
|
||||
// expose this. If count is 0 then this field must be zero.
|
||||
Sum float64 |
||||
// Percentiles is a map from percentile (range (0-100.0]) to the value of
|
||||
// the percentile.
|
||||
Percentiles map[float64]float64 |
||||
} |
||||
|
||||
//go:generate stringer -type Type
|
||||
|
||||
// Type is the overall type of metric, including its value type and whether it
|
||||
// represents a cumulative total (since the start time) or if it represents a
|
||||
// gauge value.
|
||||
type Type int |
||||
|
||||
// Metric types.
|
||||
const ( |
||||
TypeGaugeInt64 Type = iota |
||||
TypeGaugeFloat64 |
||||
TypeGaugeDistribution |
||||
TypeCumulativeInt64 |
||||
TypeCumulativeFloat64 |
||||
TypeCumulativeDistribution |
||||
TypeSummary |
||||
) |
||||
@ -0,0 +1,16 @@
|
||||
// Code generated by "stringer -type Type"; DO NOT EDIT.
|
||||
|
||||
package metricdata |
||||
|
||||
import "strconv" |
||||
|
||||
const _Type_name = "TypeGaugeInt64TypeGaugeFloat64TypeGaugeDistributionTypeCumulativeInt64TypeCumulativeFloat64TypeCumulativeDistributionTypeSummary" |
||||
|
||||
var _Type_index = [...]uint8{0, 14, 30, 51, 70, 91, 117, 128} |
||||
|
||||
func (i Type) String() string { |
||||
if i < 0 || i >= Type(len(_Type_index)-1) { |
||||
return "Type(" + strconv.FormatInt(int64(i), 10) + ")" |
||||
} |
||||
return _Type_name[_Type_index[i]:_Type_index[i+1]] |
||||
} |
||||
@ -0,0 +1,27 @@
|
||||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// 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 metricdata |
||||
|
||||
// Unit is a string encoded according to the case-sensitive abbreviations from the
|
||||
// Unified Code for Units of Measure: http://unitsofmeasure.org/ucum.html
|
||||
type Unit string |
||||
|
||||
// Predefined units. To record against a unit not represented here, create your
|
||||
// own Unit type constant from a string.
|
||||
const ( |
||||
UnitDimensionless Unit = "1" |
||||
UnitBytes Unit = "By" |
||||
UnitMilliseconds Unit = "ms" |
||||
) |
||||
@ -0,0 +1,78 @@
|
||||
// Copyright 2019, OpenCensus Authors
|
||||
//
|
||||
// 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 metricproducer |
||||
|
||||
import ( |
||||
"sync" |
||||
) |
||||
|
||||
// Manager maintains a list of active producers. Producers can register
|
||||
// with the manager to allow readers to read all metrics provided by them.
|
||||
// Readers can retrieve all producers registered with the manager,
|
||||
// read metrics from the producers and export them.
|
||||
type Manager struct { |
||||
mu sync.RWMutex |
||||
producers map[Producer]struct{} |
||||
} |
||||
|
||||
var prodMgr *Manager |
||||
var once sync.Once |
||||
|
||||
// GlobalManager is a single instance of producer manager
|
||||
// that is used by all producers and all readers.
|
||||
func GlobalManager() *Manager { |
||||
once.Do(func() { |
||||
prodMgr = &Manager{} |
||||
prodMgr.producers = make(map[Producer]struct{}) |
||||
}) |
||||
return prodMgr |
||||
} |
||||
|
||||
// AddProducer adds the producer to the Manager if it is not already present.
|
||||
func (pm *Manager) AddProducer(producer Producer) { |
||||
if producer == nil { |
||||
return |
||||
} |
||||
pm.mu.Lock() |
||||
defer pm.mu.Unlock() |
||||
pm.producers[producer] = struct{}{} |
||||
} |
||||
|
||||
// DeleteProducer deletes the producer from the Manager if it is present.
|
||||
func (pm *Manager) DeleteProducer(producer Producer) { |
||||
if producer == nil { |
||||
return |
||||
} |
||||
pm.mu.Lock() |
||||
defer pm.mu.Unlock() |
||||
delete(pm.producers, producer) |
||||
} |
||||
|
||||
// GetAll returns a slice of all producer currently registered with
|
||||
// the Manager. For each call it generates a new slice. The slice
|
||||
// should not be cached as registration may change at any time. It is
|
||||
// typically called periodically by exporter to read metrics from
|
||||
// the producers.
|
||||
func (pm *Manager) GetAll() []Producer { |
||||
pm.mu.Lock() |
||||
defer pm.mu.Unlock() |
||||
producers := make([]Producer, len(pm.producers)) |
||||
i := 0 |
||||
for producer := range pm.producers { |
||||
producers[i] = producer |
||||
i++ |
||||
} |
||||
return producers |
||||
} |
||||
@ -0,0 +1,28 @@
|
||||
// Copyright 2019, OpenCensus Authors
|
||||
//
|
||||
// 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 metricproducer |
||||
|
||||
import ( |
||||
"go.opencensus.io/metric/metricdata" |
||||
) |
||||
|
||||
// Producer is a source of metrics.
|
||||
type Producer interface { |
||||
// Read should return the current values of all metrics supported by this
|
||||
// metric provider.
|
||||
// The returned metrics should be unique for each combination of name and
|
||||
// resource.
|
||||
Read() []*metricdata.Metric |
||||
} |
||||
@ -0,0 +1,21 @@
|
||||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// 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 opencensus contains Go support for OpenCensus.
|
||||
package opencensus // import "go.opencensus.io"
|
||||
|
||||
// Version is the current release version of OpenCensus in use.
|
||||
func Version() string { |
||||
return "0.21.0" |
||||
} |
||||
@ -0,0 +1,117 @@
|
||||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// 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 ochttp |
||||
|
||||
import ( |
||||
"net/http" |
||||
"net/http/httptrace" |
||||
|
||||
"go.opencensus.io/trace" |
||||
"go.opencensus.io/trace/propagation" |
||||
) |
||||
|
||||
// Transport is an http.RoundTripper that instruments all outgoing requests with
|
||||
// OpenCensus stats and tracing.
|
||||
//
|
||||
// The zero value is intended to be a useful default, but for
|
||||
// now it's recommended that you explicitly set Propagation, since the default
|
||||
// for this may change.
|
||||
type Transport struct { |
||||
// Base may be set to wrap another http.RoundTripper that does the actual
|
||||
// requests. By default http.DefaultTransport is used.
|
||||
//
|
||||
// If base HTTP roundtripper implements CancelRequest,
|
||||
// the returned round tripper will be cancelable.
|
||||
Base http.RoundTripper |
||||
|
||||
// Propagation defines how traces are propagated. If unspecified, a default
|
||||
// (currently B3 format) will be used.
|
||||
Propagation propagation.HTTPFormat |
||||
|
||||
// StartOptions are applied to the span started by this Transport around each
|
||||
// request.
|
||||
//
|
||||
// StartOptions.SpanKind will always be set to trace.SpanKindClient
|
||||
// for spans started by this transport.
|
||||
StartOptions trace.StartOptions |
||||
|
||||
// GetStartOptions allows to set start options per request. If set,
|
||||
// StartOptions is going to be ignored.
|
||||
GetStartOptions func(*http.Request) trace.StartOptions |
||||
|
||||
// NameFromRequest holds the function to use for generating the span name
|
||||
// from the information found in the outgoing HTTP Request. By default the
|
||||
// name equals the URL Path.
|
||||
FormatSpanName func(*http.Request) string |
||||
|
||||
// NewClientTrace may be set to a function allowing the current *trace.Span
|
||||
// to be annotated with HTTP request event information emitted by the
|
||||
// httptrace package.
|
||||
NewClientTrace func(*http.Request, *trace.Span) *httptrace.ClientTrace |
||||
|
||||
// TODO: Implement tag propagation for HTTP.
|
||||
} |
||||
|
||||
// RoundTrip implements http.RoundTripper, delegating to Base and recording stats and traces for the request.
|
||||
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) { |
||||
rt := t.base() |
||||
if isHealthEndpoint(req.URL.Path) { |
||||
return rt.RoundTrip(req) |
||||
} |
||||
// TODO: remove excessive nesting of http.RoundTrippers here.
|
||||
format := t.Propagation |
||||
if format == nil { |
||||
format = defaultFormat |
||||
} |
||||
spanNameFormatter := t.FormatSpanName |
||||
if spanNameFormatter == nil { |
||||
spanNameFormatter = spanNameFromURL |
||||
} |
||||
|
||||
startOpts := t.StartOptions |
||||
if t.GetStartOptions != nil { |
||||
startOpts = t.GetStartOptions(req) |
||||
} |
||||
|
||||
rt = &traceTransport{ |
||||
base: rt, |
||||
format: format, |
||||
startOptions: trace.StartOptions{ |
||||
Sampler: startOpts.Sampler, |
||||
SpanKind: trace.SpanKindClient, |
||||
}, |
||||
formatSpanName: spanNameFormatter, |
||||
newClientTrace: t.NewClientTrace, |
||||
} |
||||
rt = statsTransport{base: rt} |
||||
return rt.RoundTrip(req) |
||||
} |
||||
|
||||
func (t *Transport) base() http.RoundTripper { |
||||
if t.Base != nil { |
||||
return t.Base |
||||
} |
||||
return http.DefaultTransport |
||||
} |
||||
|
||||
// CancelRequest cancels an in-flight request by closing its connection.
|
||||
func (t *Transport) CancelRequest(req *http.Request) { |
||||
type canceler interface { |
||||
CancelRequest(*http.Request) |
||||
} |
||||
if cr, ok := t.base().(canceler); ok { |
||||
cr.CancelRequest(req) |
||||
} |
||||
} |
||||
@ -0,0 +1,143 @@
|
||||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// 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 ochttp |
||||
|
||||
import ( |
||||
"context" |
||||
"io" |
||||
"net/http" |
||||
"strconv" |
||||
"sync" |
||||
"time" |
||||
|
||||
"go.opencensus.io/stats" |
||||
"go.opencensus.io/tag" |
||||
) |
||||
|
||||
// statsTransport is an http.RoundTripper that collects stats for the outgoing requests.
|
||||
type statsTransport struct { |
||||
base http.RoundTripper |
||||
} |
||||
|
||||
// RoundTrip implements http.RoundTripper, delegating to Base and recording stats for the request.
|
||||
func (t statsTransport) RoundTrip(req *http.Request) (*http.Response, error) { |
||||
ctx, _ := tag.New(req.Context(), |
||||
tag.Upsert(KeyClientHost, req.Host), |
||||
tag.Upsert(Host, req.Host), |
||||
tag.Upsert(KeyClientPath, req.URL.Path), |
||||
tag.Upsert(Path, req.URL.Path), |
||||
tag.Upsert(KeyClientMethod, req.Method), |
||||
tag.Upsert(Method, req.Method)) |
||||
req = req.WithContext(ctx) |
||||
track := &tracker{ |
||||
start: time.Now(), |
||||
ctx: ctx, |
||||
} |
||||
if req.Body == nil { |
||||
// TODO: Handle cases where ContentLength is not set.
|
||||
track.reqSize = -1 |
||||
} else if req.ContentLength > 0 { |
||||
track.reqSize = req.ContentLength |
||||
} |
||||
stats.Record(ctx, ClientRequestCount.M(1)) |
||||
|
||||
// Perform request.
|
||||
resp, err := t.base.RoundTrip(req) |
||||
|
||||
if err != nil { |
||||
track.statusCode = http.StatusInternalServerError |
||||
track.end() |
||||
} else { |
||||
track.statusCode = resp.StatusCode |
||||
if req.Method != "HEAD" { |
||||
track.respContentLength = resp.ContentLength |
||||
} |
||||
if resp.Body == nil { |
||||
track.end() |
||||
} else { |
||||
track.body = resp.Body |
||||
resp.Body = wrappedBody(track, resp.Body) |
||||
} |
||||
} |
||||
return resp, err |
||||
} |
||||
|
||||
// CancelRequest cancels an in-flight request by closing its connection.
|
||||
func (t statsTransport) CancelRequest(req *http.Request) { |
||||
type canceler interface { |
||||
CancelRequest(*http.Request) |
||||
} |
||||
if cr, ok := t.base.(canceler); ok { |
||||
cr.CancelRequest(req) |
||||
} |
||||
} |
||||
|
||||
type tracker struct { |
||||
ctx context.Context |
||||
respSize int64 |
||||
respContentLength int64 |
||||
reqSize int64 |
||||
start time.Time |
||||
body io.ReadCloser |
||||
statusCode int |
||||
endOnce sync.Once |
||||
} |
||||
|
||||
var _ io.ReadCloser = (*tracker)(nil) |
||||
|
||||
func (t *tracker) end() { |
||||
t.endOnce.Do(func() { |
||||
latencyMs := float64(time.Since(t.start)) / float64(time.Millisecond) |
||||
respSize := t.respSize |
||||
if t.respSize == 0 && t.respContentLength > 0 { |
||||
respSize = t.respContentLength |
||||
} |
||||
m := []stats.Measurement{ |
||||
ClientSentBytes.M(t.reqSize), |
||||
ClientReceivedBytes.M(respSize), |
||||
ClientRoundtripLatency.M(latencyMs), |
||||
ClientLatency.M(latencyMs), |
||||
ClientResponseBytes.M(t.respSize), |
||||
} |
||||
if t.reqSize >= 0 { |
||||
m = append(m, ClientRequestBytes.M(t.reqSize)) |
||||
} |
||||
|
||||
stats.RecordWithTags(t.ctx, []tag.Mutator{ |
||||
tag.Upsert(StatusCode, strconv.Itoa(t.statusCode)), |
||||
tag.Upsert(KeyClientStatus, strconv.Itoa(t.statusCode)), |
||||
}, m...) |
||||
}) |
||||
} |
||||
|
||||
func (t *tracker) Read(b []byte) (int, error) { |
||||
n, err := t.body.Read(b) |
||||
t.respSize += int64(n) |
||||
switch err { |
||||
case nil: |
||||
return n, nil |
||||
case io.EOF: |
||||
t.end() |
||||
} |
||||
return n, err |
||||
} |
||||
|
||||
func (t *tracker) Close() error { |
||||
// Invoking endSpan on Close will help catch the cases
|
||||
// in which a read returned a non-nil error, we set the
|
||||
// span status but didn't end the span.
|
||||
t.end() |
||||
return t.body.Close() |
||||
} |
||||
@ -0,0 +1,19 @@
|
||||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// 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 ochttp provides OpenCensus instrumentation for net/http package.
|
||||
//
|
||||
// For server instrumentation, see Handler. For client-side instrumentation,
|
||||
// see Transport.
|
||||
package ochttp // import "go.opencensus.io/plugin/ochttp"
|
||||
@ -0,0 +1,123 @@
|
||||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// 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 b3 contains a propagation.HTTPFormat implementation
|
||||
// for B3 propagation. See https://github.com/openzipkin/b3-propagation
|
||||
// for more details.
|
||||
package b3 // import "go.opencensus.io/plugin/ochttp/propagation/b3"
|
||||
|
||||
import ( |
||||
"encoding/hex" |
||||
"net/http" |
||||
|
||||
"go.opencensus.io/trace" |
||||
"go.opencensus.io/trace/propagation" |
||||
) |
||||
|
||||
// B3 headers that OpenCensus understands.
|
||||
const ( |
||||
TraceIDHeader = "X-B3-TraceId" |
||||
SpanIDHeader = "X-B3-SpanId" |
||||
SampledHeader = "X-B3-Sampled" |
||||
) |
||||
|
||||
// HTTPFormat implements propagation.HTTPFormat to propagate
|
||||
// traces in HTTP headers in B3 propagation format.
|
||||
// HTTPFormat skips the X-B3-ParentId and X-B3-Flags headers
|
||||
// because there are additional fields not represented in the
|
||||
// OpenCensus span context. Spans created from the incoming
|
||||
// header will be the direct children of the client-side span.
|
||||
// Similarly, receiver of the outgoing spans should use client-side
|
||||
// span created by OpenCensus as the parent.
|
||||
type HTTPFormat struct{} |
||||
|
||||
var _ propagation.HTTPFormat = (*HTTPFormat)(nil) |
||||
|
||||
// SpanContextFromRequest extracts a B3 span context from incoming requests.
|
||||
func (f *HTTPFormat) SpanContextFromRequest(req *http.Request) (sc trace.SpanContext, ok bool) { |
||||
tid, ok := ParseTraceID(req.Header.Get(TraceIDHeader)) |
||||
if !ok { |
||||
return trace.SpanContext{}, false |
||||
} |
||||
sid, ok := ParseSpanID(req.Header.Get(SpanIDHeader)) |
||||
if !ok { |
||||
return trace.SpanContext{}, false |
||||
} |
||||
sampled, _ := ParseSampled(req.Header.Get(SampledHeader)) |
||||
return trace.SpanContext{ |
||||
TraceID: tid, |
||||
SpanID: sid, |
||||
TraceOptions: sampled, |
||||
}, true |
||||
} |
||||
|
||||
// ParseTraceID parses the value of the X-B3-TraceId header.
|
||||
func ParseTraceID(tid string) (trace.TraceID, bool) { |
||||
if tid == "" { |
||||
return trace.TraceID{}, false |
||||
} |
||||
b, err := hex.DecodeString(tid) |
||||
if err != nil { |
||||
return trace.TraceID{}, false |
||||
} |
||||
var traceID trace.TraceID |
||||
if len(b) <= 8 { |
||||
// The lower 64-bits.
|
||||
start := 8 + (8 - len(b)) |
||||
copy(traceID[start:], b) |
||||
} else { |
||||
start := 16 - len(b) |
||||
copy(traceID[start:], b) |
||||
} |
||||
|
||||
return traceID, true |
||||
} |
||||
|
||||
// ParseSpanID parses the value of the X-B3-SpanId or X-B3-ParentSpanId headers.
|
||||
func ParseSpanID(sid string) (spanID trace.SpanID, ok bool) { |
||||
if sid == "" { |
||||
return trace.SpanID{}, false |
||||
} |
||||
b, err := hex.DecodeString(sid) |
||||
if err != nil { |
||||
return trace.SpanID{}, false |
||||
} |
||||
start := 8 - len(b) |
||||
copy(spanID[start:], b) |
||||
return spanID, true |
||||
} |
||||
|
||||
// ParseSampled parses the value of the X-B3-Sampled header.
|
||||
func ParseSampled(sampled string) (trace.TraceOptions, bool) { |
||||
switch sampled { |
||||
case "true", "1": |
||||
return trace.TraceOptions(1), true |
||||
default: |
||||
return trace.TraceOptions(0), false |
||||
} |
||||
} |
||||
|
||||
// SpanContextToRequest modifies the given request to include B3 headers.
|
||||
func (f *HTTPFormat) SpanContextToRequest(sc trace.SpanContext, req *http.Request) { |
||||
req.Header.Set(TraceIDHeader, hex.EncodeToString(sc.TraceID[:])) |
||||
req.Header.Set(SpanIDHeader, hex.EncodeToString(sc.SpanID[:])) |
||||
|
||||
var sampled string |
||||
if sc.IsSampled() { |
||||
sampled = "1" |
||||
} else { |
||||
sampled = "0" |
||||
} |
||||
req.Header.Set(SampledHeader, sampled) |
||||
} |
||||
@ -0,0 +1,61 @@
|
||||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// 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 ochttp |
||||
|
||||
import ( |
||||
"context" |
||||
"net/http" |
||||
|
||||
"go.opencensus.io/tag" |
||||
) |
||||
|
||||
// SetRoute sets the http_server_route tag to the given value.
|
||||
// It's useful when an HTTP framework does not support the http.Handler interface
|
||||
// and using WithRouteTag is not an option, but provides a way to hook into the request flow.
|
||||
func SetRoute(ctx context.Context, route string) { |
||||
if a, ok := ctx.Value(addedTagsKey{}).(*addedTags); ok { |
||||
a.t = append(a.t, tag.Upsert(KeyServerRoute, route)) |
||||
} |
||||
} |
||||
|
||||
// WithRouteTag returns an http.Handler that records stats with the
|
||||
// http_server_route tag set to the given value.
|
||||
func WithRouteTag(handler http.Handler, route string) http.Handler { |
||||
return taggedHandlerFunc(func(w http.ResponseWriter, r *http.Request) []tag.Mutator { |
||||
addRoute := []tag.Mutator{tag.Upsert(KeyServerRoute, route)} |
||||
ctx, _ := tag.New(r.Context(), addRoute...) |
||||
r = r.WithContext(ctx) |
||||
handler.ServeHTTP(w, r) |
||||
return addRoute |
||||
}) |
||||
} |
||||
|
||||
// taggedHandlerFunc is a http.Handler that returns tags describing the
|
||||
// processing of the request. These tags will be recorded along with the
|
||||
// measures in this package at the end of the request.
|
||||
type taggedHandlerFunc func(w http.ResponseWriter, r *http.Request) []tag.Mutator |
||||
|
||||
func (h taggedHandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) { |
||||
tags := h(w, r) |
||||
if a, ok := r.Context().Value(addedTagsKey{}).(*addedTags); ok { |
||||
a.t = append(a.t, tags...) |
||||
} |
||||
} |
||||
|
||||
type addedTagsKey struct{} |
||||
|
||||
type addedTags struct { |
||||
t []tag.Mutator |
||||
} |
||||
@ -0,0 +1,449 @@
|
||||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// 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 ochttp |
||||
|
||||
import ( |
||||
"context" |
||||
"io" |
||||
"net/http" |
||||
"strconv" |
||||
"sync" |
||||
"time" |
||||
|
||||
"go.opencensus.io/stats" |
||||
"go.opencensus.io/tag" |
||||
"go.opencensus.io/trace" |
||||
"go.opencensus.io/trace/propagation" |
||||
) |
||||
|
||||
// Handler is an http.Handler wrapper to instrument your HTTP server with
|
||||
// OpenCensus. It supports both stats and tracing.
|
||||
//
|
||||
// Tracing
|
||||
//
|
||||
// This handler is aware of the incoming request's span, reading it from request
|
||||
// headers as configured using the Propagation field.
|
||||
// The extracted span can be accessed from the incoming request's
|
||||
// context.
|
||||
//
|
||||
// span := trace.FromContext(r.Context())
|
||||
//
|
||||
// The server span will be automatically ended at the end of ServeHTTP.
|
||||
type Handler struct { |
||||
// Propagation defines how traces are propagated. If unspecified,
|
||||
// B3 propagation will be used.
|
||||
Propagation propagation.HTTPFormat |
||||
|
||||
// Handler is the handler used to handle the incoming request.
|
||||
Handler http.Handler |
||||
|
||||
// StartOptions are applied to the span started by this Handler around each
|
||||
// request.
|
||||
//
|
||||
// StartOptions.SpanKind will always be set to trace.SpanKindServer
|
||||
// for spans started by this transport.
|
||||
StartOptions trace.StartOptions |
||||
|
||||
// GetStartOptions allows to set start options per request. If set,
|
||||
// StartOptions is going to be ignored.
|
||||
GetStartOptions func(*http.Request) trace.StartOptions |
||||
|
||||
// IsPublicEndpoint should be set to true for publicly accessible HTTP(S)
|
||||
// servers. If true, any trace metadata set on the incoming request will
|
||||
// be added as a linked trace instead of being added as a parent of the
|
||||
// current trace.
|
||||
IsPublicEndpoint bool |
||||
|
||||
// FormatSpanName holds the function to use for generating the span name
|
||||
// from the information found in the incoming HTTP Request. By default the
|
||||
// name equals the URL Path.
|
||||
FormatSpanName func(*http.Request) string |
||||
} |
||||
|
||||
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { |
||||
var tags addedTags |
||||
r, traceEnd := h.startTrace(w, r) |
||||
defer traceEnd() |
||||
w, statsEnd := h.startStats(w, r) |
||||
defer statsEnd(&tags) |
||||
handler := h.Handler |
||||
if handler == nil { |
||||
handler = http.DefaultServeMux |
||||
} |
||||
r = r.WithContext(context.WithValue(r.Context(), addedTagsKey{}, &tags)) |
||||
handler.ServeHTTP(w, r) |
||||
} |
||||
|
||||
func (h *Handler) startTrace(w http.ResponseWriter, r *http.Request) (*http.Request, func()) { |
||||
if isHealthEndpoint(r.URL.Path) { |
||||
return r, func() {} |
||||
} |
||||
var name string |
||||
if h.FormatSpanName == nil { |
||||
name = spanNameFromURL(r) |
||||
} else { |
||||
name = h.FormatSpanName(r) |
||||
} |
||||
ctx := r.Context() |
||||
|
||||
startOpts := h.StartOptions |
||||
if h.GetStartOptions != nil { |
||||
startOpts = h.GetStartOptions(r) |
||||
} |
||||
|
||||
var span *trace.Span |
||||
sc, ok := h.extractSpanContext(r) |
||||
if ok && !h.IsPublicEndpoint { |
||||
ctx, span = trace.StartSpanWithRemoteParent(ctx, name, sc, |
||||
trace.WithSampler(startOpts.Sampler), |
||||
trace.WithSpanKind(trace.SpanKindServer)) |
||||
} else { |
||||
ctx, span = trace.StartSpan(ctx, name, |
||||
trace.WithSampler(startOpts.Sampler), |
||||
trace.WithSpanKind(trace.SpanKindServer), |
||||
) |
||||
if ok { |
||||
span.AddLink(trace.Link{ |
||||
TraceID: sc.TraceID, |
||||
SpanID: sc.SpanID, |
||||
Type: trace.LinkTypeParent, |
||||
Attributes: nil, |
||||
}) |
||||
} |
||||
} |
||||
span.AddAttributes(requestAttrs(r)...) |
||||
if r.Body == nil { |
||||
// TODO: Handle cases where ContentLength is not set.
|
||||
} else if r.ContentLength > 0 { |
||||
span.AddMessageReceiveEvent(0, /* TODO: messageID */ |
||||
int64(r.ContentLength), -1) |
||||
} |
||||
return r.WithContext(ctx), span.End |
||||
} |
||||
|
||||
func (h *Handler) extractSpanContext(r *http.Request) (trace.SpanContext, bool) { |
||||
if h.Propagation == nil { |
||||
return defaultFormat.SpanContextFromRequest(r) |
||||
} |
||||
return h.Propagation.SpanContextFromRequest(r) |
||||
} |
||||
|
||||
func (h *Handler) startStats(w http.ResponseWriter, r *http.Request) (http.ResponseWriter, func(tags *addedTags)) { |
||||
ctx, _ := tag.New(r.Context(), |
||||
tag.Upsert(Host, r.Host), |
||||
tag.Upsert(Path, r.URL.Path), |
||||
tag.Upsert(Method, r.Method)) |
||||
track := &trackingResponseWriter{ |
||||
start: time.Now(), |
||||
ctx: ctx, |
||||
writer: w, |
||||
} |
||||
if r.Body == nil { |
||||
// TODO: Handle cases where ContentLength is not set.
|
||||
track.reqSize = -1 |
||||
} else if r.ContentLength > 0 { |
||||
track.reqSize = r.ContentLength |
||||
} |
||||
stats.Record(ctx, ServerRequestCount.M(1)) |
||||
return track.wrappedResponseWriter(), track.end |
||||
} |
||||
|
||||
type trackingResponseWriter struct { |
||||
ctx context.Context |
||||
reqSize int64 |
||||
respSize int64 |
||||
start time.Time |
||||
statusCode int |
||||
statusLine string |
||||
endOnce sync.Once |
||||
writer http.ResponseWriter |
||||
} |
||||
|
||||
// Compile time assertion for ResponseWriter interface
|
||||
var _ http.ResponseWriter = (*trackingResponseWriter)(nil) |
||||
|
||||
var logTagsErrorOnce sync.Once |
||||
|
||||
func (t *trackingResponseWriter) end(tags *addedTags) { |
||||
t.endOnce.Do(func() { |
||||
if t.statusCode == 0 { |
||||
t.statusCode = 200 |
||||
} |
||||
|
||||
span := trace.FromContext(t.ctx) |
||||
span.SetStatus(TraceStatus(t.statusCode, t.statusLine)) |
||||
span.AddAttributes(trace.Int64Attribute(StatusCodeAttribute, int64(t.statusCode))) |
||||
|
||||
m := []stats.Measurement{ |
||||
ServerLatency.M(float64(time.Since(t.start)) / float64(time.Millisecond)), |
||||
ServerResponseBytes.M(t.respSize), |
||||
} |
||||
if t.reqSize >= 0 { |
||||
m = append(m, ServerRequestBytes.M(t.reqSize)) |
||||
} |
||||
allTags := make([]tag.Mutator, len(tags.t)+1) |
||||
allTags[0] = tag.Upsert(StatusCode, strconv.Itoa(t.statusCode)) |
||||
copy(allTags[1:], tags.t) |
||||
stats.RecordWithTags(t.ctx, allTags, m...) |
||||
}) |
||||
} |
||||
|
||||
func (t *trackingResponseWriter) Header() http.Header { |
||||
return t.writer.Header() |
||||
} |
||||
|
||||
func (t *trackingResponseWriter) Write(data []byte) (int, error) { |
||||
n, err := t.writer.Write(data) |
||||
t.respSize += int64(n) |
||||
// Add message event for request bytes sent.
|
||||
span := trace.FromContext(t.ctx) |
||||
span.AddMessageSendEvent(0 /* TODO: messageID */, int64(n), -1) |
||||
return n, err |
||||
} |
||||
|
||||
func (t *trackingResponseWriter) WriteHeader(statusCode int) { |
||||
t.writer.WriteHeader(statusCode) |
||||
t.statusCode = statusCode |
||||
t.statusLine = http.StatusText(t.statusCode) |
||||
} |
||||
|
||||
// wrappedResponseWriter returns a wrapped version of the original
|
||||
// ResponseWriter and only implements the same combination of additional
|
||||
// interfaces as the original.
|
||||
// This implementation is based on https://github.com/felixge/httpsnoop.
|
||||
func (t *trackingResponseWriter) wrappedResponseWriter() http.ResponseWriter { |
||||
var ( |
||||
hj, i0 = t.writer.(http.Hijacker) |
||||
cn, i1 = t.writer.(http.CloseNotifier) |
||||
pu, i2 = t.writer.(http.Pusher) |
||||
fl, i3 = t.writer.(http.Flusher) |
||||
rf, i4 = t.writer.(io.ReaderFrom) |
||||
) |
||||
|
||||
switch { |
||||
case !i0 && !i1 && !i2 && !i3 && !i4: |
||||
return struct { |
||||
http.ResponseWriter |
||||
}{t} |
||||
case !i0 && !i1 && !i2 && !i3 && i4: |
||||
return struct { |
||||
http.ResponseWriter |
||||
io.ReaderFrom |
||||
}{t, rf} |
||||
case !i0 && !i1 && !i2 && i3 && !i4: |
||||
return struct { |
||||
http.ResponseWriter |
||||
http.Flusher |
||||
}{t, fl} |
||||
case !i0 && !i1 && !i2 && i3 && i4: |
||||
return struct { |
||||
http.ResponseWriter |
||||
http.Flusher |
||||
io.ReaderFrom |
||||
}{t, fl, rf} |
||||
case !i0 && !i1 && i2 && !i3 && !i4: |
||||
return struct { |
||||
http.ResponseWriter |
||||
http.Pusher |
||||
}{t, pu} |
||||
case !i0 && !i1 && i2 && !i3 && i4: |
||||
return struct { |
||||
http.ResponseWriter |
||||
http.Pusher |
||||
io.ReaderFrom |
||||
}{t, pu, rf} |
||||
case !i0 && !i1 && i2 && i3 && !i4: |
||||
return struct { |
||||
http.ResponseWriter |
||||
http.Pusher |
||||
http.Flusher |
||||
}{t, pu, fl} |
||||
case !i0 && !i1 && i2 && i3 && i4: |
||||
return struct { |
||||
http.ResponseWriter |
||||
http.Pusher |
||||
http.Flusher |
||||
io.ReaderFrom |
||||
}{t, pu, fl, rf} |
||||
case !i0 && i1 && !i2 && !i3 && !i4: |
||||
return struct { |
||||
http.ResponseWriter |
||||
http.CloseNotifier |
||||
}{t, cn} |
||||
case !i0 && i1 && !i2 && !i3 && i4: |
||||
return struct { |
||||
http.ResponseWriter |
||||
http.CloseNotifier |
||||
io.ReaderFrom |
||||
}{t, cn, rf} |
||||
case !i0 && i1 && !i2 && i3 && !i4: |
||||
return struct { |
||||
http.ResponseWriter |
||||
http.CloseNotifier |
||||
http.Flusher |
||||
}{t, cn, fl} |
||||
case !i0 && i1 && !i2 && i3 && i4: |
||||
return struct { |
||||
http.ResponseWriter |
||||
http.CloseNotifier |
||||
http.Flusher |
||||
io.ReaderFrom |
||||
}{t, cn, fl, rf} |
||||
case !i0 && i1 && i2 && !i3 && !i4: |
||||
return struct { |
||||
http.ResponseWriter |
||||
http.CloseNotifier |
||||
http.Pusher |
||||
}{t, cn, pu} |
||||
case !i0 && i1 && i2 && !i3 && i4: |
||||
return struct { |
||||
http.ResponseWriter |
||||
http.CloseNotifier |
||||
http.Pusher |
||||
io.ReaderFrom |
||||
}{t, cn, pu, rf} |
||||
case !i0 && i1 && i2 && i3 && !i4: |
||||
return struct { |
||||
http.ResponseWriter |
||||
http.CloseNotifier |
||||
http.Pusher |
||||
http.Flusher |
||||
}{t, cn, pu, fl} |
||||
case !i0 && i1 && i2 && i3 && i4: |
||||
return struct { |
||||
http.ResponseWriter |
||||
http.CloseNotifier |
||||
http.Pusher |
||||
http.Flusher |
||||
io.ReaderFrom |
||||
}{t, cn, pu, fl, rf} |
||||
case i0 && !i1 && !i2 && !i3 && !i4: |
||||
return struct { |
||||
http.ResponseWriter |
||||
http.Hijacker |
||||
}{t, hj} |
||||
case i0 && !i1 && !i2 && !i3 && i4: |
||||
return struct { |
||||
http.ResponseWriter |
||||
http.Hijacker |
||||
io.ReaderFrom |
||||
}{t, hj, rf} |
||||
case i0 && !i1 && !i2 && i3 && !i4: |
||||
return struct { |
||||
http.ResponseWriter |
||||
http.Hijacker |
||||
http.Flusher |
||||
}{t, hj, fl} |
||||
case i0 && !i1 && !i2 && i3 && i4: |
||||
return struct { |
||||
http.ResponseWriter |
||||
http.Hijacker |
||||
http.Flusher |
||||
io.ReaderFrom |
||||
}{t, hj, fl, rf} |
||||
case i0 && !i1 && i2 && !i3 && !i4: |
||||
return struct { |
||||
http.ResponseWriter |
||||
http.Hijacker |
||||
http.Pusher |
||||
}{t, hj, pu} |
||||
case i0 && !i1 && i2 && !i3 && i4: |
||||
return struct { |
||||
http.ResponseWriter |
||||
http.Hijacker |
||||
http.Pusher |
||||
io.ReaderFrom |
||||
}{t, hj, pu, rf} |
||||
case i0 && !i1 && i2 && i3 && !i4: |
||||
return struct { |
||||
http.ResponseWriter |
||||
http.Hijacker |
||||
http.Pusher |
||||
http.Flusher |
||||
}{t, hj, pu, fl} |
||||
case i0 && !i1 && i2 && i3 && i4: |
||||
return struct { |
||||
http.ResponseWriter |
||||
http.Hijacker |
||||
http.Pusher |
||||
http.Flusher |
||||
io.ReaderFrom |
||||
}{t, hj, pu, fl, rf} |
||||
case i0 && i1 && !i2 && !i3 && !i4: |
||||
return struct { |
||||
http.ResponseWriter |
||||
http.Hijacker |
||||
http.CloseNotifier |
||||
}{t, hj, cn} |
||||
case i0 && i1 && !i2 && !i3 && i4: |
||||
return struct { |
||||
http.ResponseWriter |
||||
http.Hijacker |
||||
http.CloseNotifier |
||||
io.ReaderFrom |
||||
}{t, hj, cn, rf} |
||||
case i0 && i1 && !i2 && i3 && !i4: |
||||
return struct { |
||||
http.ResponseWriter |
||||
http.Hijacker |
||||
http.CloseNotifier |
||||
http.Flusher |
||||
}{t, hj, cn, fl} |
||||
case i0 && i1 && !i2 && i3 && i4: |
||||
return struct { |
||||
http.ResponseWriter |
||||
http.Hijacker |
||||
http.CloseNotifier |
||||
http.Flusher |
||||
io.ReaderFrom |
||||
}{t, hj, cn, fl, rf} |
||||
case i0 && i1 && i2 && !i3 && !i4: |
||||
return struct { |
||||
http.ResponseWriter |
||||
http.Hijacker |
||||
http.CloseNotifier |
||||
http.Pusher |
||||
}{t, hj, cn, pu} |
||||
case i0 && i1 && i2 && !i3 && i4: |
||||
return struct { |
||||
http.ResponseWriter |
||||
http.Hijacker |
||||
http.CloseNotifier |
||||
http.Pusher |
||||
io.ReaderFrom |
||||
}{t, hj, cn, pu, rf} |
||||
case i0 && i1 && i2 && i3 && !i4: |
||||
return struct { |
||||
http.ResponseWriter |
||||
http.Hijacker |
||||
http.CloseNotifier |
||||
http.Pusher |
||||
http.Flusher |
||||
}{t, hj, cn, pu, fl} |
||||
case i0 && i1 && i2 && i3 && i4: |
||||
return struct { |
||||
http.ResponseWriter |
||||
http.Hijacker |
||||
http.CloseNotifier |
||||
http.Pusher |
||||
http.Flusher |
||||
io.ReaderFrom |
||||
}{t, hj, cn, pu, fl, rf} |
||||
default: |
||||
return struct { |
||||
http.ResponseWriter |
||||
}{t} |
||||
} |
||||
} |
||||
@ -0,0 +1,169 @@
|
||||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// 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 ochttp |
||||
|
||||
import ( |
||||
"crypto/tls" |
||||
"net/http" |
||||
"net/http/httptrace" |
||||
"strings" |
||||
|
||||
"go.opencensus.io/trace" |
||||
) |
||||
|
||||
type spanAnnotator struct { |
||||
sp *trace.Span |
||||
} |
||||
|
||||
// TODO: Remove NewSpanAnnotator at the next release.
|
||||
|
||||
// NewSpanAnnotator returns a httptrace.ClientTrace which annotates
|
||||
// all emitted httptrace events on the provided Span.
|
||||
// Deprecated: Use NewSpanAnnotatingClientTrace instead
|
||||
func NewSpanAnnotator(r *http.Request, s *trace.Span) *httptrace.ClientTrace { |
||||
return NewSpanAnnotatingClientTrace(r, s) |
||||
} |
||||
|
||||
// NewSpanAnnotatingClientTrace returns a httptrace.ClientTrace which annotates
|
||||
// all emitted httptrace events on the provided Span.
|
||||
func NewSpanAnnotatingClientTrace(_ *http.Request, s *trace.Span) *httptrace.ClientTrace { |
||||
sa := spanAnnotator{sp: s} |
||||
|
||||
return &httptrace.ClientTrace{ |
||||
GetConn: sa.getConn, |
||||
GotConn: sa.gotConn, |
||||
PutIdleConn: sa.putIdleConn, |
||||
GotFirstResponseByte: sa.gotFirstResponseByte, |
||||
Got100Continue: sa.got100Continue, |
||||
DNSStart: sa.dnsStart, |
||||
DNSDone: sa.dnsDone, |
||||
ConnectStart: sa.connectStart, |
||||
ConnectDone: sa.connectDone, |
||||
TLSHandshakeStart: sa.tlsHandshakeStart, |
||||
TLSHandshakeDone: sa.tlsHandshakeDone, |
||||
WroteHeaders: sa.wroteHeaders, |
||||
Wait100Continue: sa.wait100Continue, |
||||
WroteRequest: sa.wroteRequest, |
||||
} |
||||
} |
||||
|
||||
func (s spanAnnotator) getConn(hostPort string) { |
||||
attrs := []trace.Attribute{ |
||||
trace.StringAttribute("httptrace.get_connection.host_port", hostPort), |
||||
} |
||||
s.sp.Annotate(attrs, "GetConn") |
||||
} |
||||
|
||||
func (s spanAnnotator) gotConn(info httptrace.GotConnInfo) { |
||||
attrs := []trace.Attribute{ |
||||
trace.BoolAttribute("httptrace.got_connection.reused", info.Reused), |
||||
trace.BoolAttribute("httptrace.got_connection.was_idle", info.WasIdle), |
||||
} |
||||
if info.WasIdle { |
||||
attrs = append(attrs, |
||||
trace.StringAttribute("httptrace.got_connection.idle_time", info.IdleTime.String())) |
||||
} |
||||
s.sp.Annotate(attrs, "GotConn") |
||||
} |
||||
|
||||
// PutIdleConn implements a httptrace.ClientTrace hook
|
||||
func (s spanAnnotator) putIdleConn(err error) { |
||||
var attrs []trace.Attribute |
||||
if err != nil { |
||||
attrs = append(attrs, |
||||
trace.StringAttribute("httptrace.put_idle_connection.error", err.Error())) |
||||
} |
||||
s.sp.Annotate(attrs, "PutIdleConn") |
||||
} |
||||
|
||||
func (s spanAnnotator) gotFirstResponseByte() { |
||||
s.sp.Annotate(nil, "GotFirstResponseByte") |
||||
} |
||||
|
||||
func (s spanAnnotator) got100Continue() { |
||||
s.sp.Annotate(nil, "Got100Continue") |
||||
} |
||||
|
||||
func (s spanAnnotator) dnsStart(info httptrace.DNSStartInfo) { |
||||
attrs := []trace.Attribute{ |
||||
trace.StringAttribute("httptrace.dns_start.host", info.Host), |
||||
} |
||||
s.sp.Annotate(attrs, "DNSStart") |
||||
} |
||||
|
||||
func (s spanAnnotator) dnsDone(info httptrace.DNSDoneInfo) { |
||||
var addrs []string |
||||
for _, addr := range info.Addrs { |
||||
addrs = append(addrs, addr.String()) |
||||
} |
||||
attrs := []trace.Attribute{ |
||||
trace.StringAttribute("httptrace.dns_done.addrs", strings.Join(addrs, " , ")), |
||||
} |
||||
if info.Err != nil { |
||||
attrs = append(attrs, |
||||
trace.StringAttribute("httptrace.dns_done.error", info.Err.Error())) |
||||
} |
||||
s.sp.Annotate(attrs, "DNSDone") |
||||
} |
||||
|
||||
func (s spanAnnotator) connectStart(network, addr string) { |
||||
attrs := []trace.Attribute{ |
||||
trace.StringAttribute("httptrace.connect_start.network", network), |
||||
trace.StringAttribute("httptrace.connect_start.addr", addr), |
||||
} |
||||
s.sp.Annotate(attrs, "ConnectStart") |
||||
} |
||||
|
||||
func (s spanAnnotator) connectDone(network, addr string, err error) { |
||||
attrs := []trace.Attribute{ |
||||
trace.StringAttribute("httptrace.connect_done.network", network), |
||||
trace.StringAttribute("httptrace.connect_done.addr", addr), |
||||
} |
||||
if err != nil { |
||||
attrs = append(attrs, |
||||
trace.StringAttribute("httptrace.connect_done.error", err.Error())) |
||||
} |
||||
s.sp.Annotate(attrs, "ConnectDone") |
||||
} |
||||
|
||||
func (s spanAnnotator) tlsHandshakeStart() { |
||||
s.sp.Annotate(nil, "TLSHandshakeStart") |
||||
} |
||||
|
||||
func (s spanAnnotator) tlsHandshakeDone(_ tls.ConnectionState, err error) { |
||||
var attrs []trace.Attribute |
||||
if err != nil { |
||||
attrs = append(attrs, |
||||
trace.StringAttribute("httptrace.tls_handshake_done.error", err.Error())) |
||||
} |
||||
s.sp.Annotate(attrs, "TLSHandshakeDone") |
||||
} |
||||
|
||||
func (s spanAnnotator) wroteHeaders() { |
||||
s.sp.Annotate(nil, "WroteHeaders") |
||||
} |
||||
|
||||
func (s spanAnnotator) wait100Continue() { |
||||
s.sp.Annotate(nil, "Wait100Continue") |
||||
} |
||||
|
||||
func (s spanAnnotator) wroteRequest(info httptrace.WroteRequestInfo) { |
||||
var attrs []trace.Attribute |
||||
if info.Err != nil { |
||||
attrs = append(attrs, |
||||
trace.StringAttribute("httptrace.wrote_request.error", info.Err.Error())) |
||||
} |
||||
s.sp.Annotate(attrs, "WroteRequest") |
||||
} |
||||
@ -0,0 +1,292 @@
|
||||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// 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 ochttp |
||||
|
||||
import ( |
||||
"go.opencensus.io/stats" |
||||
"go.opencensus.io/stats/view" |
||||
"go.opencensus.io/tag" |
||||
) |
||||
|
||||
// Deprecated: client HTTP measures.
|
||||
var ( |
||||
// Deprecated: Use a Count aggregation over one of the other client measures to achieve the same effect.
|
||||
ClientRequestCount = stats.Int64( |
||||
"opencensus.io/http/client/request_count", |
||||
"Number of HTTP requests started", |
||||
stats.UnitDimensionless) |
||||
// Deprecated: Use ClientSentBytes.
|
||||
ClientRequestBytes = stats.Int64( |
||||
"opencensus.io/http/client/request_bytes", |
||||
"HTTP request body size if set as ContentLength (uncompressed)", |
||||
stats.UnitBytes) |
||||
// Deprecated: Use ClientReceivedBytes.
|
||||
ClientResponseBytes = stats.Int64( |
||||
"opencensus.io/http/client/response_bytes", |
||||
"HTTP response body size (uncompressed)", |
||||
stats.UnitBytes) |
||||
// Deprecated: Use ClientRoundtripLatency.
|
||||
ClientLatency = stats.Float64( |
||||
"opencensus.io/http/client/latency", |
||||
"End-to-end latency", |
||||
stats.UnitMilliseconds) |
||||
) |
||||
|
||||
// The following client HTTP measures are supported for use in custom views.
|
||||
var ( |
||||
ClientSentBytes = stats.Int64( |
||||
"opencensus.io/http/client/sent_bytes", |
||||
"Total bytes sent in request body (not including headers)", |
||||
stats.UnitBytes, |
||||
) |
||||
ClientReceivedBytes = stats.Int64( |
||||
"opencensus.io/http/client/received_bytes", |
||||
"Total bytes received in response bodies (not including headers but including error responses with bodies)", |
||||
stats.UnitBytes, |
||||
) |
||||
ClientRoundtripLatency = stats.Float64( |
||||
"opencensus.io/http/client/roundtrip_latency", |
||||
"Time between first byte of request headers sent to last byte of response received, or terminal error", |
||||
stats.UnitMilliseconds, |
||||
) |
||||
) |
||||
|
||||
// The following server HTTP measures are supported for use in custom views:
|
||||
var ( |
||||
ServerRequestCount = stats.Int64( |
||||
"opencensus.io/http/server/request_count", |
||||
"Number of HTTP requests started", |
||||
stats.UnitDimensionless) |
||||
ServerRequestBytes = stats.Int64( |
||||
"opencensus.io/http/server/request_bytes", |
||||
"HTTP request body size if set as ContentLength (uncompressed)", |
||||
stats.UnitBytes) |
||||
ServerResponseBytes = stats.Int64( |
||||
"opencensus.io/http/server/response_bytes", |
||||
"HTTP response body size (uncompressed)", |
||||
stats.UnitBytes) |
||||
ServerLatency = stats.Float64( |
||||
"opencensus.io/http/server/latency", |
||||
"End-to-end latency", |
||||
stats.UnitMilliseconds) |
||||
) |
||||
|
||||
// The following tags are applied to stats recorded by this package. Host, Path
|
||||
// and Method are applied to all measures. StatusCode is not applied to
|
||||
// ClientRequestCount or ServerRequestCount, since it is recorded before the status is known.
|
||||
var ( |
||||
// Host is the value of the HTTP Host header.
|
||||
//
|
||||
// The value of this tag can be controlled by the HTTP client, so you need
|
||||
// to watch out for potentially generating high-cardinality labels in your
|
||||
// metrics backend if you use this tag in views.
|
||||
Host, _ = tag.NewKey("http.host") |
||||
|
||||
// StatusCode is the numeric HTTP response status code,
|
||||
// or "error" if a transport error occurred and no status code was read.
|
||||
StatusCode, _ = tag.NewKey("http.status") |
||||
|
||||
// Path is the URL path (not including query string) in the request.
|
||||
//
|
||||
// The value of this tag can be controlled by the HTTP client, so you need
|
||||
// to watch out for potentially generating high-cardinality labels in your
|
||||
// metrics backend if you use this tag in views.
|
||||
Path, _ = tag.NewKey("http.path") |
||||
|
||||
// Method is the HTTP method of the request, capitalized (GET, POST, etc.).
|
||||
Method, _ = tag.NewKey("http.method") |
||||
|
||||
// KeyServerRoute is a low cardinality string representing the logical
|
||||
// handler of the request. This is usually the pattern registered on the a
|
||||
// ServeMux (or similar string).
|
||||
KeyServerRoute, _ = tag.NewKey("http_server_route") |
||||
) |
||||
|
||||
// Client tag keys.
|
||||
var ( |
||||
// KeyClientMethod is the HTTP method, capitalized (i.e. GET, POST, PUT, DELETE, etc.).
|
||||
KeyClientMethod, _ = tag.NewKey("http_client_method") |
||||
// KeyClientPath is the URL path (not including query string).
|
||||
KeyClientPath, _ = tag.NewKey("http_client_path") |
||||
// KeyClientStatus is the HTTP status code as an integer (e.g. 200, 404, 500.), or "error" if no response status line was received.
|
||||
KeyClientStatus, _ = tag.NewKey("http_client_status") |
||||
// KeyClientHost is the value of the request Host header.
|
||||
KeyClientHost, _ = tag.NewKey("http_client_host") |
||||
) |
||||
|
||||
// Default distributions used by views in this package.
|
||||
var ( |
||||
DefaultSizeDistribution = view.Distribution(1024, 2048, 4096, 16384, 65536, 262144, 1048576, 4194304, 16777216, 67108864, 268435456, 1073741824, 4294967296) |
||||
DefaultLatencyDistribution = view.Distribution(1, 2, 3, 4, 5, 6, 8, 10, 13, 16, 20, 25, 30, 40, 50, 65, 80, 100, 130, 160, 200, 250, 300, 400, 500, 650, 800, 1000, 2000, 5000, 10000, 20000, 50000, 100000) |
||||
) |
||||
|
||||
// Package ochttp provides some convenience views for client measures.
|
||||
// You still need to register these views for data to actually be collected.
|
||||
var ( |
||||
ClientSentBytesDistribution = &view.View{ |
||||
Name: "opencensus.io/http/client/sent_bytes", |
||||
Measure: ClientSentBytes, |
||||
Aggregation: DefaultSizeDistribution, |
||||
Description: "Total bytes sent in request body (not including headers), by HTTP method and response status", |
||||
TagKeys: []tag.Key{KeyClientMethod, KeyClientStatus}, |
||||
} |
||||
|
||||
ClientReceivedBytesDistribution = &view.View{ |
||||
Name: "opencensus.io/http/client/received_bytes", |
||||
Measure: ClientReceivedBytes, |
||||
Aggregation: DefaultSizeDistribution, |
||||
Description: "Total bytes received in response bodies (not including headers but including error responses with bodies), by HTTP method and response status", |
||||
TagKeys: []tag.Key{KeyClientMethod, KeyClientStatus}, |
||||
} |
||||
|
||||
ClientRoundtripLatencyDistribution = &view.View{ |
||||
Name: "opencensus.io/http/client/roundtrip_latency", |
||||
Measure: ClientRoundtripLatency, |
||||
Aggregation: DefaultLatencyDistribution, |
||||
Description: "End-to-end latency, by HTTP method and response status", |
||||
TagKeys: []tag.Key{KeyClientMethod, KeyClientStatus}, |
||||
} |
||||
|
||||
ClientCompletedCount = &view.View{ |
||||
Name: "opencensus.io/http/client/completed_count", |
||||
Measure: ClientRoundtripLatency, |
||||
Aggregation: view.Count(), |
||||
Description: "Count of completed requests, by HTTP method and response status", |
||||
TagKeys: []tag.Key{KeyClientMethod, KeyClientStatus}, |
||||
} |
||||
) |
||||
|
||||
// Deprecated: Old client Views.
|
||||
var ( |
||||
// Deprecated: No direct replacement, but see ClientCompletedCount.
|
||||
ClientRequestCountView = &view.View{ |
||||
Name: "opencensus.io/http/client/request_count", |
||||
Description: "Count of HTTP requests started", |
||||
Measure: ClientRequestCount, |
||||
Aggregation: view.Count(), |
||||
} |
||||
|
||||
// Deprecated: Use ClientSentBytesDistribution.
|
||||
ClientRequestBytesView = &view.View{ |
||||
Name: "opencensus.io/http/client/request_bytes", |
||||
Description: "Size distribution of HTTP request body", |
||||
Measure: ClientSentBytes, |
||||
Aggregation: DefaultSizeDistribution, |
||||
} |
||||
|
||||
// Deprecated: Use ClientReceivedBytesDistribution instead.
|
||||
ClientResponseBytesView = &view.View{ |
||||
Name: "opencensus.io/http/client/response_bytes", |
||||
Description: "Size distribution of HTTP response body", |
||||
Measure: ClientReceivedBytes, |
||||
Aggregation: DefaultSizeDistribution, |
||||
} |
||||
|
||||
// Deprecated: Use ClientRoundtripLatencyDistribution instead.
|
||||
ClientLatencyView = &view.View{ |
||||
Name: "opencensus.io/http/client/latency", |
||||
Description: "Latency distribution of HTTP requests", |
||||
Measure: ClientRoundtripLatency, |
||||
Aggregation: DefaultLatencyDistribution, |
||||
} |
||||
|
||||
// Deprecated: Use ClientCompletedCount instead.
|
||||
ClientRequestCountByMethod = &view.View{ |
||||
Name: "opencensus.io/http/client/request_count_by_method", |
||||
Description: "Client request count by HTTP method", |
||||
TagKeys: []tag.Key{Method}, |
||||
Measure: ClientSentBytes, |
||||
Aggregation: view.Count(), |
||||
} |
||||
|
||||
// Deprecated: Use ClientCompletedCount instead.
|
||||
ClientResponseCountByStatusCode = &view.View{ |
||||
Name: "opencensus.io/http/client/response_count_by_status_code", |
||||
Description: "Client response count by status code", |
||||
TagKeys: []tag.Key{StatusCode}, |
||||
Measure: ClientRoundtripLatency, |
||||
Aggregation: view.Count(), |
||||
} |
||||
) |
||||
|
||||
// Package ochttp provides some convenience views for server measures.
|
||||
// You still need to register these views for data to actually be collected.
|
||||
var ( |
||||
ServerRequestCountView = &view.View{ |
||||
Name: "opencensus.io/http/server/request_count", |
||||
Description: "Count of HTTP requests started", |
||||
Measure: ServerRequestCount, |
||||
Aggregation: view.Count(), |
||||
} |
||||
|
||||
ServerRequestBytesView = &view.View{ |
||||
Name: "opencensus.io/http/server/request_bytes", |
||||
Description: "Size distribution of HTTP request body", |
||||
Measure: ServerRequestBytes, |
||||
Aggregation: DefaultSizeDistribution, |
||||
} |
||||
|
||||
ServerResponseBytesView = &view.View{ |
||||
Name: "opencensus.io/http/server/response_bytes", |
||||
Description: "Size distribution of HTTP response body", |
||||
Measure: ServerResponseBytes, |
||||
Aggregation: DefaultSizeDistribution, |
||||
} |
||||
|
||||
ServerLatencyView = &view.View{ |
||||
Name: "opencensus.io/http/server/latency", |
||||
Description: "Latency distribution of HTTP requests", |
||||
Measure: ServerLatency, |
||||
Aggregation: DefaultLatencyDistribution, |
||||
} |
||||
|
||||
ServerRequestCountByMethod = &view.View{ |
||||
Name: "opencensus.io/http/server/request_count_by_method", |
||||
Description: "Server request count by HTTP method", |
||||
TagKeys: []tag.Key{Method}, |
||||
Measure: ServerRequestCount, |
||||
Aggregation: view.Count(), |
||||
} |
||||
|
||||
ServerResponseCountByStatusCode = &view.View{ |
||||
Name: "opencensus.io/http/server/response_count_by_status_code", |
||||
Description: "Server response count by status code", |
||||
TagKeys: []tag.Key{StatusCode}, |
||||
Measure: ServerLatency, |
||||
Aggregation: view.Count(), |
||||
} |
||||
) |
||||
|
||||
// DefaultClientViews are the default client views provided by this package.
|
||||
// Deprecated: No replacement. Register the views you would like individually.
|
||||
var DefaultClientViews = []*view.View{ |
||||
ClientRequestCountView, |
||||
ClientRequestBytesView, |
||||
ClientResponseBytesView, |
||||
ClientLatencyView, |
||||
ClientRequestCountByMethod, |
||||
ClientResponseCountByStatusCode, |
||||
} |
||||
|
||||
// DefaultServerViews are the default server views provided by this package.
|
||||
// Deprecated: No replacement. Register the views you would like individually.
|
||||
var DefaultServerViews = []*view.View{ |
||||
ServerRequestCountView, |
||||
ServerRequestBytesView, |
||||
ServerResponseBytesView, |
||||
ServerLatencyView, |
||||
ServerRequestCountByMethod, |
||||
ServerResponseCountByStatusCode, |
||||
} |
||||
@ -0,0 +1,239 @@
|
||||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// 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 ochttp |
||||
|
||||
import ( |
||||
"io" |
||||
"net/http" |
||||
"net/http/httptrace" |
||||
|
||||
"go.opencensus.io/plugin/ochttp/propagation/b3" |
||||
"go.opencensus.io/trace" |
||||
"go.opencensus.io/trace/propagation" |
||||
) |
||||
|
||||
// TODO(jbd): Add godoc examples.
|
||||
|
||||
var defaultFormat propagation.HTTPFormat = &b3.HTTPFormat{} |
||||
|
||||
// Attributes recorded on the span for the requests.
|
||||
// Only trace exporters will need them.
|
||||
const ( |
||||
HostAttribute = "http.host" |
||||
MethodAttribute = "http.method" |
||||
PathAttribute = "http.path" |
||||
URLAttribute = "http.url" |
||||
UserAgentAttribute = "http.user_agent" |
||||
StatusCodeAttribute = "http.status_code" |
||||
) |
||||
|
||||
type traceTransport struct { |
||||
base http.RoundTripper |
||||
startOptions trace.StartOptions |
||||
format propagation.HTTPFormat |
||||
formatSpanName func(*http.Request) string |
||||
newClientTrace func(*http.Request, *trace.Span) *httptrace.ClientTrace |
||||
} |
||||
|
||||
// TODO(jbd): Add message events for request and response size.
|
||||
|
||||
// RoundTrip creates a trace.Span and inserts it into the outgoing request's headers.
|
||||
// The created span can follow a parent span, if a parent is presented in
|
||||
// the request's context.
|
||||
func (t *traceTransport) RoundTrip(req *http.Request) (*http.Response, error) { |
||||
name := t.formatSpanName(req) |
||||
// TODO(jbd): Discuss whether we want to prefix
|
||||
// outgoing requests with Sent.
|
||||
ctx, span := trace.StartSpan(req.Context(), name, |
||||
trace.WithSampler(t.startOptions.Sampler), |
||||
trace.WithSpanKind(trace.SpanKindClient)) |
||||
|
||||
if t.newClientTrace != nil { |
||||
req = req.WithContext(httptrace.WithClientTrace(ctx, t.newClientTrace(req, span))) |
||||
} else { |
||||
req = req.WithContext(ctx) |
||||
} |
||||
|
||||
if t.format != nil { |
||||
// SpanContextToRequest will modify its Request argument, which is
|
||||
// contrary to the contract for http.RoundTripper, so we need to
|
||||
// pass it a copy of the Request.
|
||||
// However, the Request struct itself was already copied by
|
||||
// the WithContext calls above and so we just need to copy the header.
|
||||
header := make(http.Header) |
||||
for k, v := range req.Header { |
||||
header[k] = v |
||||
} |
||||
req.Header = header |
||||
t.format.SpanContextToRequest(span.SpanContext(), req) |
||||
} |
||||
|
||||
span.AddAttributes(requestAttrs(req)...) |
||||
resp, err := t.base.RoundTrip(req) |
||||
if err != nil { |
||||
span.SetStatus(trace.Status{Code: trace.StatusCodeUnknown, Message: err.Error()}) |
||||
span.End() |
||||
return resp, err |
||||
} |
||||
|
||||
span.AddAttributes(responseAttrs(resp)...) |
||||
span.SetStatus(TraceStatus(resp.StatusCode, resp.Status)) |
||||
|
||||
// span.End() will be invoked after
|
||||
// a read from resp.Body returns io.EOF or when
|
||||
// resp.Body.Close() is invoked.
|
||||
bt := &bodyTracker{rc: resp.Body, span: span} |
||||
resp.Body = wrappedBody(bt, resp.Body) |
||||
return resp, err |
||||
} |
||||
|
||||
// bodyTracker wraps a response.Body and invokes
|
||||
// trace.EndSpan on encountering io.EOF on reading
|
||||
// the body of the original response.
|
||||
type bodyTracker struct { |
||||
rc io.ReadCloser |
||||
span *trace.Span |
||||
} |
||||
|
||||
var _ io.ReadCloser = (*bodyTracker)(nil) |
||||
|
||||
func (bt *bodyTracker) Read(b []byte) (int, error) { |
||||
n, err := bt.rc.Read(b) |
||||
|
||||
switch err { |
||||
case nil: |
||||
return n, nil |
||||
case io.EOF: |
||||
bt.span.End() |
||||
default: |
||||
// For all other errors, set the span status
|
||||
bt.span.SetStatus(trace.Status{ |
||||
// Code 2 is the error code for Internal server error.
|
||||
Code: 2, |
||||
Message: err.Error(), |
||||
}) |
||||
} |
||||
return n, err |
||||
} |
||||
|
||||
func (bt *bodyTracker) Close() error { |
||||
// Invoking endSpan on Close will help catch the cases
|
||||
// in which a read returned a non-nil error, we set the
|
||||
// span status but didn't end the span.
|
||||
bt.span.End() |
||||
return bt.rc.Close() |
||||
} |
||||
|
||||
// CancelRequest cancels an in-flight request by closing its connection.
|
||||
func (t *traceTransport) CancelRequest(req *http.Request) { |
||||
type canceler interface { |
||||
CancelRequest(*http.Request) |
||||
} |
||||
if cr, ok := t.base.(canceler); ok { |
||||
cr.CancelRequest(req) |
||||
} |
||||
} |
||||
|
||||
func spanNameFromURL(req *http.Request) string { |
||||
return req.URL.Path |
||||
} |
||||
|
||||
func requestAttrs(r *http.Request) []trace.Attribute { |
||||
userAgent := r.UserAgent() |
||||
|
||||
attrs := make([]trace.Attribute, 0, 5) |
||||
attrs = append(attrs, |
||||
trace.StringAttribute(PathAttribute, r.URL.Path), |
||||
trace.StringAttribute(URLAttribute, r.URL.String()), |
||||
trace.StringAttribute(HostAttribute, r.Host), |
||||
trace.StringAttribute(MethodAttribute, r.Method), |
||||
) |
||||
|
||||
if userAgent != "" { |
||||
attrs = append(attrs, trace.StringAttribute(UserAgentAttribute, userAgent)) |
||||
} |
||||
|
||||
return attrs |
||||
} |
||||
|
||||
func responseAttrs(resp *http.Response) []trace.Attribute { |
||||
return []trace.Attribute{ |
||||
trace.Int64Attribute(StatusCodeAttribute, int64(resp.StatusCode)), |
||||
} |
||||
} |
||||
|
||||
// TraceStatus is a utility to convert the HTTP status code to a trace.Status that
|
||||
// represents the outcome as closely as possible.
|
||||
func TraceStatus(httpStatusCode int, statusLine string) trace.Status { |
||||
var code int32 |
||||
if httpStatusCode < 200 || httpStatusCode >= 400 { |
||||
code = trace.StatusCodeUnknown |
||||
} |
||||
switch httpStatusCode { |
||||
case 499: |
||||
code = trace.StatusCodeCancelled |
||||
case http.StatusBadRequest: |
||||
code = trace.StatusCodeInvalidArgument |
||||
case http.StatusGatewayTimeout: |
||||
code = trace.StatusCodeDeadlineExceeded |
||||
case http.StatusNotFound: |
||||
code = trace.StatusCodeNotFound |
||||
case http.StatusForbidden: |
||||
code = trace.StatusCodePermissionDenied |
||||
case http.StatusUnauthorized: // 401 is actually unauthenticated.
|
||||
code = trace.StatusCodeUnauthenticated |
||||
case http.StatusTooManyRequests: |
||||
code = trace.StatusCodeResourceExhausted |
||||
case http.StatusNotImplemented: |
||||
code = trace.StatusCodeUnimplemented |
||||
case http.StatusServiceUnavailable: |
||||
code = trace.StatusCodeUnavailable |
||||
case http.StatusOK: |
||||
code = trace.StatusCodeOK |
||||
} |
||||
return trace.Status{Code: code, Message: codeToStr[code]} |
||||
} |
||||
|
||||
var codeToStr = map[int32]string{ |
||||
trace.StatusCodeOK: `OK`, |
||||
trace.StatusCodeCancelled: `CANCELLED`, |
||||
trace.StatusCodeUnknown: `UNKNOWN`, |
||||
trace.StatusCodeInvalidArgument: `INVALID_ARGUMENT`, |
||||
trace.StatusCodeDeadlineExceeded: `DEADLINE_EXCEEDED`, |
||||
trace.StatusCodeNotFound: `NOT_FOUND`, |
||||
trace.StatusCodeAlreadyExists: `ALREADY_EXISTS`, |
||||
trace.StatusCodePermissionDenied: `PERMISSION_DENIED`, |
||||
trace.StatusCodeResourceExhausted: `RESOURCE_EXHAUSTED`, |
||||
trace.StatusCodeFailedPrecondition: `FAILED_PRECONDITION`, |
||||
trace.StatusCodeAborted: `ABORTED`, |
||||
trace.StatusCodeOutOfRange: `OUT_OF_RANGE`, |
||||
trace.StatusCodeUnimplemented: `UNIMPLEMENTED`, |
||||
trace.StatusCodeInternal: `INTERNAL`, |
||||
trace.StatusCodeUnavailable: `UNAVAILABLE`, |
||||
trace.StatusCodeDataLoss: `DATA_LOSS`, |
||||
trace.StatusCodeUnauthenticated: `UNAUTHENTICATED`, |
||||
} |
||||
|
||||
func isHealthEndpoint(path string) bool { |
||||
// Health checking is pretty frequent and
|
||||
// traces collected for health endpoints
|
||||
// can be extremely noisy and expensive.
|
||||
// Disable canonical health checking endpoints
|
||||
// like /healthz and /_ah/health for now.
|
||||
if path == "/healthz" || path == "/_ah/health" { |
||||
return true |
||||
} |
||||
return false |
||||
} |
||||
@ -0,0 +1,44 @@
|
||||
// Copyright 2019, OpenCensus Authors
|
||||
//
|
||||
// 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 ochttp |
||||
|
||||
import ( |
||||
"io" |
||||
) |
||||
|
||||
// wrappedBody returns a wrapped version of the original
|
||||
// Body and only implements the same combination of additional
|
||||
// interfaces as the original.
|
||||
func wrappedBody(wrapper io.ReadCloser, body io.ReadCloser) io.ReadCloser { |
||||
var ( |
||||
wr, i0 = body.(io.Writer) |
||||
) |
||||
switch { |
||||
case !i0: |
||||
return struct { |
||||
io.ReadCloser |
||||
}{wrapper} |
||||
|
||||
case i0: |
||||
return struct { |
||||
io.ReadCloser |
||||
io.Writer |
||||
}{wrapper, wr} |
||||
default: |
||||
return struct { |
||||
io.ReadCloser |
||||
}{wrapper} |
||||
} |
||||
} |
||||
@ -0,0 +1,164 @@
|
||||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// 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 resource provides functionality for resource, which capture
|
||||
// identifying information about the entities for which signals are exported.
|
||||
package resource |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
"os" |
||||
"regexp" |
||||
"sort" |
||||
"strconv" |
||||
"strings" |
||||
) |
||||
|
||||
// Environment variables used by FromEnv to decode a resource.
|
||||
const ( |
||||
EnvVarType = "OC_RESOURCE_TYPE" |
||||
EnvVarLabels = "OC_RESOURCE_LABELS" |
||||
) |
||||
|
||||
// Resource describes an entity about which identifying information and metadata is exposed.
|
||||
// For example, a type "k8s.io/container" may hold labels describing the pod name and namespace.
|
||||
type Resource struct { |
||||
Type string |
||||
Labels map[string]string |
||||
} |
||||
|
||||
// EncodeLabels encodes a labels map to a string as provided via the OC_RESOURCE_LABELS environment variable.
|
||||
func EncodeLabels(labels map[string]string) string { |
||||
sortedKeys := make([]string, 0, len(labels)) |
||||
for k := range labels { |
||||
sortedKeys = append(sortedKeys, k) |
||||
} |
||||
sort.Strings(sortedKeys) |
||||
|
||||
s := "" |
||||
for i, k := range sortedKeys { |
||||
if i > 0 { |
||||
s += "," |
||||
} |
||||
s += k + "=" + strconv.Quote(labels[k]) |
||||
} |
||||
return s |
||||
} |
||||
|
||||
var labelRegex = regexp.MustCompile(`^\s*([[:ascii:]]{1,256}?)=("[[:ascii:]]{0,256}?")\s*,`) |
||||
|
||||
// DecodeLabels decodes a serialized label map as used in the OC_RESOURCE_LABELS variable.
|
||||
// A list of labels of the form `<key1>="<value1>",<key2>="<value2>",...` is accepted.
|
||||
// Domain names and paths are accepted as label keys.
|
||||
// Most users will want to use FromEnv instead.
|
||||
func DecodeLabels(s string) (map[string]string, error) { |
||||
m := map[string]string{} |
||||
// Ensure a trailing comma, which allows us to keep the regex simpler
|
||||
s = strings.TrimRight(strings.TrimSpace(s), ",") + "," |
||||
|
||||
for len(s) > 0 { |
||||
match := labelRegex.FindStringSubmatch(s) |
||||
if len(match) == 0 { |
||||
return nil, fmt.Errorf("invalid label formatting, remainder: %s", s) |
||||
} |
||||
v := match[2] |
||||
if v == "" { |
||||
v = match[3] |
||||
} else { |
||||
var err error |
||||
if v, err = strconv.Unquote(v); err != nil { |
||||
return nil, fmt.Errorf("invalid label formatting, remainder: %s, err: %s", s, err) |
||||
} |
||||
} |
||||
m[match[1]] = v |
||||
|
||||
s = s[len(match[0]):] |
||||
} |
||||
return m, nil |
||||
} |
||||
|
||||
// FromEnv is a detector that loads resource information from the OC_RESOURCE_TYPE
|
||||
// and OC_RESOURCE_labelS environment variables.
|
||||
func FromEnv(context.Context) (*Resource, error) { |
||||
res := &Resource{ |
||||
Type: strings.TrimSpace(os.Getenv(EnvVarType)), |
||||
} |
||||
labels := strings.TrimSpace(os.Getenv(EnvVarLabels)) |
||||
if labels == "" { |
||||
return res, nil |
||||
} |
||||
var err error |
||||
if res.Labels, err = DecodeLabels(labels); err != nil { |
||||
return nil, err |
||||
} |
||||
return res, nil |
||||
} |
||||
|
||||
var _ Detector = FromEnv |
||||
|
||||
// merge resource information from b into a. In case of a collision, a takes precedence.
|
||||
func merge(a, b *Resource) *Resource { |
||||
if a == nil { |
||||
return b |
||||
} |
||||
if b == nil { |
||||
return a |
||||
} |
||||
res := &Resource{ |
||||
Type: a.Type, |
||||
Labels: map[string]string{}, |
||||
} |
||||
if res.Type == "" { |
||||
res.Type = b.Type |
||||
} |
||||
for k, v := range b.Labels { |
||||
res.Labels[k] = v |
||||
} |
||||
// Labels from resource a overwrite labels from resource b.
|
||||
for k, v := range a.Labels { |
||||
res.Labels[k] = v |
||||
} |
||||
return res |
||||
} |
||||
|
||||
// Detector attempts to detect resource information.
|
||||
// If the detector cannot find resource information, the returned resource is nil but no
|
||||
// error is returned.
|
||||
// An error is only returned on unexpected failures.
|
||||
type Detector func(context.Context) (*Resource, error) |
||||
|
||||
// MultiDetector returns a Detector that calls all input detectors in order and
|
||||
// merges each result with the previous one. In case a type of label key is already set,
|
||||
// the first set value is takes precedence.
|
||||
// It returns on the first error that a sub-detector encounters.
|
||||
func MultiDetector(detectors ...Detector) Detector { |
||||
return func(ctx context.Context) (*Resource, error) { |
||||
return detectAll(ctx, detectors...) |
||||
} |
||||
} |
||||
|
||||
// detectall calls all input detectors sequentially an merges each result with the previous one.
|
||||
// It returns on the first error that a sub-detector encounters.
|
||||
func detectAll(ctx context.Context, detectors ...Detector) (*Resource, error) { |
||||
var res *Resource |
||||
for _, d := range detectors { |
||||
r, err := d(ctx) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
res = merge(res, r) |
||||
} |
||||
return res, nil |
||||
} |
||||
@ -0,0 +1,69 @@
|
||||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// 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 stats contains support for OpenCensus stats recording. |
||||
|
||||
OpenCensus allows users to create typed measures, record measurements, |
||||
aggregate the collected data, and export the aggregated data. |
||||
|
||||
Measures |
||||
|
||||
A measure represents a type of data point to be tracked and recorded. |
||||
For example, latency, request Mb/s, and response Mb/s are measures |
||||
to collect from a server. |
||||
|
||||
Measure constructors such as Int64 and Float64 automatically |
||||
register the measure by the given name. Each registered measure needs |
||||
to be unique by name. Measures also have a description and a unit. |
||||
|
||||
Libraries can define and export measures. Application authors can then |
||||
create views and collect and break down measures by the tags they are |
||||
interested in. |
||||
|
||||
Recording measurements |
||||
|
||||
Measurement is a data point to be collected for a measure. For example, |
||||
for a latency (ms) measure, 100 is a measurement that represents a 100ms |
||||
latency event. Measurements are created from measures with |
||||
the current context. Tags from the current context are recorded with the |
||||
measurements if they are any. |
||||
|
||||
Recorded measurements are dropped immediately if no views are registered for them. |
||||
There is usually no need to conditionally enable and disable |
||||
recording to reduce cost. Recording of measurements is cheap. |
||||
|
||||
Libraries can always record measurements, and applications can later decide |
||||
on which measurements they want to collect by registering views. This allows |
||||
libraries to turn on the instrumentation by default. |
||||
|
||||
Exemplars |
||||
|
||||
For a given recorded measurement, the associated exemplar is a diagnostic map |
||||
that gives more information about the measurement. |
||||
|
||||
When aggregated using a Distribution aggregation, an exemplar is kept for each |
||||
bucket in the Distribution. This allows you to easily find an example of a |
||||
measurement that fell into each bucket. |
||||
|
||||
For example, if you also use the OpenCensus trace package and you |
||||
record a measurement with a context that contains a sampled trace span, |
||||
then the trace span will be added to the exemplar associated with the measurement. |
||||
|
||||
When exported to a supporting back end, you should be able to easily navigate |
||||
to example traces that fell into each bucket in the Distribution. |
||||
|
||||
*/ |
||||
package stats // import "go.opencensus.io/stats"
|
||||
@ -0,0 +1,25 @@
|
||||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// 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 internal |
||||
|
||||
import ( |
||||
"go.opencensus.io/tag" |
||||
) |
||||
|
||||
// DefaultRecorder will be called for each Record call.
|
||||
var DefaultRecorder func(tags *tag.Map, measurement interface{}, attachments map[string]interface{}) |
||||
|
||||
// SubscriptionReporter reports when a view subscribed with a measure.
|
||||
var SubscriptionReporter func(measure string) |
||||
@ -0,0 +1,109 @@
|
||||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// 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 stats |
||||
|
||||
import ( |
||||
"sync" |
||||
"sync/atomic" |
||||
) |
||||
|
||||
// Measure represents a single numeric value to be tracked and recorded.
|
||||
// For example, latency, request bytes, and response bytes could be measures
|
||||
// to collect from a server.
|
||||
//
|
||||
// Measures by themselves have no outside effects. In order to be exported,
|
||||
// the measure needs to be used in a View. If no Views are defined over a
|
||||
// measure, there is very little cost in recording it.
|
||||
type Measure interface { |
||||
// Name returns the name of this measure.
|
||||
//
|
||||
// Measure names are globally unique (among all libraries linked into your program).
|
||||
// We recommend prefixing the measure name with a domain name relevant to your
|
||||
// project or application.
|
||||
//
|
||||
// Measure names are never sent over the wire or exported to backends.
|
||||
// They are only used to create Views.
|
||||
Name() string |
||||
|
||||
// Description returns the human-readable description of this measure.
|
||||
Description() string |
||||
|
||||
// Unit returns the units for the values this measure takes on.
|
||||
//
|
||||
// Units are encoded according to the case-sensitive abbreviations from the
|
||||
// Unified Code for Units of Measure: http://unitsofmeasure.org/ucum.html
|
||||
Unit() string |
||||
} |
||||
|
||||
// measureDescriptor is the untyped descriptor associated with each measure.
|
||||
// Int64Measure and Float64Measure wrap measureDescriptor to provide typed
|
||||
// recording APIs.
|
||||
// Two Measures with the same name will have the same measureDescriptor.
|
||||
type measureDescriptor struct { |
||||
subs int32 // access atomically
|
||||
|
||||
name string |
||||
description string |
||||
unit string |
||||
} |
||||
|
||||
func (m *measureDescriptor) subscribe() { |
||||
atomic.StoreInt32(&m.subs, 1) |
||||
} |
||||
|
||||
func (m *measureDescriptor) subscribed() bool { |
||||
return atomic.LoadInt32(&m.subs) == 1 |
||||
} |
||||
|
||||
var ( |
||||
mu sync.RWMutex |
||||
measures = make(map[string]*measureDescriptor) |
||||
) |
||||
|
||||
func registerMeasureHandle(name, desc, unit string) *measureDescriptor { |
||||
mu.Lock() |
||||
defer mu.Unlock() |
||||
|
||||
if stored, ok := measures[name]; ok { |
||||
return stored |
||||
} |
||||
m := &measureDescriptor{ |
||||
name: name, |
||||
description: desc, |
||||
unit: unit, |
||||
} |
||||
measures[name] = m |
||||
return m |
||||
} |
||||
|
||||
// Measurement is the numeric value measured when recording stats. Each measure
|
||||
// provides methods to create measurements of their kind. For example, Int64Measure
|
||||
// provides M to convert an int64 into a measurement.
|
||||
type Measurement struct { |
||||
v float64 |
||||
m Measure |
||||
desc *measureDescriptor |
||||
} |
||||
|
||||
// Value returns the value of the Measurement as a float64.
|
||||
func (m Measurement) Value() float64 { |
||||
return m.v |
||||
} |
||||
|
||||
// Measure returns the Measure from which this Measurement was created.
|
||||
func (m Measurement) Measure() Measure { |
||||
return m.m |
||||
} |
||||
@ -0,0 +1,55 @@
|
||||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// 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 stats |
||||
|
||||
// Float64Measure is a measure for float64 values.
|
||||
type Float64Measure struct { |
||||
desc *measureDescriptor |
||||
} |
||||
|
||||
// M creates a new float64 measurement.
|
||||
// Use Record to record measurements.
|
||||
func (m *Float64Measure) M(v float64) Measurement { |
||||
return Measurement{ |
||||
m: m, |
||||
desc: m.desc, |
||||
v: v, |
||||
} |
||||
} |
||||
|
||||
// Float64 creates a new measure for float64 values.
|
||||
//
|
||||
// See the documentation for interface Measure for more guidance on the
|
||||
// parameters of this function.
|
||||
func Float64(name, description, unit string) *Float64Measure { |
||||
mi := registerMeasureHandle(name, description, unit) |
||||
return &Float64Measure{mi} |
||||
} |
||||
|
||||
// Name returns the name of the measure.
|
||||
func (m *Float64Measure) Name() string { |
||||
return m.desc.name |
||||
} |
||||
|
||||
// Description returns the description of the measure.
|
||||
func (m *Float64Measure) Description() string { |
||||
return m.desc.description |
||||
} |
||||
|
||||
// Unit returns the unit of the measure.
|
||||
func (m *Float64Measure) Unit() string { |
||||
return m.desc.unit |
||||
} |
||||
@ -0,0 +1,55 @@
|
||||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// 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 stats |
||||
|
||||
// Int64Measure is a measure for int64 values.
|
||||
type Int64Measure struct { |
||||
desc *measureDescriptor |
||||
} |
||||
|
||||
// M creates a new int64 measurement.
|
||||
// Use Record to record measurements.
|
||||
func (m *Int64Measure) M(v int64) Measurement { |
||||
return Measurement{ |
||||
m: m, |
||||
desc: m.desc, |
||||
v: float64(v), |
||||
} |
||||
} |
||||
|
||||
// Int64 creates a new measure for int64 values.
|
||||
//
|
||||
// See the documentation for interface Measure for more guidance on the
|
||||
// parameters of this function.
|
||||
func Int64(name, description, unit string) *Int64Measure { |
||||
mi := registerMeasureHandle(name, description, unit) |
||||
return &Int64Measure{mi} |
||||
} |
||||
|
||||
// Name returns the name of the measure.
|
||||
func (m *Int64Measure) Name() string { |
||||
return m.desc.name |
||||
} |
||||
|
||||
// Description returns the description of the measure.
|
||||
func (m *Int64Measure) Description() string { |
||||
return m.desc.description |
||||
} |
||||
|
||||
// Unit returns the unit of the measure.
|
||||
func (m *Int64Measure) Unit() string { |
||||
return m.desc.unit |
||||
} |
||||
@ -0,0 +1,117 @@
|
||||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// 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 stats |
||||
|
||||
import ( |
||||
"context" |
||||
|
||||
"go.opencensus.io/metric/metricdata" |
||||
"go.opencensus.io/stats/internal" |
||||
"go.opencensus.io/tag" |
||||
) |
||||
|
||||
func init() { |
||||
internal.SubscriptionReporter = func(measure string) { |
||||
mu.Lock() |
||||
measures[measure].subscribe() |
||||
mu.Unlock() |
||||
} |
||||
} |
||||
|
||||
type recordOptions struct { |
||||
attachments metricdata.Attachments |
||||
mutators []tag.Mutator |
||||
measurements []Measurement |
||||
} |
||||
|
||||
// WithAttachments applies provided exemplar attachments.
|
||||
func WithAttachments(attachments metricdata.Attachments) Options { |
||||
return func(ro *recordOptions) { |
||||
ro.attachments = attachments |
||||
} |
||||
} |
||||
|
||||
// WithTags applies provided tag mutators.
|
||||
func WithTags(mutators ...tag.Mutator) Options { |
||||
return func(ro *recordOptions) { |
||||
ro.mutators = mutators |
||||
} |
||||
} |
||||
|
||||
// WithMeasurements applies provided measurements.
|
||||
func WithMeasurements(measurements ...Measurement) Options { |
||||
return func(ro *recordOptions) { |
||||
ro.measurements = measurements |
||||
} |
||||
} |
||||
|
||||
// Options apply changes to recordOptions.
|
||||
type Options func(*recordOptions) |
||||
|
||||
func createRecordOption(ros ...Options) *recordOptions { |
||||
o := &recordOptions{} |
||||
for _, ro := range ros { |
||||
ro(o) |
||||
} |
||||
return o |
||||
} |
||||
|
||||
// Record records one or multiple measurements with the same context at once.
|
||||
// If there are any tags in the context, measurements will be tagged with them.
|
||||
func Record(ctx context.Context, ms ...Measurement) { |
||||
RecordWithOptions(ctx, WithMeasurements(ms...)) |
||||
} |
||||
|
||||
// RecordWithTags records one or multiple measurements at once.
|
||||
//
|
||||
// Measurements will be tagged with the tags in the context mutated by the mutators.
|
||||
// RecordWithTags is useful if you want to record with tag mutations but don't want
|
||||
// to propagate the mutations in the context.
|
||||
func RecordWithTags(ctx context.Context, mutators []tag.Mutator, ms ...Measurement) error { |
||||
return RecordWithOptions(ctx, WithTags(mutators...), WithMeasurements(ms...)) |
||||
} |
||||
|
||||
// RecordWithOptions records measurements from the given options (if any) against context
|
||||
// and tags and attachments in the options (if any).
|
||||
// If there are any tags in the context, measurements will be tagged with them.
|
||||
func RecordWithOptions(ctx context.Context, ros ...Options) error { |
||||
o := createRecordOption(ros...) |
||||
if len(o.measurements) == 0 { |
||||
return nil |
||||
} |
||||
recorder := internal.DefaultRecorder |
||||
if recorder == nil { |
||||
return nil |
||||
} |
||||
record := false |
||||
for _, m := range o.measurements { |
||||
if m.desc.subscribed() { |
||||
record = true |
||||
break |
||||
} |
||||
} |
||||
if !record { |
||||
return nil |
||||
} |
||||
if len(o.mutators) > 0 { |
||||
var err error |
||||
if ctx, err = tag.New(ctx, o.mutators...); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
recorder(tag.FromContext(ctx), o.measurements, o.attachments) |
||||
return nil |
||||
} |
||||
@ -0,0 +1,25 @@
|
||||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// 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 stats |
||||
|
||||
// Units are encoded according to the case-sensitive abbreviations from the
|
||||
// Unified Code for Units of Measure: http://unitsofmeasure.org/ucum.html
|
||||
const ( |
||||
UnitNone = "1" // Deprecated: Use UnitDimensionless.
|
||||
UnitDimensionless = "1" |
||||
UnitBytes = "By" |
||||
UnitMilliseconds = "ms" |
||||
) |
||||
@ -0,0 +1,120 @@
|
||||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// 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 view |
||||
|
||||
// AggType represents the type of aggregation function used on a View.
|
||||
type AggType int |
||||
|
||||
// All available aggregation types.
|
||||
const ( |
||||
AggTypeNone AggType = iota // no aggregation; reserved for future use.
|
||||
AggTypeCount // the count aggregation, see Count.
|
||||
AggTypeSum // the sum aggregation, see Sum.
|
||||
AggTypeDistribution // the distribution aggregation, see Distribution.
|
||||
AggTypeLastValue // the last value aggregation, see LastValue.
|
||||
) |
||||
|
||||
func (t AggType) String() string { |
||||
return aggTypeName[t] |
||||
} |
||||
|
||||
var aggTypeName = map[AggType]string{ |
||||
AggTypeNone: "None", |
||||
AggTypeCount: "Count", |
||||
AggTypeSum: "Sum", |
||||
AggTypeDistribution: "Distribution", |
||||
AggTypeLastValue: "LastValue", |
||||
} |
||||
|
||||
// Aggregation represents a data aggregation method. Use one of the functions:
|
||||
// Count, Sum, or Distribution to construct an Aggregation.
|
||||
type Aggregation struct { |
||||
Type AggType // Type is the AggType of this Aggregation.
|
||||
Buckets []float64 // Buckets are the bucket endpoints if this Aggregation represents a distribution, see Distribution.
|
||||
|
||||
newData func() AggregationData |
||||
} |
||||
|
||||
var ( |
||||
aggCount = &Aggregation{ |
||||
Type: AggTypeCount, |
||||
newData: func() AggregationData { |
||||
return &CountData{} |
||||
}, |
||||
} |
||||
aggSum = &Aggregation{ |
||||
Type: AggTypeSum, |
||||
newData: func() AggregationData { |
||||
return &SumData{} |
||||
}, |
||||
} |
||||
) |
||||
|
||||
// Count indicates that data collected and aggregated
|
||||
// with this method will be turned into a count value.
|
||||
// For example, total number of accepted requests can be
|
||||
// aggregated by using Count.
|
||||
func Count() *Aggregation { |
||||
return aggCount |
||||
} |
||||
|
||||
// Sum indicates that data collected and aggregated
|
||||
// with this method will be summed up.
|
||||
// For example, accumulated request bytes can be aggregated by using
|
||||
// Sum.
|
||||
func Sum() *Aggregation { |
||||
return aggSum |
||||
} |
||||
|
||||
// Distribution indicates that the desired aggregation is
|
||||
// a histogram distribution.
|
||||
//
|
||||
// An distribution aggregation may contain a histogram of the values in the
|
||||
// population. The bucket boundaries for that histogram are described
|
||||
// by the bounds. This defines len(bounds)+1 buckets.
|
||||
//
|
||||
// If len(bounds) >= 2 then the boundaries for bucket index i are:
|
||||
//
|
||||
// [-infinity, bounds[i]) for i = 0
|
||||
// [bounds[i-1], bounds[i]) for 0 < i < length
|
||||
// [bounds[i-1], +infinity) for i = length
|
||||
//
|
||||
// If len(bounds) is 0 then there is no histogram associated with the
|
||||
// distribution. There will be a single bucket with boundaries
|
||||
// (-infinity, +infinity).
|
||||
//
|
||||
// If len(bounds) is 1 then there is no finite buckets, and that single
|
||||
// element is the common boundary of the overflow and underflow buckets.
|
||||
func Distribution(bounds ...float64) *Aggregation { |
||||
return &Aggregation{ |
||||
Type: AggTypeDistribution, |
||||
Buckets: bounds, |
||||
newData: func() AggregationData { |
||||
return newDistributionData(bounds) |
||||
}, |
||||
} |
||||
} |
||||
|
||||
// LastValue only reports the last value recorded using this
|
||||
// aggregation. All other measurements will be dropped.
|
||||
func LastValue() *Aggregation { |
||||
return &Aggregation{ |
||||
Type: AggTypeLastValue, |
||||
newData: func() AggregationData { |
||||
return &LastValueData{} |
||||
}, |
||||
} |
||||
} |
||||
@ -0,0 +1,293 @@
|
||||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// 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 view |
||||
|
||||
import ( |
||||
"math" |
||||
"time" |
||||
|
||||
"go.opencensus.io/metric/metricdata" |
||||
) |
||||
|
||||
// AggregationData represents an aggregated value from a collection.
|
||||
// They are reported on the view data during exporting.
|
||||
// Mosts users won't directly access aggregration data.
|
||||
type AggregationData interface { |
||||
isAggregationData() bool |
||||
addSample(v float64, attachments map[string]interface{}, t time.Time) |
||||
clone() AggregationData |
||||
equal(other AggregationData) bool |
||||
toPoint(t metricdata.Type, time time.Time) metricdata.Point |
||||
} |
||||
|
||||
const epsilon = 1e-9 |
||||
|
||||
// CountData is the aggregated data for the Count aggregation.
|
||||
// A count aggregation processes data and counts the recordings.
|
||||
//
|
||||
// Most users won't directly access count data.
|
||||
type CountData struct { |
||||
Value int64 |
||||
} |
||||
|
||||
func (a *CountData) isAggregationData() bool { return true } |
||||
|
||||
func (a *CountData) addSample(_ float64, _ map[string]interface{}, _ time.Time) { |
||||
a.Value = a.Value + 1 |
||||
} |
||||
|
||||
func (a *CountData) clone() AggregationData { |
||||
return &CountData{Value: a.Value} |
||||
} |
||||
|
||||
func (a *CountData) equal(other AggregationData) bool { |
||||
a2, ok := other.(*CountData) |
||||
if !ok { |
||||
return false |
||||
} |
||||
|
||||
return a.Value == a2.Value |
||||
} |
||||
|
||||
func (a *CountData) toPoint(metricType metricdata.Type, t time.Time) metricdata.Point { |
||||
switch metricType { |
||||
case metricdata.TypeCumulativeInt64: |
||||
return metricdata.NewInt64Point(t, a.Value) |
||||
default: |
||||
panic("unsupported metricdata.Type") |
||||
} |
||||
} |
||||
|
||||
// SumData is the aggregated data for the Sum aggregation.
|
||||
// A sum aggregation processes data and sums up the recordings.
|
||||
//
|
||||
// Most users won't directly access sum data.
|
||||
type SumData struct { |
||||
Value float64 |
||||
} |
||||
|
||||
func (a *SumData) isAggregationData() bool { return true } |
||||
|
||||
func (a *SumData) addSample(v float64, _ map[string]interface{}, _ time.Time) { |
||||
a.Value += v |
||||
} |
||||
|
||||
func (a *SumData) clone() AggregationData { |
||||
return &SumData{Value: a.Value} |
||||
} |
||||
|
||||
func (a *SumData) equal(other AggregationData) bool { |
||||
a2, ok := other.(*SumData) |
||||
if !ok { |
||||
return false |
||||
} |
||||
return math.Pow(a.Value-a2.Value, 2) < epsilon |
||||
} |
||||
|
||||
func (a *SumData) toPoint(metricType metricdata.Type, t time.Time) metricdata.Point { |
||||
switch metricType { |
||||
case metricdata.TypeCumulativeInt64: |
||||
return metricdata.NewInt64Point(t, int64(a.Value)) |
||||
case metricdata.TypeCumulativeFloat64: |
||||
return metricdata.NewFloat64Point(t, a.Value) |
||||
default: |
||||
panic("unsupported metricdata.Type") |
||||
} |
||||
} |
||||
|
||||
// DistributionData is the aggregated data for the
|
||||
// Distribution aggregation.
|
||||
//
|
||||
// Most users won't directly access distribution data.
|
||||
//
|
||||
// For a distribution with N bounds, the associated DistributionData will have
|
||||
// N+1 buckets.
|
||||
type DistributionData struct { |
||||
Count int64 // number of data points aggregated
|
||||
Min float64 // minimum value in the distribution
|
||||
Max float64 // max value in the distribution
|
||||
Mean float64 // mean of the distribution
|
||||
SumOfSquaredDev float64 // sum of the squared deviation from the mean
|
||||
CountPerBucket []int64 // number of occurrences per bucket
|
||||
// ExemplarsPerBucket is slice the same length as CountPerBucket containing
|
||||
// an exemplar for the associated bucket, or nil.
|
||||
ExemplarsPerBucket []*metricdata.Exemplar |
||||
bounds []float64 // histogram distribution of the values
|
||||
} |
||||
|
||||
func newDistributionData(bounds []float64) *DistributionData { |
||||
bucketCount := len(bounds) + 1 |
||||
return &DistributionData{ |
||||
CountPerBucket: make([]int64, bucketCount), |
||||
ExemplarsPerBucket: make([]*metricdata.Exemplar, bucketCount), |
||||
bounds: bounds, |
||||
Min: math.MaxFloat64, |
||||
Max: math.SmallestNonzeroFloat64, |
||||
} |
||||
} |
||||
|
||||
// Sum returns the sum of all samples collected.
|
||||
func (a *DistributionData) Sum() float64 { return a.Mean * float64(a.Count) } |
||||
|
||||
func (a *DistributionData) variance() float64 { |
||||
if a.Count <= 1 { |
||||
return 0 |
||||
} |
||||
return a.SumOfSquaredDev / float64(a.Count-1) |
||||
} |
||||
|
||||
func (a *DistributionData) isAggregationData() bool { return true } |
||||
|
||||
// TODO(songy23): support exemplar attachments.
|
||||
func (a *DistributionData) addSample(v float64, attachments map[string]interface{}, t time.Time) { |
||||
if v < a.Min { |
||||
a.Min = v |
||||
} |
||||
if v > a.Max { |
||||
a.Max = v |
||||
} |
||||
a.Count++ |
||||
a.addToBucket(v, attachments, t) |
||||
|
||||
if a.Count == 1 { |
||||
a.Mean = v |
||||
return |
||||
} |
||||
|
||||
oldMean := a.Mean |
||||
a.Mean = a.Mean + (v-a.Mean)/float64(a.Count) |
||||
a.SumOfSquaredDev = a.SumOfSquaredDev + (v-oldMean)*(v-a.Mean) |
||||
} |
||||
|
||||
func (a *DistributionData) addToBucket(v float64, attachments map[string]interface{}, t time.Time) { |
||||
var count *int64 |
||||
var i int |
||||
var b float64 |
||||
for i, b = range a.bounds { |
||||
if v < b { |
||||
count = &a.CountPerBucket[i] |
||||
break |
||||
} |
||||
} |
||||
if count == nil { // Last bucket.
|
||||
i = len(a.bounds) |
||||
count = &a.CountPerBucket[i] |
||||
} |
||||
*count++ |
||||
if exemplar := getExemplar(v, attachments, t); exemplar != nil { |
||||
a.ExemplarsPerBucket[i] = exemplar |
||||
} |
||||
} |
||||
|
||||
func getExemplar(v float64, attachments map[string]interface{}, t time.Time) *metricdata.Exemplar { |
||||
if len(attachments) == 0 { |
||||
return nil |
||||
} |
||||
return &metricdata.Exemplar{ |
||||
Value: v, |
||||
Timestamp: t, |
||||
Attachments: attachments, |
||||
} |
||||
} |
||||
|
||||
func (a *DistributionData) clone() AggregationData { |
||||
c := *a |
||||
c.CountPerBucket = append([]int64(nil), a.CountPerBucket...) |
||||
c.ExemplarsPerBucket = append([]*metricdata.Exemplar(nil), a.ExemplarsPerBucket...) |
||||
return &c |
||||
} |
||||
|
||||
func (a *DistributionData) equal(other AggregationData) bool { |
||||
a2, ok := other.(*DistributionData) |
||||
if !ok { |
||||
return false |
||||
} |
||||
if a2 == nil { |
||||
return false |
||||
} |
||||
if len(a.CountPerBucket) != len(a2.CountPerBucket) { |
||||
return false |
||||
} |
||||
for i := range a.CountPerBucket { |
||||
if a.CountPerBucket[i] != a2.CountPerBucket[i] { |
||||
return false |
||||
} |
||||
} |
||||
return a.Count == a2.Count && a.Min == a2.Min && a.Max == a2.Max && math.Pow(a.Mean-a2.Mean, 2) < epsilon && math.Pow(a.variance()-a2.variance(), 2) < epsilon |
||||
} |
||||
|
||||
func (a *DistributionData) toPoint(metricType metricdata.Type, t time.Time) metricdata.Point { |
||||
switch metricType { |
||||
case metricdata.TypeCumulativeDistribution: |
||||
buckets := []metricdata.Bucket{} |
||||
for i := 0; i < len(a.CountPerBucket); i++ { |
||||
buckets = append(buckets, metricdata.Bucket{ |
||||
Count: a.CountPerBucket[i], |
||||
Exemplar: a.ExemplarsPerBucket[i], |
||||
}) |
||||
} |
||||
bucketOptions := &metricdata.BucketOptions{Bounds: a.bounds} |
||||
|
||||
val := &metricdata.Distribution{ |
||||
Count: a.Count, |
||||
Sum: a.Sum(), |
||||
SumOfSquaredDeviation: a.SumOfSquaredDev, |
||||
BucketOptions: bucketOptions, |
||||
Buckets: buckets, |
||||
} |
||||
return metricdata.NewDistributionPoint(t, val) |
||||
|
||||
default: |
||||
// TODO: [rghetia] when we have a use case for TypeGaugeDistribution.
|
||||
panic("unsupported metricdata.Type") |
||||
} |
||||
} |
||||
|
||||
// LastValueData returns the last value recorded for LastValue aggregation.
|
||||
type LastValueData struct { |
||||
Value float64 |
||||
} |
||||
|
||||
func (l *LastValueData) isAggregationData() bool { |
||||
return true |
||||
} |
||||
|
||||
func (l *LastValueData) addSample(v float64, _ map[string]interface{}, _ time.Time) { |
||||
l.Value = v |
||||
} |
||||
|
||||
func (l *LastValueData) clone() AggregationData { |
||||
return &LastValueData{l.Value} |
||||
} |
||||
|
||||
func (l *LastValueData) equal(other AggregationData) bool { |
||||
a2, ok := other.(*LastValueData) |
||||
if !ok { |
||||
return false |
||||
} |
||||
return l.Value == a2.Value |
||||
} |
||||
|
||||
func (l *LastValueData) toPoint(metricType metricdata.Type, t time.Time) metricdata.Point { |
||||
switch metricType { |
||||
case metricdata.TypeGaugeInt64: |
||||
return metricdata.NewInt64Point(t, int64(l.Value)) |
||||
case metricdata.TypeGaugeFloat64: |
||||
return metricdata.NewFloat64Point(t, l.Value) |
||||
default: |
||||
panic("unsupported metricdata.Type") |
||||
} |
||||
} |
||||
@ -0,0 +1,86 @@
|
||||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// 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 view |
||||
|
||||
import ( |
||||
"sort" |
||||
"time" |
||||
|
||||
"go.opencensus.io/internal/tagencoding" |
||||
"go.opencensus.io/tag" |
||||
) |
||||
|
||||
type collector struct { |
||||
// signatures holds the aggregations values for each unique tag signature
|
||||
// (values for all keys) to its aggregator.
|
||||
signatures map[string]AggregationData |
||||
// Aggregation is the description of the aggregation to perform for this
|
||||
// view.
|
||||
a *Aggregation |
||||
} |
||||
|
||||
func (c *collector) addSample(s string, v float64, attachments map[string]interface{}, t time.Time) { |
||||
aggregator, ok := c.signatures[s] |
||||
if !ok { |
||||
aggregator = c.a.newData() |
||||
c.signatures[s] = aggregator |
||||
} |
||||
aggregator.addSample(v, attachments, t) |
||||
} |
||||
|
||||
// collectRows returns a snapshot of the collected Row values.
|
||||
func (c *collector) collectedRows(keys []tag.Key) []*Row { |
||||
rows := make([]*Row, 0, len(c.signatures)) |
||||
for sig, aggregator := range c.signatures { |
||||
tags := decodeTags([]byte(sig), keys) |
||||
row := &Row{Tags: tags, Data: aggregator.clone()} |
||||
rows = append(rows, row) |
||||
} |
||||
return rows |
||||
} |
||||
|
||||
func (c *collector) clearRows() { |
||||
c.signatures = make(map[string]AggregationData) |
||||
} |
||||
|
||||
// encodeWithKeys encodes the map by using values
|
||||
// only associated with the keys provided.
|
||||
func encodeWithKeys(m *tag.Map, keys []tag.Key) []byte { |
||||
vb := &tagencoding.Values{ |
||||
Buffer: make([]byte, len(keys)), |
||||
} |
||||
for _, k := range keys { |
||||
v, _ := m.Value(k) |
||||
vb.WriteValue([]byte(v)) |
||||
} |
||||
return vb.Bytes() |
||||
} |
||||
|
||||
// decodeTags decodes tags from the buffer and
|
||||
// orders them by the keys.
|
||||
func decodeTags(buf []byte, keys []tag.Key) []tag.Tag { |
||||
vb := &tagencoding.Values{Buffer: buf} |
||||
var tags []tag.Tag |
||||
for _, k := range keys { |
||||
v := vb.ReadValue() |
||||
if v != nil { |
||||
tags = append(tags, tag.Tag{Key: k, Value: string(v)}) |
||||
} |
||||
} |
||||
vb.ReadIndex = 0 |
||||
sort.Slice(tags, func(i, j int) bool { return tags[i].Key.Name() < tags[j].Key.Name() }) |
||||
return tags |
||||
} |
||||
@ -0,0 +1,47 @@
|
||||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// 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 view contains support for collecting and exposing aggregates over stats.
|
||||
//
|
||||
// In order to collect measurements, views need to be defined and registered.
|
||||
// A view allows recorded measurements to be filtered and aggregated.
|
||||
//
|
||||
// All recorded measurements can be grouped by a list of tags.
|
||||
//
|
||||
// OpenCensus provides several aggregation methods: Count, Distribution and Sum.
|
||||
//
|
||||
// Count only counts the number of measurement points recorded.
|
||||
// Distribution provides statistical summary of the aggregated data by counting
|
||||
// how many recorded measurements fall into each bucket.
|
||||
// Sum adds up the measurement values.
|
||||
// LastValue just keeps track of the most recently recorded measurement value.
|
||||
// All aggregations are cumulative.
|
||||
//
|
||||
// Views can be registerd and unregistered at any time during program execution.
|
||||
//
|
||||
// Libraries can define views but it is recommended that in most cases registering
|
||||
// views be left up to applications.
|
||||
//
|
||||
// Exporting
|
||||
//
|
||||
// Collected and aggregated data can be exported to a metric collection
|
||||
// backend by registering its exporter.
|
||||
//
|
||||
// Multiple exporters can be registered to upload the data to various
|
||||
// different back ends.
|
||||
package view // import "go.opencensus.io/stats/view"
|
||||
|
||||
// TODO(acetechnologist): Add a link to the language independent OpenCensus
|
||||
// spec when it is available.
|
||||
@ -0,0 +1,58 @@
|
||||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// 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 view |
||||
|
||||
import "sync" |
||||
|
||||
var ( |
||||
exportersMu sync.RWMutex // guards exporters
|
||||
exporters = make(map[Exporter]struct{}) |
||||
) |
||||
|
||||
// Exporter exports the collected records as view data.
|
||||
//
|
||||
// The ExportView method should return quickly; if an
|
||||
// Exporter takes a significant amount of time to
|
||||
// process a Data, that work should be done on another goroutine.
|
||||
//
|
||||
// It is safe to assume that ExportView will not be called concurrently from
|
||||
// multiple goroutines.
|
||||
//
|
||||
// The Data should not be modified.
|
||||
type Exporter interface { |
||||
ExportView(viewData *Data) |
||||
} |
||||
|
||||
// RegisterExporter registers an exporter.
|
||||
// Collected data will be reported via all the
|
||||
// registered exporters. Once you no longer
|
||||
// want data to be exported, invoke UnregisterExporter
|
||||
// with the previously registered exporter.
|
||||
//
|
||||
// Binaries can register exporters, libraries shouldn't register exporters.
|
||||
func RegisterExporter(e Exporter) { |
||||
exportersMu.Lock() |
||||
defer exportersMu.Unlock() |
||||
|
||||
exporters[e] = struct{}{} |
||||
} |
||||
|
||||
// UnregisterExporter unregisters an exporter.
|
||||
func UnregisterExporter(e Exporter) { |
||||
exportersMu.Lock() |
||||
defer exportersMu.Unlock() |
||||
|
||||
delete(exporters, e) |
||||
} |
||||
@ -0,0 +1,221 @@
|
||||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// 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 view |
||||
|
||||
import ( |
||||
"bytes" |
||||
"errors" |
||||
"fmt" |
||||
"reflect" |
||||
"sort" |
||||
"sync/atomic" |
||||
"time" |
||||
|
||||
"go.opencensus.io/metric/metricdata" |
||||
"go.opencensus.io/stats" |
||||
"go.opencensus.io/tag" |
||||
) |
||||
|
||||
// View allows users to aggregate the recorded stats.Measurements.
|
||||
// Views need to be passed to the Register function to be before data will be
|
||||
// collected and sent to Exporters.
|
||||
type View struct { |
||||
Name string // Name of View. Must be unique. If unset, will default to the name of the Measure.
|
||||
Description string // Description is a human-readable description for this view.
|
||||
|
||||
// TagKeys are the tag keys describing the grouping of this view.
|
||||
// A single Row will be produced for each combination of associated tag values.
|
||||
TagKeys []tag.Key |
||||
|
||||
// Measure is a stats.Measure to aggregate in this view.
|
||||
Measure stats.Measure |
||||
|
||||
// Aggregation is the aggregation function tp apply to the set of Measurements.
|
||||
Aggregation *Aggregation |
||||
} |
||||
|
||||
// WithName returns a copy of the View with a new name. This is useful for
|
||||
// renaming views to cope with limitations placed on metric names by various
|
||||
// backends.
|
||||
func (v *View) WithName(name string) *View { |
||||
vNew := *v |
||||
vNew.Name = name |
||||
return &vNew |
||||
} |
||||
|
||||
// same compares two views and returns true if they represent the same aggregation.
|
||||
func (v *View) same(other *View) bool { |
||||
if v == other { |
||||
return true |
||||
} |
||||
if v == nil { |
||||
return false |
||||
} |
||||
return reflect.DeepEqual(v.Aggregation, other.Aggregation) && |
||||
v.Measure.Name() == other.Measure.Name() |
||||
} |
||||
|
||||
// ErrNegativeBucketBounds error returned if histogram contains negative bounds.
|
||||
//
|
||||
// Deprecated: this should not be public.
|
||||
var ErrNegativeBucketBounds = errors.New("negative bucket bounds not supported") |
||||
|
||||
// canonicalize canonicalizes v by setting explicit
|
||||
// defaults for Name and Description and sorting the TagKeys
|
||||
func (v *View) canonicalize() error { |
||||
if v.Measure == nil { |
||||
return fmt.Errorf("cannot register view %q: measure not set", v.Name) |
||||
} |
||||
if v.Aggregation == nil { |
||||
return fmt.Errorf("cannot register view %q: aggregation not set", v.Name) |
||||
} |
||||
if v.Name == "" { |
||||
v.Name = v.Measure.Name() |
||||
} |
||||
if v.Description == "" { |
||||
v.Description = v.Measure.Description() |
||||
} |
||||
if err := checkViewName(v.Name); err != nil { |
||||
return err |
||||
} |
||||
sort.Slice(v.TagKeys, func(i, j int) bool { |
||||
return v.TagKeys[i].Name() < v.TagKeys[j].Name() |
||||
}) |
||||
sort.Float64s(v.Aggregation.Buckets) |
||||
for _, b := range v.Aggregation.Buckets { |
||||
if b < 0 { |
||||
return ErrNegativeBucketBounds |
||||
} |
||||
} |
||||
// drop 0 bucket silently.
|
||||
v.Aggregation.Buckets = dropZeroBounds(v.Aggregation.Buckets...) |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func dropZeroBounds(bounds ...float64) []float64 { |
||||
for i, bound := range bounds { |
||||
if bound > 0 { |
||||
return bounds[i:] |
||||
} |
||||
} |
||||
return []float64{} |
||||
} |
||||
|
||||
// viewInternal is the internal representation of a View.
|
||||
type viewInternal struct { |
||||
view *View // view is the canonicalized View definition associated with this view.
|
||||
subscribed uint32 // 1 if someone is subscribed and data need to be exported, use atomic to access
|
||||
collector *collector |
||||
metricDescriptor *metricdata.Descriptor |
||||
} |
||||
|
||||
func newViewInternal(v *View) (*viewInternal, error) { |
||||
return &viewInternal{ |
||||
view: v, |
||||
collector: &collector{make(map[string]AggregationData), v.Aggregation}, |
||||
metricDescriptor: viewToMetricDescriptor(v), |
||||
}, nil |
||||
} |
||||
|
||||
func (v *viewInternal) subscribe() { |
||||
atomic.StoreUint32(&v.subscribed, 1) |
||||
} |
||||
|
||||
func (v *viewInternal) unsubscribe() { |
||||
atomic.StoreUint32(&v.subscribed, 0) |
||||
} |
||||
|
||||
// isSubscribed returns true if the view is exporting
|
||||
// data by subscription.
|
||||
func (v *viewInternal) isSubscribed() bool { |
||||
return atomic.LoadUint32(&v.subscribed) == 1 |
||||
} |
||||
|
||||
func (v *viewInternal) clearRows() { |
||||
v.collector.clearRows() |
||||
} |
||||
|
||||
func (v *viewInternal) collectedRows() []*Row { |
||||
return v.collector.collectedRows(v.view.TagKeys) |
||||
} |
||||
|
||||
func (v *viewInternal) addSample(m *tag.Map, val float64, attachments map[string]interface{}, t time.Time) { |
||||
if !v.isSubscribed() { |
||||
return |
||||
} |
||||
sig := string(encodeWithKeys(m, v.view.TagKeys)) |
||||
v.collector.addSample(sig, val, attachments, t) |
||||
} |
||||
|
||||
// A Data is a set of rows about usage of the single measure associated
|
||||
// with the given view. Each row is specific to a unique set of tags.
|
||||
type Data struct { |
||||
View *View |
||||
Start, End time.Time |
||||
Rows []*Row |
||||
} |
||||
|
||||
// Row is the collected value for a specific set of key value pairs a.k.a tags.
|
||||
type Row struct { |
||||
Tags []tag.Tag |
||||
Data AggregationData |
||||
} |
||||
|
||||
func (r *Row) String() string { |
||||
var buffer bytes.Buffer |
||||
buffer.WriteString("{ ") |
||||
buffer.WriteString("{ ") |
||||
for _, t := range r.Tags { |
||||
buffer.WriteString(fmt.Sprintf("{%v %v}", t.Key.Name(), t.Value)) |
||||
} |
||||
buffer.WriteString(" }") |
||||
buffer.WriteString(fmt.Sprintf("%v", r.Data)) |
||||
buffer.WriteString(" }") |
||||
return buffer.String() |
||||
} |
||||
|
||||
// Equal returns true if both rows are equal. Tags are expected to be ordered
|
||||
// by the key name. Even both rows have the same tags but the tags appear in
|
||||
// different orders it will return false.
|
||||
func (r *Row) Equal(other *Row) bool { |
||||
if r == other { |
||||
return true |
||||
} |
||||
return reflect.DeepEqual(r.Tags, other.Tags) && r.Data.equal(other.Data) |
||||
} |
||||
|
||||
const maxNameLength = 255 |
||||
|
||||
// Returns true if the given string contains only printable characters.
|
||||
func isPrintable(str string) bool { |
||||
for _, r := range str { |
||||
if !(r >= ' ' && r <= '~') { |
||||
return false |
||||
} |
||||
} |
||||
return true |
||||
} |
||||
|
||||
func checkViewName(name string) error { |
||||
if len(name) > maxNameLength { |
||||
return fmt.Errorf("view name cannot be larger than %v", maxNameLength) |
||||
} |
||||
if !isPrintable(name) { |
||||
return fmt.Errorf("view name needs to be an ASCII string") |
||||
} |
||||
return nil |
||||
} |
||||
@ -0,0 +1,140 @@
|
||||
// Copyright 2019, OpenCensus Authors
|
||||
//
|
||||
// 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 view |
||||
|
||||
import ( |
||||
"time" |
||||
|
||||
"go.opencensus.io/metric/metricdata" |
||||
"go.opencensus.io/stats" |
||||
) |
||||
|
||||
func getUnit(unit string) metricdata.Unit { |
||||
switch unit { |
||||
case "1": |
||||
return metricdata.UnitDimensionless |
||||
case "ms": |
||||
return metricdata.UnitMilliseconds |
||||
case "By": |
||||
return metricdata.UnitBytes |
||||
} |
||||
return metricdata.UnitDimensionless |
||||
} |
||||
|
||||
func getType(v *View) metricdata.Type { |
||||
m := v.Measure |
||||
agg := v.Aggregation |
||||
|
||||
switch agg.Type { |
||||
case AggTypeSum: |
||||
switch m.(type) { |
||||
case *stats.Int64Measure: |
||||
return metricdata.TypeCumulativeInt64 |
||||
case *stats.Float64Measure: |
||||
return metricdata.TypeCumulativeFloat64 |
||||
default: |
||||
panic("unexpected measure type") |
||||
} |
||||
case AggTypeDistribution: |
||||
return metricdata.TypeCumulativeDistribution |
||||
case AggTypeLastValue: |
||||
switch m.(type) { |
||||
case *stats.Int64Measure: |
||||
return metricdata.TypeGaugeInt64 |
||||
case *stats.Float64Measure: |
||||
return metricdata.TypeGaugeFloat64 |
||||
default: |
||||
panic("unexpected measure type") |
||||
} |
||||
case AggTypeCount: |
||||
switch m.(type) { |
||||
case *stats.Int64Measure: |
||||
return metricdata.TypeCumulativeInt64 |
||||
case *stats.Float64Measure: |
||||
return metricdata.TypeCumulativeInt64 |
||||
default: |
||||
panic("unexpected measure type") |
||||
} |
||||
default: |
||||
panic("unexpected aggregation type") |
||||
} |
||||
} |
||||
|
||||
func getLableKeys(v *View) []metricdata.LabelKey { |
||||
labelKeys := []metricdata.LabelKey{} |
||||
for _, k := range v.TagKeys { |
||||
labelKeys = append(labelKeys, metricdata.LabelKey{Key: k.Name()}) |
||||
} |
||||
return labelKeys |
||||
} |
||||
|
||||
func viewToMetricDescriptor(v *View) *metricdata.Descriptor { |
||||
return &metricdata.Descriptor{ |
||||
Name: v.Name, |
||||
Description: v.Description, |
||||
Unit: getUnit(v.Measure.Unit()), |
||||
Type: getType(v), |
||||
LabelKeys: getLableKeys(v), |
||||
} |
||||
} |
||||
|
||||
func toLabelValues(row *Row, expectedKeys []metricdata.LabelKey) []metricdata.LabelValue { |
||||
labelValues := []metricdata.LabelValue{} |
||||
tagMap := make(map[string]string) |
||||
for _, tag := range row.Tags { |
||||
tagMap[tag.Key.Name()] = tag.Value |
||||
} |
||||
|
||||
for _, key := range expectedKeys { |
||||
if val, ok := tagMap[key.Key]; ok { |
||||
labelValues = append(labelValues, metricdata.NewLabelValue(val)) |
||||
} else { |
||||
labelValues = append(labelValues, metricdata.LabelValue{}) |
||||
} |
||||
} |
||||
return labelValues |
||||
} |
||||
|
||||
func rowToTimeseries(v *viewInternal, row *Row, now time.Time, startTime time.Time) *metricdata.TimeSeries { |
||||
return &metricdata.TimeSeries{ |
||||
Points: []metricdata.Point{row.Data.toPoint(v.metricDescriptor.Type, now)}, |
||||
LabelValues: toLabelValues(row, v.metricDescriptor.LabelKeys), |
||||
StartTime: startTime, |
||||
} |
||||
} |
||||
|
||||
func viewToMetric(v *viewInternal, now time.Time, startTime time.Time) *metricdata.Metric { |
||||
if v.metricDescriptor.Type == metricdata.TypeGaugeInt64 || |
||||
v.metricDescriptor.Type == metricdata.TypeGaugeFloat64 { |
||||
startTime = time.Time{} |
||||
} |
||||
|
||||
rows := v.collectedRows() |
||||
if len(rows) == 0 { |
||||
return nil |
||||
} |
||||
|
||||
ts := []*metricdata.TimeSeries{} |
||||
for _, row := range rows { |
||||
ts = append(ts, rowToTimeseries(v, row, now, startTime)) |
||||
} |
||||
|
||||
m := &metricdata.Metric{ |
||||
Descriptor: *v.metricDescriptor, |
||||
TimeSeries: ts, |
||||
} |
||||
return m |
||||
} |
||||
@ -0,0 +1,281 @@
|
||||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// 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 view |
||||
|
||||
import ( |
||||
"fmt" |
||||
"sync" |
||||
"time" |
||||
|
||||
"go.opencensus.io/metric/metricdata" |
||||
"go.opencensus.io/metric/metricproducer" |
||||
"go.opencensus.io/stats" |
||||
"go.opencensus.io/stats/internal" |
||||
"go.opencensus.io/tag" |
||||
) |
||||
|
||||
func init() { |
||||
defaultWorker = newWorker() |
||||
go defaultWorker.start() |
||||
internal.DefaultRecorder = record |
||||
} |
||||
|
||||
type measureRef struct { |
||||
measure string |
||||
views map[*viewInternal]struct{} |
||||
} |
||||
|
||||
type worker struct { |
||||
measures map[string]*measureRef |
||||
views map[string]*viewInternal |
||||
startTimes map[*viewInternal]time.Time |
||||
|
||||
timer *time.Ticker |
||||
c chan command |
||||
quit, done chan bool |
||||
mu sync.RWMutex |
||||
} |
||||
|
||||
var defaultWorker *worker |
||||
|
||||
var defaultReportingDuration = 10 * time.Second |
||||
|
||||
// Find returns a registered view associated with this name.
|
||||
// If no registered view is found, nil is returned.
|
||||
func Find(name string) (v *View) { |
||||
req := &getViewByNameReq{ |
||||
name: name, |
||||
c: make(chan *getViewByNameResp), |
||||
} |
||||
defaultWorker.c <- req |
||||
resp := <-req.c |
||||
return resp.v |
||||
} |
||||
|
||||
// Register begins collecting data for the given views.
|
||||
// Once a view is registered, it reports data to the registered exporters.
|
||||
func Register(views ...*View) error { |
||||
req := ®isterViewReq{ |
||||
views: views, |
||||
err: make(chan error), |
||||
} |
||||
defaultWorker.c <- req |
||||
return <-req.err |
||||
} |
||||
|
||||
// Unregister the given views. Data will not longer be exported for these views
|
||||
// after Unregister returns.
|
||||
// It is not necessary to unregister from views you expect to collect for the
|
||||
// duration of your program execution.
|
||||
func Unregister(views ...*View) { |
||||
names := make([]string, len(views)) |
||||
for i := range views { |
||||
names[i] = views[i].Name |
||||
} |
||||
req := &unregisterFromViewReq{ |
||||
views: names, |
||||
done: make(chan struct{}), |
||||
} |
||||
defaultWorker.c <- req |
||||
<-req.done |
||||
} |
||||
|
||||
// RetrieveData gets a snapshot of the data collected for the the view registered
|
||||
// with the given name. It is intended for testing only.
|
||||
func RetrieveData(viewName string) ([]*Row, error) { |
||||
req := &retrieveDataReq{ |
||||
now: time.Now(), |
||||
v: viewName, |
||||
c: make(chan *retrieveDataResp), |
||||
} |
||||
defaultWorker.c <- req |
||||
resp := <-req.c |
||||
return resp.rows, resp.err |
||||
} |
||||
|
||||
func record(tags *tag.Map, ms interface{}, attachments map[string]interface{}) { |
||||
req := &recordReq{ |
||||
tm: tags, |
||||
ms: ms.([]stats.Measurement), |
||||
attachments: attachments, |
||||
t: time.Now(), |
||||
} |
||||
defaultWorker.c <- req |
||||
} |
||||
|
||||
// SetReportingPeriod sets the interval between reporting aggregated views in
|
||||
// the program. If duration is less than or equal to zero, it enables the
|
||||
// default behavior.
|
||||
//
|
||||
// Note: each exporter makes different promises about what the lowest supported
|
||||
// duration is. For example, the Stackdriver exporter recommends a value no
|
||||
// lower than 1 minute. Consult each exporter per your needs.
|
||||
func SetReportingPeriod(d time.Duration) { |
||||
// TODO(acetechnologist): ensure that the duration d is more than a certain
|
||||
// value. e.g. 1s
|
||||
req := &setReportingPeriodReq{ |
||||
d: d, |
||||
c: make(chan bool), |
||||
} |
||||
defaultWorker.c <- req |
||||
<-req.c // don't return until the timer is set to the new duration.
|
||||
} |
||||
|
||||
func newWorker() *worker { |
||||
return &worker{ |
||||
measures: make(map[string]*measureRef), |
||||
views: make(map[string]*viewInternal), |
||||
startTimes: make(map[*viewInternal]time.Time), |
||||
timer: time.NewTicker(defaultReportingDuration), |
||||
c: make(chan command, 1024), |
||||
quit: make(chan bool), |
||||
done: make(chan bool), |
||||
} |
||||
} |
||||
|
||||
func (w *worker) start() { |
||||
prodMgr := metricproducer.GlobalManager() |
||||
prodMgr.AddProducer(w) |
||||
|
||||
for { |
||||
select { |
||||
case cmd := <-w.c: |
||||
cmd.handleCommand(w) |
||||
case <-w.timer.C: |
||||
w.reportUsage(time.Now()) |
||||
case <-w.quit: |
||||
w.timer.Stop() |
||||
close(w.c) |
||||
w.done <- true |
||||
return |
||||
} |
||||
} |
||||
} |
||||
|
||||
func (w *worker) stop() { |
||||
prodMgr := metricproducer.GlobalManager() |
||||
prodMgr.DeleteProducer(w) |
||||
|
||||
w.quit <- true |
||||
<-w.done |
||||
} |
||||
|
||||
func (w *worker) getMeasureRef(name string) *measureRef { |
||||
if mr, ok := w.measures[name]; ok { |
||||
return mr |
||||
} |
||||
mr := &measureRef{ |
||||
measure: name, |
||||
views: make(map[*viewInternal]struct{}), |
||||
} |
||||
w.measures[name] = mr |
||||
return mr |
||||
} |
||||
|
||||
func (w *worker) tryRegisterView(v *View) (*viewInternal, error) { |
||||
w.mu.Lock() |
||||
defer w.mu.Unlock() |
||||
vi, err := newViewInternal(v) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
if x, ok := w.views[vi.view.Name]; ok { |
||||
if !x.view.same(vi.view) { |
||||
return nil, fmt.Errorf("cannot register view %q; a different view with the same name is already registered", v.Name) |
||||
} |
||||
|
||||
// the view is already registered so there is nothing to do and the
|
||||
// command is considered successful.
|
||||
return x, nil |
||||
} |
||||
w.views[vi.view.Name] = vi |
||||
ref := w.getMeasureRef(vi.view.Measure.Name()) |
||||
ref.views[vi] = struct{}{} |
||||
return vi, nil |
||||
} |
||||
|
||||
func (w *worker) unregisterView(viewName string) { |
||||
w.mu.Lock() |
||||
defer w.mu.Unlock() |
||||
delete(w.views, viewName) |
||||
} |
||||
|
||||
func (w *worker) reportView(v *viewInternal, now time.Time) { |
||||
if !v.isSubscribed() { |
||||
return |
||||
} |
||||
rows := v.collectedRows() |
||||
_, ok := w.startTimes[v] |
||||
if !ok { |
||||
w.startTimes[v] = now |
||||
} |
||||
viewData := &Data{ |
||||
View: v.view, |
||||
Start: w.startTimes[v], |
||||
End: time.Now(), |
||||
Rows: rows, |
||||
} |
||||
exportersMu.Lock() |
||||
for e := range exporters { |
||||
e.ExportView(viewData) |
||||
} |
||||
exportersMu.Unlock() |
||||
} |
||||
|
||||
func (w *worker) reportUsage(now time.Time) { |
||||
w.mu.Lock() |
||||
defer w.mu.Unlock() |
||||
for _, v := range w.views { |
||||
w.reportView(v, now) |
||||
} |
||||
} |
||||
|
||||
func (w *worker) toMetric(v *viewInternal, now time.Time) *metricdata.Metric { |
||||
if !v.isSubscribed() { |
||||
return nil |
||||
} |
||||
|
||||
_, ok := w.startTimes[v] |
||||
if !ok { |
||||
w.startTimes[v] = now |
||||
} |
||||
|
||||
var startTime time.Time |
||||
if v.metricDescriptor.Type == metricdata.TypeGaugeInt64 || |
||||
v.metricDescriptor.Type == metricdata.TypeGaugeFloat64 { |
||||
startTime = time.Time{} |
||||
} else { |
||||
startTime = w.startTimes[v] |
||||
} |
||||
|
||||
return viewToMetric(v, now, startTime) |
||||
} |
||||
|
||||
// Read reads all view data and returns them as metrics.
|
||||
// It is typically invoked by metric reader to export stats in metric format.
|
||||
func (w *worker) Read() []*metricdata.Metric { |
||||
w.mu.Lock() |
||||
defer w.mu.Unlock() |
||||
now := time.Now() |
||||
metrics := make([]*metricdata.Metric, 0, len(w.views)) |
||||
for _, v := range w.views { |
||||
metric := w.toMetric(v, now) |
||||
if metric != nil { |
||||
metrics = append(metrics, metric) |
||||
} |
||||
} |
||||
return metrics |
||||
} |
||||
@ -0,0 +1,186 @@
|
||||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// 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 view |
||||
|
||||
import ( |
||||
"errors" |
||||
"fmt" |
||||
"strings" |
||||
"time" |
||||
|
||||
"go.opencensus.io/stats" |
||||
"go.opencensus.io/stats/internal" |
||||
"go.opencensus.io/tag" |
||||
) |
||||
|
||||
type command interface { |
||||
handleCommand(w *worker) |
||||
} |
||||
|
||||
// getViewByNameReq is the command to get a view given its name.
|
||||
type getViewByNameReq struct { |
||||
name string |
||||
c chan *getViewByNameResp |
||||
} |
||||
|
||||
type getViewByNameResp struct { |
||||
v *View |
||||
} |
||||
|
||||
func (cmd *getViewByNameReq) handleCommand(w *worker) { |
||||
v := w.views[cmd.name] |
||||
if v == nil { |
||||
cmd.c <- &getViewByNameResp{nil} |
||||
return |
||||
} |
||||
cmd.c <- &getViewByNameResp{v.view} |
||||
} |
||||
|
||||
// registerViewReq is the command to register a view.
|
||||
type registerViewReq struct { |
||||
views []*View |
||||
err chan error |
||||
} |
||||
|
||||
func (cmd *registerViewReq) handleCommand(w *worker) { |
||||
for _, v := range cmd.views { |
||||
if err := v.canonicalize(); err != nil { |
||||
cmd.err <- err |
||||
return |
||||
} |
||||
} |
||||
var errstr []string |
||||
for _, view := range cmd.views { |
||||
vi, err := w.tryRegisterView(view) |
||||
if err != nil { |
||||
errstr = append(errstr, fmt.Sprintf("%s: %v", view.Name, err)) |
||||
continue |
||||
} |
||||
internal.SubscriptionReporter(view.Measure.Name()) |
||||
vi.subscribe() |
||||
} |
||||
if len(errstr) > 0 { |
||||
cmd.err <- errors.New(strings.Join(errstr, "\n")) |
||||
} else { |
||||
cmd.err <- nil |
||||
} |
||||
} |
||||
|
||||
// unregisterFromViewReq is the command to unregister to a view. Has no
|
||||
// impact on the data collection for client that are pulling data from the
|
||||
// library.
|
||||
type unregisterFromViewReq struct { |
||||
views []string |
||||
done chan struct{} |
||||
} |
||||
|
||||
func (cmd *unregisterFromViewReq) handleCommand(w *worker) { |
||||
for _, name := range cmd.views { |
||||
vi, ok := w.views[name] |
||||
if !ok { |
||||
continue |
||||
} |
||||
|
||||
// Report pending data for this view before removing it.
|
||||
w.reportView(vi, time.Now()) |
||||
|
||||
vi.unsubscribe() |
||||
if !vi.isSubscribed() { |
||||
// this was the last subscription and view is not collecting anymore.
|
||||
// The collected data can be cleared.
|
||||
vi.clearRows() |
||||
} |
||||
w.unregisterView(name) |
||||
} |
||||
cmd.done <- struct{}{} |
||||
} |
||||
|
||||
// retrieveDataReq is the command to retrieve data for a view.
|
||||
type retrieveDataReq struct { |
||||
now time.Time |
||||
v string |
||||
c chan *retrieveDataResp |
||||
} |
||||
|
||||
type retrieveDataResp struct { |
||||
rows []*Row |
||||
err error |
||||
} |
||||
|
||||
func (cmd *retrieveDataReq) handleCommand(w *worker) { |
||||
w.mu.Lock() |
||||
defer w.mu.Unlock() |
||||
vi, ok := w.views[cmd.v] |
||||
if !ok { |
||||
cmd.c <- &retrieveDataResp{ |
||||
nil, |
||||
fmt.Errorf("cannot retrieve data; view %q is not registered", cmd.v), |
||||
} |
||||
return |
||||
} |
||||
|
||||
if !vi.isSubscribed() { |
||||
cmd.c <- &retrieveDataResp{ |
||||
nil, |
||||
fmt.Errorf("cannot retrieve data; view %q has no subscriptions or collection is not forcibly started", cmd.v), |
||||
} |
||||
return |
||||
} |
||||
cmd.c <- &retrieveDataResp{ |
||||
vi.collectedRows(), |
||||
nil, |
||||
} |
||||
} |
||||
|
||||
// recordReq is the command to record data related to multiple measures
|
||||
// at once.
|
||||
type recordReq struct { |
||||
tm *tag.Map |
||||
ms []stats.Measurement |
||||
attachments map[string]interface{} |
||||
t time.Time |
||||
} |
||||
|
||||
func (cmd *recordReq) handleCommand(w *worker) { |
||||
w.mu.Lock() |
||||
defer w.mu.Unlock() |
||||
for _, m := range cmd.ms { |
||||
if (m == stats.Measurement{}) { // not registered
|
||||
continue |
||||
} |
||||
ref := w.getMeasureRef(m.Measure().Name()) |
||||
for v := range ref.views { |
||||
v.addSample(cmd.tm, m.Value(), cmd.attachments, time.Now()) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// setReportingPeriodReq is the command to modify the duration between
|
||||
// reporting the collected data to the registered clients.
|
||||
type setReportingPeriodReq struct { |
||||
d time.Duration |
||||
c chan bool |
||||
} |
||||
|
||||
func (cmd *setReportingPeriodReq) handleCommand(w *worker) { |
||||
w.timer.Stop() |
||||
if cmd.d <= 0 { |
||||
w.timer = time.NewTicker(defaultReportingDuration) |
||||
} else { |
||||
w.timer = time.NewTicker(cmd.d) |
||||
} |
||||
cmd.c <- true |
||||
} |
||||
@ -0,0 +1,43 @@
|
||||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// 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 tag |
||||
|
||||
import ( |
||||
"context" |
||||
) |
||||
|
||||
// FromContext returns the tag map stored in the context.
|
||||
func FromContext(ctx context.Context) *Map { |
||||
// The returned tag map shouldn't be mutated.
|
||||
ts := ctx.Value(mapCtxKey) |
||||
if ts == nil { |
||||
return nil |
||||
} |
||||
return ts.(*Map) |
||||
} |
||||
|
||||
// NewContext creates a new context with the given tag map.
|
||||
// To propagate a tag map to downstream methods and downstream RPCs, add a tag map
|
||||
// to the current context. NewContext will return a copy of the current context,
|
||||
// and put the tag map into the returned one.
|
||||
// If there is already a tag map in the current context, it will be replaced with m.
|
||||
func NewContext(ctx context.Context, m *Map) context.Context { |
||||
return context.WithValue(ctx, mapCtxKey, m) |
||||
} |
||||
|
||||
type ctxKey struct{} |
||||
|
||||
var mapCtxKey = ctxKey{} |
||||
@ -0,0 +1,26 @@
|
||||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// 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 tag contains OpenCensus tags. |
||||
|
||||
Tags are key-value pairs. Tags provide additional cardinality to |
||||
the OpenCensus instrumentation data. |
||||
|
||||
Tags can be propagated on the wire and in the same |
||||
process via context.Context. Encode and Decode should be |
||||
used to represent tags into their binary propagation form. |
||||
*/ |
||||
package tag // import "go.opencensus.io/tag"
|
||||
@ -0,0 +1,35 @@
|
||||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// 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 tag |
||||
|
||||
// Key represents a tag key.
|
||||
type Key struct { |
||||
name string |
||||
} |
||||
|
||||
// NewKey creates or retrieves a string key identified by name.
|
||||
// Calling NewKey consequently with the same name returns the same key.
|
||||
func NewKey(name string) (Key, error) { |
||||
if !checkKeyName(name) { |
||||
return Key{}, errInvalidKeyName |
||||
} |
||||
return Key{name: name}, nil |
||||
} |
||||
|
||||
// Name returns the name of the key.
|
||||
func (k Key) Name() string { |
||||
return k.name |
||||
} |
||||
@ -0,0 +1,229 @@
|
||||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// 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 tag |
||||
|
||||
import ( |
||||
"bytes" |
||||
"context" |
||||
"fmt" |
||||
"sort" |
||||
) |
||||
|
||||
// Tag is a key value pair that can be propagated on wire.
|
||||
type Tag struct { |
||||
Key Key |
||||
Value string |
||||
} |
||||
|
||||
type tagContent struct { |
||||
value string |
||||
m metadatas |
||||
} |
||||
|
||||
// Map is a map of tags. Use New to create a context containing
|
||||
// a new Map.
|
||||
type Map struct { |
||||
m map[Key]tagContent |
||||
} |
||||
|
||||
// Value returns the value for the key if a value for the key exists.
|
||||
func (m *Map) Value(k Key) (string, bool) { |
||||
if m == nil { |
||||
return "", false |
||||
} |
||||
v, ok := m.m[k] |
||||
return v.value, ok |
||||
} |
||||
|
||||
func (m *Map) String() string { |
||||
if m == nil { |
||||
return "nil" |
||||
} |
||||
keys := make([]Key, 0, len(m.m)) |
||||
for k := range m.m { |
||||
keys = append(keys, k) |
||||
} |
||||
sort.Slice(keys, func(i, j int) bool { return keys[i].Name() < keys[j].Name() }) |
||||
|
||||
var buffer bytes.Buffer |
||||
buffer.WriteString("{ ") |
||||
for _, k := range keys { |
||||
buffer.WriteString(fmt.Sprintf("{%v %v}", k.name, m.m[k])) |
||||
} |
||||
buffer.WriteString(" }") |
||||
return buffer.String() |
||||
} |
||||
|
||||
func (m *Map) insert(k Key, v string, md metadatas) { |
||||
if _, ok := m.m[k]; ok { |
||||
return |
||||
} |
||||
m.m[k] = tagContent{value: v, m: md} |
||||
} |
||||
|
||||
func (m *Map) update(k Key, v string, md metadatas) { |
||||
if _, ok := m.m[k]; ok { |
||||
m.m[k] = tagContent{value: v, m: md} |
||||
} |
||||
} |
||||
|
||||
func (m *Map) upsert(k Key, v string, md metadatas) { |
||||
m.m[k] = tagContent{value: v, m: md} |
||||
} |
||||
|
||||
func (m *Map) delete(k Key) { |
||||
delete(m.m, k) |
||||
} |
||||
|
||||
func newMap() *Map { |
||||
return &Map{m: make(map[Key]tagContent)} |
||||
} |
||||
|
||||
// Mutator modifies a tag map.
|
||||
type Mutator interface { |
||||
Mutate(t *Map) (*Map, error) |
||||
} |
||||
|
||||
// Insert returns a mutator that inserts a
|
||||
// value associated with k. If k already exists in the tag map,
|
||||
// mutator doesn't update the value.
|
||||
// Metadata applies metadata to the tag. It is optional.
|
||||
// Metadatas are applied in the order in which it is provided.
|
||||
// If more than one metadata updates the same attribute then
|
||||
// the update from the last metadata prevails.
|
||||
func Insert(k Key, v string, mds ...Metadata) Mutator { |
||||
return &mutator{ |
||||
fn: func(m *Map) (*Map, error) { |
||||
if !checkValue(v) { |
||||
return nil, errInvalidValue |
||||
} |
||||
m.insert(k, v, createMetadatas(mds...)) |
||||
return m, nil |
||||
}, |
||||
} |
||||
} |
||||
|
||||
// Update returns a mutator that updates the
|
||||
// value of the tag associated with k with v. If k doesn't
|
||||
// exists in the tag map, the mutator doesn't insert the value.
|
||||
// Metadata applies metadata to the tag. It is optional.
|
||||
// Metadatas are applied in the order in which it is provided.
|
||||
// If more than one metadata updates the same attribute then
|
||||
// the update from the last metadata prevails.
|
||||
func Update(k Key, v string, mds ...Metadata) Mutator { |
||||
return &mutator{ |
||||
fn: func(m *Map) (*Map, error) { |
||||
if !checkValue(v) { |
||||
return nil, errInvalidValue |
||||
} |
||||
m.update(k, v, createMetadatas(mds...)) |
||||
return m, nil |
||||
}, |
||||
} |
||||
} |
||||
|
||||
// Upsert returns a mutator that upserts the
|
||||
// value of the tag associated with k with v. It inserts the
|
||||
// value if k doesn't exist already. It mutates the value
|
||||
// if k already exists.
|
||||
// Metadata applies metadata to the tag. It is optional.
|
||||
// Metadatas are applied in the order in which it is provided.
|
||||
// If more than one metadata updates the same attribute then
|
||||
// the update from the last metadata prevails.
|
||||
func Upsert(k Key, v string, mds ...Metadata) Mutator { |
||||
return &mutator{ |
||||
fn: func(m *Map) (*Map, error) { |
||||
if !checkValue(v) { |
||||
return nil, errInvalidValue |
||||
} |
||||
m.upsert(k, v, createMetadatas(mds...)) |
||||
return m, nil |
||||
}, |
||||
} |
||||
} |
||||
|
||||
func createMetadatas(mds ...Metadata) metadatas { |
||||
var metas metadatas |
||||
if len(mds) > 0 { |
||||
for _, md := range mds { |
||||
if md != nil { |
||||
md(&metas) |
||||
} |
||||
} |
||||
} else { |
||||
WithTTL(TTLUnlimitedPropagation)(&metas) |
||||
} |
||||
return metas |
||||
|
||||
} |
||||
|
||||
// Delete returns a mutator that deletes
|
||||
// the value associated with k.
|
||||
func Delete(k Key) Mutator { |
||||
return &mutator{ |
||||
fn: func(m *Map) (*Map, error) { |
||||
m.delete(k) |
||||
return m, nil |
||||
}, |
||||
} |
||||
} |
||||
|
||||
// New returns a new context that contains a tag map
|
||||
// originated from the incoming context and modified
|
||||
// with the provided mutators.
|
||||
func New(ctx context.Context, mutator ...Mutator) (context.Context, error) { |
||||
m := newMap() |
||||
orig := FromContext(ctx) |
||||
if orig != nil { |
||||
for k, v := range orig.m { |
||||
if !checkKeyName(k.Name()) { |
||||
return ctx, fmt.Errorf("key:%q: %v", k, errInvalidKeyName) |
||||
} |
||||
if !checkValue(v.value) { |
||||
return ctx, fmt.Errorf("key:%q value:%q: %v", k.Name(), v, errInvalidValue) |
||||
} |
||||
m.insert(k, v.value, v.m) |
||||
} |
||||
} |
||||
var err error |
||||
for _, mod := range mutator { |
||||
m, err = mod.Mutate(m) |
||||
if err != nil { |
||||
return ctx, err |
||||
} |
||||
} |
||||
return NewContext(ctx, m), nil |
||||
} |
||||
|
||||
// Do is similar to pprof.Do: a convenience for installing the tags
|
||||
// from the context as Go profiler labels. This allows you to
|
||||
// correlated runtime profiling with stats.
|
||||
//
|
||||
// It converts the key/values from the given map to Go profiler labels
|
||||
// and calls pprof.Do.
|
||||
//
|
||||
// Do is going to do nothing if your Go version is below 1.9.
|
||||
func Do(ctx context.Context, f func(ctx context.Context)) { |
||||
do(ctx, f) |
||||
} |
||||
|
||||
type mutator struct { |
||||
fn func(t *Map) (*Map, error) |
||||
} |
||||
|
||||
func (m *mutator) Mutate(t *Map) (*Map, error) { |
||||
return m.fn(t) |
||||
} |
||||
@ -0,0 +1,239 @@
|
||||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// 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 tag |
||||
|
||||
import ( |
||||
"encoding/binary" |
||||
"fmt" |
||||
) |
||||
|
||||
// KeyType defines the types of keys allowed. Currently only keyTypeString is
|
||||
// supported.
|
||||
type keyType byte |
||||
|
||||
const ( |
||||
keyTypeString keyType = iota |
||||
keyTypeInt64 |
||||
keyTypeTrue |
||||
keyTypeFalse |
||||
|
||||
tagsVersionID = byte(0) |
||||
) |
||||
|
||||
type encoderGRPC struct { |
||||
buf []byte |
||||
writeIdx, readIdx int |
||||
} |
||||
|
||||
// writeKeyString writes the fieldID '0' followed by the key string and value
|
||||
// string.
|
||||
func (eg *encoderGRPC) writeTagString(k, v string) { |
||||
eg.writeByte(byte(keyTypeString)) |
||||
eg.writeStringWithVarintLen(k) |
||||
eg.writeStringWithVarintLen(v) |
||||
} |
||||
|
||||
func (eg *encoderGRPC) writeTagUint64(k string, i uint64) { |
||||
eg.writeByte(byte(keyTypeInt64)) |
||||
eg.writeStringWithVarintLen(k) |
||||
eg.writeUint64(i) |
||||
} |
||||
|
||||
func (eg *encoderGRPC) writeTagTrue(k string) { |
||||
eg.writeByte(byte(keyTypeTrue)) |
||||
eg.writeStringWithVarintLen(k) |
||||
} |
||||
|
||||
func (eg *encoderGRPC) writeTagFalse(k string) { |
||||
eg.writeByte(byte(keyTypeFalse)) |
||||
eg.writeStringWithVarintLen(k) |
||||
} |
||||
|
||||
func (eg *encoderGRPC) writeBytesWithVarintLen(bytes []byte) { |
||||
length := len(bytes) |
||||
|
||||
eg.growIfRequired(binary.MaxVarintLen64 + length) |
||||
eg.writeIdx += binary.PutUvarint(eg.buf[eg.writeIdx:], uint64(length)) |
||||
copy(eg.buf[eg.writeIdx:], bytes) |
||||
eg.writeIdx += length |
||||
} |
||||
|
||||
func (eg *encoderGRPC) writeStringWithVarintLen(s string) { |
||||
length := len(s) |
||||
|
||||
eg.growIfRequired(binary.MaxVarintLen64 + length) |
||||
eg.writeIdx += binary.PutUvarint(eg.buf[eg.writeIdx:], uint64(length)) |
||||
copy(eg.buf[eg.writeIdx:], s) |
||||
eg.writeIdx += length |
||||
} |
||||
|
||||
func (eg *encoderGRPC) writeByte(v byte) { |
||||
eg.growIfRequired(1) |
||||
eg.buf[eg.writeIdx] = v |
||||
eg.writeIdx++ |
||||
} |
||||
|
||||
func (eg *encoderGRPC) writeUint32(i uint32) { |
||||
eg.growIfRequired(4) |
||||
binary.LittleEndian.PutUint32(eg.buf[eg.writeIdx:], i) |
||||
eg.writeIdx += 4 |
||||
} |
||||
|
||||
func (eg *encoderGRPC) writeUint64(i uint64) { |
||||
eg.growIfRequired(8) |
||||
binary.LittleEndian.PutUint64(eg.buf[eg.writeIdx:], i) |
||||
eg.writeIdx += 8 |
||||
} |
||||
|
||||
func (eg *encoderGRPC) readByte() byte { |
||||
b := eg.buf[eg.readIdx] |
||||
eg.readIdx++ |
||||
return b |
||||
} |
||||
|
||||
func (eg *encoderGRPC) readUint32() uint32 { |
||||
i := binary.LittleEndian.Uint32(eg.buf[eg.readIdx:]) |
||||
eg.readIdx += 4 |
||||
return i |
||||
} |
||||
|
||||
func (eg *encoderGRPC) readUint64() uint64 { |
||||
i := binary.LittleEndian.Uint64(eg.buf[eg.readIdx:]) |
||||
eg.readIdx += 8 |
||||
return i |
||||
} |
||||
|
||||
func (eg *encoderGRPC) readBytesWithVarintLen() ([]byte, error) { |
||||
if eg.readEnded() { |
||||
return nil, fmt.Errorf("unexpected end while readBytesWithVarintLen '%x' starting at idx '%v'", eg.buf, eg.readIdx) |
||||
} |
||||
length, valueStart := binary.Uvarint(eg.buf[eg.readIdx:]) |
||||
if valueStart <= 0 { |
||||
return nil, fmt.Errorf("unexpected end while readBytesWithVarintLen '%x' starting at idx '%v'", eg.buf, eg.readIdx) |
||||
} |
||||
|
||||
valueStart += eg.readIdx |
||||
valueEnd := valueStart + int(length) |
||||
if valueEnd > len(eg.buf) { |
||||
return nil, fmt.Errorf("malformed encoding: length:%v, upper:%v, maxLength:%v", length, valueEnd, len(eg.buf)) |
||||
} |
||||
|
||||
eg.readIdx = valueEnd |
||||
return eg.buf[valueStart:valueEnd], nil |
||||
} |
||||
|
||||
func (eg *encoderGRPC) readStringWithVarintLen() (string, error) { |
||||
bytes, err := eg.readBytesWithVarintLen() |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
return string(bytes), nil |
||||
} |
||||
|
||||
func (eg *encoderGRPC) growIfRequired(expected int) { |
||||
if len(eg.buf)-eg.writeIdx < expected { |
||||
tmp := make([]byte, 2*(len(eg.buf)+1)+expected) |
||||
copy(tmp, eg.buf) |
||||
eg.buf = tmp |
||||
} |
||||
} |
||||
|
||||
func (eg *encoderGRPC) readEnded() bool { |
||||
return eg.readIdx >= len(eg.buf) |
||||
} |
||||
|
||||
func (eg *encoderGRPC) bytes() []byte { |
||||
return eg.buf[:eg.writeIdx] |
||||
} |
||||
|
||||
// Encode encodes the tag map into a []byte. It is useful to propagate
|
||||
// the tag maps on wire in binary format.
|
||||
func Encode(m *Map) []byte { |
||||
if m == nil { |
||||
return nil |
||||
} |
||||
eg := &encoderGRPC{ |
||||
buf: make([]byte, len(m.m)), |
||||
} |
||||
eg.writeByte(byte(tagsVersionID)) |
||||
for k, v := range m.m { |
||||
if v.m.ttl.ttl == valueTTLUnlimitedPropagation { |
||||
eg.writeByte(byte(keyTypeString)) |
||||
eg.writeStringWithVarintLen(k.name) |
||||
eg.writeBytesWithVarintLen([]byte(v.value)) |
||||
} |
||||
} |
||||
return eg.bytes() |
||||
} |
||||
|
||||
// Decode decodes the given []byte into a tag map.
|
||||
func Decode(bytes []byte) (*Map, error) { |
||||
ts := newMap() |
||||
err := DecodeEach(bytes, ts.upsert) |
||||
if err != nil { |
||||
// no partial failures
|
||||
return nil, err |
||||
} |
||||
return ts, nil |
||||
} |
||||
|
||||
// DecodeEach decodes the given serialized tag map, calling handler for each
|
||||
// tag key and value decoded.
|
||||
func DecodeEach(bytes []byte, fn func(key Key, val string, md metadatas)) error { |
||||
eg := &encoderGRPC{ |
||||
buf: bytes, |
||||
} |
||||
if len(eg.buf) == 0 { |
||||
return nil |
||||
} |
||||
|
||||
version := eg.readByte() |
||||
if version > tagsVersionID { |
||||
return fmt.Errorf("cannot decode: unsupported version: %q; supports only up to: %q", version, tagsVersionID) |
||||
} |
||||
|
||||
for !eg.readEnded() { |
||||
typ := keyType(eg.readByte()) |
||||
|
||||
if typ != keyTypeString { |
||||
return fmt.Errorf("cannot decode: invalid key type: %q", typ) |
||||
} |
||||
|
||||
k, err := eg.readBytesWithVarintLen() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
v, err := eg.readBytesWithVarintLen() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
key, err := NewKey(string(k)) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
val := string(v) |
||||
if !checkValue(val) { |
||||
return errInvalidValue |
||||
} |
||||
fn(key, val, createMetadatas(WithTTL(TTLUnlimitedPropagation))) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
@ -0,0 +1,52 @@
|
||||
// Copyright 2019, OpenCensus Authors
|
||||
//
|
||||
// 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 tag |
||||
|
||||
const ( |
||||
// valueTTLNoPropagation prevents tag from propagating.
|
||||
valueTTLNoPropagation = 0 |
||||
|
||||
// valueTTLUnlimitedPropagation allows tag to propagate without any limits on number of hops.
|
||||
valueTTLUnlimitedPropagation = -1 |
||||
) |
||||
|
||||
// TTL is metadata that specifies number of hops a tag can propagate.
|
||||
// Details about TTL metadata is specified at https://github.com/census-instrumentation/opencensus-specs/blob/master/tags/TagMap.md#tagmetadata
|
||||
type TTL struct { |
||||
ttl int |
||||
} |
||||
|
||||
var ( |
||||
// TTLUnlimitedPropagation is TTL metadata that allows tag to propagate without any limits on number of hops.
|
||||
TTLUnlimitedPropagation = TTL{ttl: valueTTLUnlimitedPropagation} |
||||
|
||||
// TTLNoPropagation is TTL metadata that prevents tag from propagating.
|
||||
TTLNoPropagation = TTL{ttl: valueTTLNoPropagation} |
||||
) |
||||
|
||||
type metadatas struct { |
||||
ttl TTL |
||||
} |
||||
|
||||
// Metadata applies metadatas specified by the function.
|
||||
type Metadata func(*metadatas) |
||||
|
||||
// WithTTL applies metadata with provided ttl.
|
||||
func WithTTL(ttl TTL) Metadata { |
||||
return func(m *metadatas) { |
||||
m.ttl = ttl |
||||
} |
||||
} |
||||
@ -0,0 +1,31 @@
|
||||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// 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.9
|
||||
|
||||
package tag |
||||
|
||||
import ( |
||||
"context" |
||||
"runtime/pprof" |
||||
) |
||||
|
||||
func do(ctx context.Context, f func(ctx context.Context)) { |
||||
m := FromContext(ctx) |
||||
keyvals := make([]string, 0, 2*len(m.m)) |
||||
for k, v := range m.m { |
||||
keyvals = append(keyvals, k.Name(), v.value) |
||||
} |
||||
pprof.Do(ctx, pprof.Labels(keyvals...), f) |
||||
} |
||||
@ -0,0 +1,23 @@
|
||||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// 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.9
|
||||
|
||||
package tag |
||||
|
||||
import "context" |
||||
|
||||
func do(ctx context.Context, f func(ctx context.Context)) { |
||||
f(ctx) |
||||
} |
||||
@ -0,0 +1,56 @@
|
||||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// 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 tag |
||||
|
||||
import "errors" |
||||
|
||||
const ( |
||||
maxKeyLength = 255 |
||||
|
||||
// valid are restricted to US-ASCII subset (range 0x20 (' ') to 0x7e ('~')).
|
||||
validKeyValueMin = 32 |
||||
validKeyValueMax = 126 |
||||
) |
||||
|
||||
var ( |
||||
errInvalidKeyName = errors.New("invalid key name: only ASCII characters accepted; max length must be 255 characters") |
||||
errInvalidValue = errors.New("invalid value: only ASCII characters accepted; max length must be 255 characters") |
||||
) |
||||
|
||||
func checkKeyName(name string) bool { |
||||
if len(name) == 0 { |
||||
return false |
||||
} |
||||
if len(name) > maxKeyLength { |
||||
return false |
||||
} |
||||
return isASCII(name) |
||||
} |
||||
|
||||
func isASCII(s string) bool { |
||||
for _, c := range s { |
||||
if (c < validKeyValueMin) || (c > validKeyValueMax) { |
||||
return false |
||||
} |
||||
} |
||||
return true |
||||
} |
||||
|
||||
func checkValue(v string) bool { |
||||
if len(v) > maxKeyLength { |
||||
return false |
||||
} |
||||
return isASCII(v) |
||||
} |
||||
@ -0,0 +1,119 @@
|
||||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// 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 trace |
||||
|
||||
import ( |
||||
"fmt" |
||||
"time" |
||||
) |
||||
|
||||
type ( |
||||
// TraceID is a 16-byte identifier for a set of spans.
|
||||
TraceID [16]byte |
||||
|
||||
// SpanID is an 8-byte identifier for a single span.
|
||||
SpanID [8]byte |
||||
) |
||||
|
||||
func (t TraceID) String() string { |
||||
return fmt.Sprintf("%02x", t[:]) |
||||
} |
||||
|
||||
func (s SpanID) String() string { |
||||
return fmt.Sprintf("%02x", s[:]) |
||||
} |
||||
|
||||
// Annotation represents a text annotation with a set of attributes and a timestamp.
|
||||
type Annotation struct { |
||||
Time time.Time |
||||
Message string |
||||
Attributes map[string]interface{} |
||||
} |
||||
|
||||
// Attribute represents a key-value pair on a span, link or annotation.
|
||||
// Construct with one of: BoolAttribute, Int64Attribute, or StringAttribute.
|
||||
type Attribute struct { |
||||
key string |
||||
value interface{} |
||||
} |
||||
|
||||
// BoolAttribute returns a bool-valued attribute.
|
||||
func BoolAttribute(key string, value bool) Attribute { |
||||
return Attribute{key: key, value: value} |
||||
} |
||||
|
||||
// Int64Attribute returns an int64-valued attribute.
|
||||
func Int64Attribute(key string, value int64) Attribute { |
||||
return Attribute{key: key, value: value} |
||||
} |
||||
|
||||
// Float64Attribute returns a float64-valued attribute.
|
||||
func Float64Attribute(key string, value float64) Attribute { |
||||
return Attribute{key: key, value: value} |
||||
} |
||||
|
||||
// StringAttribute returns a string-valued attribute.
|
||||
func StringAttribute(key string, value string) Attribute { |
||||
return Attribute{key: key, value: value} |
||||
} |
||||
|
||||
// LinkType specifies the relationship between the span that had the link
|
||||
// added, and the linked span.
|
||||
type LinkType int32 |
||||
|
||||
// LinkType values.
|
||||
const ( |
||||
LinkTypeUnspecified LinkType = iota // The relationship of the two spans is unknown.
|
||||
LinkTypeChild // The linked span is a child of the current span.
|
||||
LinkTypeParent // The linked span is the parent of the current span.
|
||||
) |
||||
|
||||
// Link represents a reference from one span to another span.
|
||||
type Link struct { |
||||
TraceID TraceID |
||||
SpanID SpanID |
||||
Type LinkType |
||||
// Attributes is a set of attributes on the link.
|
||||
Attributes map[string]interface{} |
||||
} |
||||
|
||||
// MessageEventType specifies the type of message event.
|
||||
type MessageEventType int32 |
||||
|
||||
// MessageEventType values.
|
||||
const ( |
||||
MessageEventTypeUnspecified MessageEventType = iota // Unknown event type.
|
||||
MessageEventTypeSent // Indicates a sent RPC message.
|
||||
MessageEventTypeRecv // Indicates a received RPC message.
|
||||
) |
||||
|
||||
// MessageEvent represents an event describing a message sent or received on the network.
|
||||
type MessageEvent struct { |
||||
Time time.Time |
||||
EventType MessageEventType |
||||
MessageID int64 |
||||
UncompressedByteSize int64 |
||||
CompressedByteSize int64 |
||||
} |
||||
|
||||
// Status is the status of a Span.
|
||||
type Status struct { |
||||
// Code is a status code. Zero indicates success.
|
||||
//
|
||||
// If Code will be propagated to Google APIs, it ideally should be a value from
|
||||
// https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto .
|
||||
Code int32 |
||||
Message string |
||||
} |
||||
@ -0,0 +1,86 @@
|
||||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// 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 trace |
||||
|
||||
import ( |
||||
"sync" |
||||
|
||||
"go.opencensus.io/trace/internal" |
||||
) |
||||
|
||||
// Config represents the global tracing configuration.
|
||||
type Config struct { |
||||
// DefaultSampler is the default sampler used when creating new spans.
|
||||
DefaultSampler Sampler |
||||
|
||||
// IDGenerator is for internal use only.
|
||||
IDGenerator internal.IDGenerator |
||||
|
||||
// MaxAnnotationEventsPerSpan is max number of annotation events per span
|
||||
MaxAnnotationEventsPerSpan int |
||||
|
||||
// MaxMessageEventsPerSpan is max number of message events per span
|
||||
MaxMessageEventsPerSpan int |
||||
|
||||
// MaxAnnotationEventsPerSpan is max number of attributes per span
|
||||
MaxAttributesPerSpan int |
||||
|
||||
// MaxLinksPerSpan is max number of links per span
|
||||
MaxLinksPerSpan int |
||||
} |
||||
|
||||
var configWriteMu sync.Mutex |
||||
|
||||
const ( |
||||
// DefaultMaxAnnotationEventsPerSpan is default max number of annotation events per span
|
||||
DefaultMaxAnnotationEventsPerSpan = 32 |
||||
|
||||
// DefaultMaxMessageEventsPerSpan is default max number of message events per span
|
||||
DefaultMaxMessageEventsPerSpan = 128 |
||||
|
||||
// DefaultMaxAttributesPerSpan is default max number of attributes per span
|
||||
DefaultMaxAttributesPerSpan = 32 |
||||
|
||||
// DefaultMaxLinksPerSpan is default max number of links per span
|
||||
DefaultMaxLinksPerSpan = 32 |
||||
) |
||||
|
||||
// ApplyConfig applies changes to the global tracing configuration.
|
||||
//
|
||||
// Fields not provided in the given config are going to be preserved.
|
||||
func ApplyConfig(cfg Config) { |
||||
configWriteMu.Lock() |
||||
defer configWriteMu.Unlock() |
||||
c := *config.Load().(*Config) |
||||
if cfg.DefaultSampler != nil { |
||||
c.DefaultSampler = cfg.DefaultSampler |
||||
} |
||||
if cfg.IDGenerator != nil { |
||||
c.IDGenerator = cfg.IDGenerator |
||||
} |
||||
if cfg.MaxAnnotationEventsPerSpan > 0 { |
||||
c.MaxAnnotationEventsPerSpan = cfg.MaxAnnotationEventsPerSpan |
||||
} |
||||
if cfg.MaxMessageEventsPerSpan > 0 { |
||||
c.MaxMessageEventsPerSpan = cfg.MaxMessageEventsPerSpan |
||||
} |
||||
if cfg.MaxAttributesPerSpan > 0 { |
||||
c.MaxAttributesPerSpan = cfg.MaxAttributesPerSpan |
||||
} |
||||
if cfg.MaxLinksPerSpan > 0 { |
||||
c.MaxLinksPerSpan = cfg.MaxLinksPerSpan |
||||
} |
||||
config.Store(&c) |
||||
} |
||||
@ -0,0 +1,53 @@
|
||||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// 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 trace contains support for OpenCensus distributed tracing. |
||||
|
||||
The following assumes a basic familiarity with OpenCensus concepts. |
||||
See http://opencensus.io
|
||||
|
||||
|
||||
Exporting Traces |
||||
|
||||
To export collected tracing data, register at least one exporter. You can use |
||||
one of the provided exporters or write your own. |
||||
|
||||
trace.RegisterExporter(exporter) |
||||
|
||||
By default, traces will be sampled relatively rarely. To change the sampling |
||||
frequency for your entire program, call ApplyConfig. Use a ProbabilitySampler |
||||
to sample a subset of traces, or use AlwaysSample to collect a trace on every run: |
||||
|
||||
trace.ApplyConfig(trace.Config{DefaultSampler: trace.AlwaysSample()}) |
||||
|
||||
Be careful about using trace.AlwaysSample in a production application with |
||||
significant traffic: a new trace will be started and exported for every request. |
||||
|
||||
Adding Spans to a Trace |
||||
|
||||
A trace consists of a tree of spans. In Go, the current span is carried in a |
||||
context.Context. |
||||
|
||||
It is common to want to capture all the activity of a function call in a span. For |
||||
this to work, the function must take a context.Context as a parameter. Add these two |
||||
lines to the top of the function: |
||||
|
||||
ctx, span := trace.StartSpan(ctx, "example.com/Run") |
||||
defer span.End() |
||||
|
||||
StartSpan will create a new top-level span if the context |
||||
doesn't contain another span, otherwise it will create a child span. |
||||
*/ |
||||
package trace // import "go.opencensus.io/trace"
|
||||
@ -0,0 +1,38 @@
|
||||
// Copyright 2019, OpenCensus Authors
|
||||
//
|
||||
// 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 trace |
||||
|
||||
type evictedQueue struct { |
||||
queue []interface{} |
||||
capacity int |
||||
droppedCount int |
||||
} |
||||
|
||||
func newEvictedQueue(capacity int) *evictedQueue { |
||||
eq := &evictedQueue{ |
||||
capacity: capacity, |
||||
queue: make([]interface{}, 0), |
||||
} |
||||
|
||||
return eq |
||||
} |
||||
|
||||
func (eq *evictedQueue) add(value interface{}) { |
||||
if len(eq.queue) == eq.capacity { |
||||
eq.queue = eq.queue[1:] |
||||
eq.droppedCount++ |
||||
} |
||||
eq.queue = append(eq.queue, value) |
||||
} |
||||
@ -0,0 +1,97 @@
|
||||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// 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 trace |
||||
|
||||
import ( |
||||
"sync" |
||||
"sync/atomic" |
||||
"time" |
||||
) |
||||
|
||||
// Exporter is a type for functions that receive sampled trace spans.
|
||||
//
|
||||
// The ExportSpan method should be safe for concurrent use and should return
|
||||
// quickly; if an Exporter takes a significant amount of time to process a
|
||||
// SpanData, that work should be done on another goroutine.
|
||||
//
|
||||
// The SpanData should not be modified, but a pointer to it can be kept.
|
||||
type Exporter interface { |
||||
ExportSpan(s *SpanData) |
||||
} |
||||
|
||||
type exportersMap map[Exporter]struct{} |
||||
|
||||
var ( |
||||
exporterMu sync.Mutex |
||||
exporters atomic.Value |
||||
) |
||||
|
||||
// RegisterExporter adds to the list of Exporters that will receive sampled
|
||||
// trace spans.
|
||||
//
|
||||
// Binaries can register exporters, libraries shouldn't register exporters.
|
||||
func RegisterExporter(e Exporter) { |
||||
exporterMu.Lock() |
||||
new := make(exportersMap) |
||||
if old, ok := exporters.Load().(exportersMap); ok { |
||||
for k, v := range old { |
||||
new[k] = v |
||||
} |
||||
} |
||||
new[e] = struct{}{} |
||||
exporters.Store(new) |
||||
exporterMu.Unlock() |
||||
} |
||||
|
||||
// UnregisterExporter removes from the list of Exporters the Exporter that was
|
||||
// registered with the given name.
|
||||
func UnregisterExporter(e Exporter) { |
||||
exporterMu.Lock() |
||||
new := make(exportersMap) |
||||
if old, ok := exporters.Load().(exportersMap); ok { |
||||
for k, v := range old { |
||||
new[k] = v |
||||
} |
||||
} |
||||
delete(new, e) |
||||
exporters.Store(new) |
||||
exporterMu.Unlock() |
||||
} |
||||
|
||||
// SpanData contains all the information collected by a Span.
|
||||
type SpanData struct { |
||||
SpanContext |
||||
ParentSpanID SpanID |
||||
SpanKind int |
||||
Name string |
||||
StartTime time.Time |
||||
// The wall clock time of EndTime will be adjusted to always be offset
|
||||
// from StartTime by the duration of the span.
|
||||
EndTime time.Time |
||||
// The values of Attributes each have type string, bool, or int64.
|
||||
Attributes map[string]interface{} |
||||
Annotations []Annotation |
||||
MessageEvents []MessageEvent |
||||
Status |
||||
Links []Link |
||||
HasRemoteParent bool |
||||
DroppedAttributeCount int |
||||
DroppedAnnotationCount int |
||||
DroppedMessageEventCount int |
||||
DroppedLinkCount int |
||||
|
||||
// ChildSpanCount holds the number of child span created for this span.
|
||||
ChildSpanCount int |
||||
} |
||||
@ -0,0 +1,22 @@
|
||||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// 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 internal provides trace internals.
|
||||
package internal |
||||
|
||||
// IDGenerator allows custom generators for TraceId and SpanId.
|
||||
type IDGenerator interface { |
||||
NewTraceID() [16]byte |
||||
NewSpanID() [8]byte |
||||
} |
||||
@ -0,0 +1,37 @@
|
||||
// Copyright 2019, OpenCensus Authors
|
||||
//
|
||||
// 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 trace |
||||
|
||||
import ( |
||||
"github.com/hashicorp/golang-lru/simplelru" |
||||
) |
||||
|
||||
type lruMap struct { |
||||
simpleLruMap *simplelru.LRU |
||||
droppedCount int |
||||
} |
||||
|
||||
func newLruMap(size int) *lruMap { |
||||
lm := &lruMap{} |
||||
lm.simpleLruMap, _ = simplelru.NewLRU(size, nil) |
||||
return lm |
||||
} |
||||
|
||||
func (lm *lruMap) add(key, value interface{}) { |
||||
evicted := lm.simpleLruMap.Add(key, value) |
||||
if evicted { |
||||
lm.droppedCount++ |
||||
} |
||||
} |
||||
@ -0,0 +1,108 @@
|
||||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// 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 propagation implements the binary trace context format.
|
||||
package propagation // import "go.opencensus.io/trace/propagation"
|
||||
|
||||
// TODO: link to external spec document.
|
||||
|
||||
// BinaryFormat format:
|
||||
//
|
||||
// Binary value: <version_id><version_format>
|
||||
// version_id: 1 byte representing the version id.
|
||||
//
|
||||
// For version_id = 0:
|
||||
//
|
||||
// version_format: <field><field>
|
||||
// field_format: <field_id><field_format>
|
||||
//
|
||||
// Fields:
|
||||
//
|
||||
// TraceId: (field_id = 0, len = 16, default = "0000000000000000") - 16-byte array representing the trace_id.
|
||||
// SpanId: (field_id = 1, len = 8, default = "00000000") - 8-byte array representing the span_id.
|
||||
// TraceOptions: (field_id = 2, len = 1, default = "0") - 1-byte array representing the trace_options.
|
||||
//
|
||||
// Fields MUST be encoded using the field id order (smaller to higher).
|
||||
//
|
||||
// Valid value example:
|
||||
//
|
||||
// {0, 0, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 1, 97,
|
||||
// 98, 99, 100, 101, 102, 103, 104, 2, 1}
|
||||
//
|
||||
// version_id = 0;
|
||||
// trace_id = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}
|
||||
// span_id = {97, 98, 99, 100, 101, 102, 103, 104};
|
||||
// trace_options = {1};
|
||||
|
||||
import ( |
||||
"net/http" |
||||
|
||||
"go.opencensus.io/trace" |
||||
) |
||||
|
||||
// Binary returns the binary format representation of a SpanContext.
|
||||
//
|
||||
// If sc is the zero value, Binary returns nil.
|
||||
func Binary(sc trace.SpanContext) []byte { |
||||
if sc == (trace.SpanContext{}) { |
||||
return nil |
||||
} |
||||
var b [29]byte |
||||
copy(b[2:18], sc.TraceID[:]) |
||||
b[18] = 1 |
||||
copy(b[19:27], sc.SpanID[:]) |
||||
b[27] = 2 |
||||
b[28] = uint8(sc.TraceOptions) |
||||
return b[:] |
||||
} |
||||
|
||||
// FromBinary returns the SpanContext represented by b.
|
||||
//
|
||||
// If b has an unsupported version ID or contains no TraceID, FromBinary
|
||||
// returns with ok==false.
|
||||
func FromBinary(b []byte) (sc trace.SpanContext, ok bool) { |
||||
if len(b) == 0 || b[0] != 0 { |
||||
return trace.SpanContext{}, false |
||||
} |
||||
b = b[1:] |
||||
if len(b) >= 17 && b[0] == 0 { |
||||
copy(sc.TraceID[:], b[1:17]) |
||||
b = b[17:] |
||||
} else { |
||||
return trace.SpanContext{}, false |
||||
} |
||||
if len(b) >= 9 && b[0] == 1 { |
||||
copy(sc.SpanID[:], b[1:9]) |
||||
b = b[9:] |
||||
} |
||||
if len(b) >= 2 && b[0] == 2 { |
||||
sc.TraceOptions = trace.TraceOptions(b[1]) |
||||
} |
||||
return sc, true |
||||
} |
||||
|
||||
// HTTPFormat implementations propagate span contexts
|
||||
// in HTTP requests.
|
||||
//
|
||||
// SpanContextFromRequest extracts a span context from incoming
|
||||
// requests.
|
||||
//
|
||||
// SpanContextToRequest modifies the given request to include the given
|
||||
// span context.
|
||||
type HTTPFormat interface { |
||||
SpanContextFromRequest(req *http.Request) (sc trace.SpanContext, ok bool) |
||||
SpanContextToRequest(sc trace.SpanContext, req *http.Request) |
||||
} |
||||
|
||||
// TODO(jbd): Find a more representative but short name for HTTPFormat.
|
||||
@ -0,0 +1,75 @@
|
||||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// 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 trace |
||||
|
||||
import ( |
||||
"encoding/binary" |
||||
) |
||||
|
||||
const defaultSamplingProbability = 1e-4 |
||||
|
||||
// Sampler decides whether a trace should be sampled and exported.
|
||||
type Sampler func(SamplingParameters) SamplingDecision |
||||
|
||||
// SamplingParameters contains the values passed to a Sampler.
|
||||
type SamplingParameters struct { |
||||
ParentContext SpanContext |
||||
TraceID TraceID |
||||
SpanID SpanID |
||||
Name string |
||||
HasRemoteParent bool |
||||
} |
||||
|
||||
// SamplingDecision is the value returned by a Sampler.
|
||||
type SamplingDecision struct { |
||||
Sample bool |
||||
} |
||||
|
||||
// ProbabilitySampler returns a Sampler that samples a given fraction of traces.
|
||||
//
|
||||
// It also samples spans whose parents are sampled.
|
||||
func ProbabilitySampler(fraction float64) Sampler { |
||||
if !(fraction >= 0) { |
||||
fraction = 0 |
||||
} else if fraction >= 1 { |
||||
return AlwaysSample() |
||||
} |
||||
|
||||
traceIDUpperBound := uint64(fraction * (1 << 63)) |
||||
return Sampler(func(p SamplingParameters) SamplingDecision { |
||||
if p.ParentContext.IsSampled() { |
||||
return SamplingDecision{Sample: true} |
||||
} |
||||
x := binary.BigEndian.Uint64(p.TraceID[0:8]) >> 1 |
||||
return SamplingDecision{Sample: x < traceIDUpperBound} |
||||
}) |
||||
} |
||||
|
||||
// AlwaysSample returns a Sampler that samples every trace.
|
||||
// Be careful about using this sampler in a production application with
|
||||
// significant traffic: a new trace will be started and exported for every
|
||||
// request.
|
||||
func AlwaysSample() Sampler { |
||||
return func(p SamplingParameters) SamplingDecision { |
||||
return SamplingDecision{Sample: true} |
||||
} |
||||
} |
||||
|
||||
// NeverSample returns a Sampler that samples no traces.
|
||||
func NeverSample() Sampler { |
||||
return func(p SamplingParameters) SamplingDecision { |
||||
return SamplingDecision{Sample: false} |
||||
} |
||||
} |
||||
@ -0,0 +1,130 @@
|
||||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// 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 trace |
||||
|
||||
import ( |
||||
"time" |
||||
) |
||||
|
||||
// samplePeriod is the minimum time between accepting spans in a single bucket.
|
||||
const samplePeriod = time.Second |
||||
|
||||
// defaultLatencies contains the default latency bucket bounds.
|
||||
// TODO: consider defaults, make configurable
|
||||
var defaultLatencies = [...]time.Duration{ |
||||
10 * time.Microsecond, |
||||
100 * time.Microsecond, |
||||
time.Millisecond, |
||||
10 * time.Millisecond, |
||||
100 * time.Millisecond, |
||||
time.Second, |
||||
10 * time.Second, |
||||
time.Minute, |
||||
} |
||||
|
||||
// bucket is a container for a set of spans for a particular error code or latency range.
|
||||
type bucket struct { |
||||
nextTime time.Time // next time we can accept a span
|
||||
buffer []*SpanData // circular buffer of spans
|
||||
nextIndex int // location next SpanData should be placed in buffer
|
||||
overflow bool // whether the circular buffer has wrapped around
|
||||
} |
||||
|
||||
func makeBucket(bufferSize int) bucket { |
||||
return bucket{ |
||||
buffer: make([]*SpanData, bufferSize), |
||||
} |
||||
} |
||||
|
||||
// add adds a span to the bucket, if nextTime has been reached.
|
||||
func (b *bucket) add(s *SpanData) { |
||||
if s.EndTime.Before(b.nextTime) { |
||||
return |
||||
} |
||||
if len(b.buffer) == 0 { |
||||
return |
||||
} |
||||
b.nextTime = s.EndTime.Add(samplePeriod) |
||||
b.buffer[b.nextIndex] = s |
||||
b.nextIndex++ |
||||
if b.nextIndex == len(b.buffer) { |
||||
b.nextIndex = 0 |
||||
b.overflow = true |
||||
} |
||||
} |
||||
|
||||
// size returns the number of spans in the bucket.
|
||||
func (b *bucket) size() int { |
||||
if b.overflow { |
||||
return len(b.buffer) |
||||
} |
||||
return b.nextIndex |
||||
} |
||||
|
||||
// span returns the ith span in the bucket.
|
||||
func (b *bucket) span(i int) *SpanData { |
||||
if !b.overflow { |
||||
return b.buffer[i] |
||||
} |
||||
if i < len(b.buffer)-b.nextIndex { |
||||
return b.buffer[b.nextIndex+i] |
||||
} |
||||
return b.buffer[b.nextIndex+i-len(b.buffer)] |
||||
} |
||||
|
||||
// resize changes the size of the bucket to n, keeping up to n existing spans.
|
||||
func (b *bucket) resize(n int) { |
||||
cur := b.size() |
||||
newBuffer := make([]*SpanData, n) |
||||
if cur < n { |
||||
for i := 0; i < cur; i++ { |
||||
newBuffer[i] = b.span(i) |
||||
} |
||||
b.buffer = newBuffer |
||||
b.nextIndex = cur |
||||
b.overflow = false |
||||
return |
||||
} |
||||
for i := 0; i < n; i++ { |
||||
newBuffer[i] = b.span(i + cur - n) |
||||
} |
||||
b.buffer = newBuffer |
||||
b.nextIndex = 0 |
||||
b.overflow = true |
||||
} |
||||
|
||||
// latencyBucket returns the appropriate bucket number for a given latency.
|
||||
func latencyBucket(latency time.Duration) int { |
||||
i := 0 |
||||
for i < len(defaultLatencies) && latency >= defaultLatencies[i] { |
||||
i++ |
||||
} |
||||
return i |
||||
} |
||||
|
||||
// latencyBucketBounds returns the lower and upper bounds for a latency bucket
|
||||
// number.
|
||||
//
|
||||
// The lower bound is inclusive, the upper bound is exclusive (except for the
|
||||
// last bucket.)
|
||||
func latencyBucketBounds(index int) (lower time.Duration, upper time.Duration) { |
||||
if index == 0 { |
||||
return 0, defaultLatencies[index] |
||||
} |
||||
if index == len(defaultLatencies) { |
||||
return defaultLatencies[index-1], 1<<63 - 1 |
||||
} |
||||
return defaultLatencies[index-1], defaultLatencies[index] |
||||
} |
||||
@ -0,0 +1,306 @@
|
||||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// 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 trace |
||||
|
||||
import ( |
||||
"sync" |
||||
"time" |
||||
|
||||
"go.opencensus.io/internal" |
||||
) |
||||
|
||||
const ( |
||||
maxBucketSize = 100000 |
||||
defaultBucketSize = 10 |
||||
) |
||||
|
||||
var ( |
||||
ssmu sync.RWMutex // protects spanStores
|
||||
spanStores = make(map[string]*spanStore) |
||||
) |
||||
|
||||
// This exists purely to avoid exposing internal methods used by z-Pages externally.
|
||||
type internalOnly struct{} |
||||
|
||||
func init() { |
||||
//TODO(#412): remove
|
||||
internal.Trace = &internalOnly{} |
||||
} |
||||
|
||||
// ReportActiveSpans returns the active spans for the given name.
|
||||
func (i internalOnly) ReportActiveSpans(name string) []*SpanData { |
||||
s := spanStoreForName(name) |
||||
if s == nil { |
||||
return nil |
||||
} |
||||
var out []*SpanData |
||||
s.mu.Lock() |
||||
defer s.mu.Unlock() |
||||
for span := range s.active { |
||||
out = append(out, span.makeSpanData()) |
||||
} |
||||
return out |
||||
} |
||||
|
||||
// ReportSpansByError returns a sample of error spans.
|
||||
//
|
||||
// If code is nonzero, only spans with that status code are returned.
|
||||
func (i internalOnly) ReportSpansByError(name string, code int32) []*SpanData { |
||||
s := spanStoreForName(name) |
||||
if s == nil { |
||||
return nil |
||||
} |
||||
var out []*SpanData |
||||
s.mu.Lock() |
||||
defer s.mu.Unlock() |
||||
if code != 0 { |
||||
if b, ok := s.errors[code]; ok { |
||||
for _, sd := range b.buffer { |
||||
if sd == nil { |
||||
break |
||||
} |
||||
out = append(out, sd) |
||||
} |
||||
} |
||||
} else { |
||||
for _, b := range s.errors { |
||||
for _, sd := range b.buffer { |
||||
if sd == nil { |
||||
break |
||||
} |
||||
out = append(out, sd) |
||||
} |
||||
} |
||||
} |
||||
return out |
||||
} |
||||
|
||||
// ConfigureBucketSizes sets the number of spans to keep per latency and error
|
||||
// bucket for different span names.
|
||||
func (i internalOnly) ConfigureBucketSizes(bcs []internal.BucketConfiguration) { |
||||
for _, bc := range bcs { |
||||
latencyBucketSize := bc.MaxRequestsSucceeded |
||||
if latencyBucketSize < 0 { |
||||
latencyBucketSize = 0 |
||||
} |
||||
if latencyBucketSize > maxBucketSize { |
||||
latencyBucketSize = maxBucketSize |
||||
} |
||||
errorBucketSize := bc.MaxRequestsErrors |
||||
if errorBucketSize < 0 { |
||||
errorBucketSize = 0 |
||||
} |
||||
if errorBucketSize > maxBucketSize { |
||||
errorBucketSize = maxBucketSize |
||||
} |
||||
spanStoreSetSize(bc.Name, latencyBucketSize, errorBucketSize) |
||||
} |
||||
} |
||||
|
||||
// ReportSpansPerMethod returns a summary of what spans are being stored for each span name.
|
||||
func (i internalOnly) ReportSpansPerMethod() map[string]internal.PerMethodSummary { |
||||
out := make(map[string]internal.PerMethodSummary) |
||||
ssmu.RLock() |
||||
defer ssmu.RUnlock() |
||||
for name, s := range spanStores { |
||||
s.mu.Lock() |
||||
p := internal.PerMethodSummary{ |
||||
Active: len(s.active), |
||||
} |
||||
for code, b := range s.errors { |
||||
p.ErrorBuckets = append(p.ErrorBuckets, internal.ErrorBucketSummary{ |
||||
ErrorCode: code, |
||||
Size: b.size(), |
||||
}) |
||||
} |
||||
for i, b := range s.latency { |
||||
min, max := latencyBucketBounds(i) |
||||
p.LatencyBuckets = append(p.LatencyBuckets, internal.LatencyBucketSummary{ |
||||
MinLatency: min, |
||||
MaxLatency: max, |
||||
Size: b.size(), |
||||
}) |
||||
} |
||||
s.mu.Unlock() |
||||
out[name] = p |
||||
} |
||||
return out |
||||
} |
||||
|
||||
// ReportSpansByLatency returns a sample of successful spans.
|
||||
//
|
||||
// minLatency is the minimum latency of spans to be returned.
|
||||
// maxLatency, if nonzero, is the maximum latency of spans to be returned.
|
||||
func (i internalOnly) ReportSpansByLatency(name string, minLatency, maxLatency time.Duration) []*SpanData { |
||||
s := spanStoreForName(name) |
||||
if s == nil { |
||||
return nil |
||||
} |
||||
var out []*SpanData |
||||
s.mu.Lock() |
||||
defer s.mu.Unlock() |
||||
for i, b := range s.latency { |
||||
min, max := latencyBucketBounds(i) |
||||
if i+1 != len(s.latency) && max <= minLatency { |
||||
continue |
||||
} |
||||
if maxLatency != 0 && maxLatency < min { |
||||
continue |
||||
} |
||||
for _, sd := range b.buffer { |
||||
if sd == nil { |
||||
break |
||||
} |
||||
if minLatency != 0 || maxLatency != 0 { |
||||
d := sd.EndTime.Sub(sd.StartTime) |
||||
if d < minLatency { |
||||
continue |
||||
} |
||||
if maxLatency != 0 && d > maxLatency { |
||||
continue |
||||
} |
||||
} |
||||
out = append(out, sd) |
||||
} |
||||
} |
||||
return out |
||||
} |
||||
|
||||
// spanStore keeps track of spans stored for a particular span name.
|
||||
//
|
||||
// It contains all active spans; a sample of spans for failed requests,
|
||||
// categorized by error code; and a sample of spans for successful requests,
|
||||
// bucketed by latency.
|
||||
type spanStore struct { |
||||
mu sync.Mutex // protects everything below.
|
||||
active map[*Span]struct{} |
||||
errors map[int32]*bucket |
||||
latency []bucket |
||||
maxSpansPerErrorBucket int |
||||
} |
||||
|
||||
// newSpanStore creates a span store.
|
||||
func newSpanStore(name string, latencyBucketSize int, errorBucketSize int) *spanStore { |
||||
s := &spanStore{ |
||||
active: make(map[*Span]struct{}), |
||||
latency: make([]bucket, len(defaultLatencies)+1), |
||||
maxSpansPerErrorBucket: errorBucketSize, |
||||
} |
||||
for i := range s.latency { |
||||
s.latency[i] = makeBucket(latencyBucketSize) |
||||
} |
||||
return s |
||||
} |
||||
|
||||
// spanStoreForName returns the spanStore for the given name.
|
||||
//
|
||||
// It returns nil if it doesn't exist.
|
||||
func spanStoreForName(name string) *spanStore { |
||||
var s *spanStore |
||||
ssmu.RLock() |
||||
s, _ = spanStores[name] |
||||
ssmu.RUnlock() |
||||
return s |
||||
} |
||||
|
||||
// spanStoreForNameCreateIfNew returns the spanStore for the given name.
|
||||
//
|
||||
// It creates it if it didn't exist.
|
||||
func spanStoreForNameCreateIfNew(name string) *spanStore { |
||||
ssmu.RLock() |
||||
s, ok := spanStores[name] |
||||
ssmu.RUnlock() |
||||
if ok { |
||||
return s |
||||
} |
||||
ssmu.Lock() |
||||
defer ssmu.Unlock() |
||||
s, ok = spanStores[name] |
||||
if ok { |
||||
return s |
||||
} |
||||
s = newSpanStore(name, defaultBucketSize, defaultBucketSize) |
||||
spanStores[name] = s |
||||
return s |
||||
} |
||||
|
||||
// spanStoreSetSize resizes the spanStore for the given name.
|
||||
//
|
||||
// It creates it if it didn't exist.
|
||||
func spanStoreSetSize(name string, latencyBucketSize int, errorBucketSize int) { |
||||
ssmu.RLock() |
||||
s, ok := spanStores[name] |
||||
ssmu.RUnlock() |
||||
if ok { |
||||
s.resize(latencyBucketSize, errorBucketSize) |
||||
return |
||||
} |
||||
ssmu.Lock() |
||||
defer ssmu.Unlock() |
||||
s, ok = spanStores[name] |
||||
if ok { |
||||
s.resize(latencyBucketSize, errorBucketSize) |
||||
return |
||||
} |
||||
s = newSpanStore(name, latencyBucketSize, errorBucketSize) |
||||
spanStores[name] = s |
||||
} |
||||
|
||||
func (s *spanStore) resize(latencyBucketSize int, errorBucketSize int) { |
||||
s.mu.Lock() |
||||
for i := range s.latency { |
||||
s.latency[i].resize(latencyBucketSize) |
||||
} |
||||
for _, b := range s.errors { |
||||
b.resize(errorBucketSize) |
||||
} |
||||
s.maxSpansPerErrorBucket = errorBucketSize |
||||
s.mu.Unlock() |
||||
} |
||||
|
||||
// add adds a span to the active bucket of the spanStore.
|
||||
func (s *spanStore) add(span *Span) { |
||||
s.mu.Lock() |
||||
s.active[span] = struct{}{} |
||||
s.mu.Unlock() |
||||
} |
||||
|
||||
// finished removes a span from the active set, and adds a corresponding
|
||||
// SpanData to a latency or error bucket.
|
||||
func (s *spanStore) finished(span *Span, sd *SpanData) { |
||||
latency := sd.EndTime.Sub(sd.StartTime) |
||||
if latency < 0 { |
||||
latency = 0 |
||||
} |
||||
code := sd.Status.Code |
||||
|
||||
s.mu.Lock() |
||||
delete(s.active, span) |
||||
if code == 0 { |
||||
s.latency[latencyBucket(latency)].add(sd) |
||||
} else { |
||||
if s.errors == nil { |
||||
s.errors = make(map[int32]*bucket) |
||||
} |
||||
if b := s.errors[code]; b != nil { |
||||
b.add(sd) |
||||
} else { |
||||
b := makeBucket(s.maxSpansPerErrorBucket) |
||||
s.errors[code] = &b |
||||
b.add(sd) |
||||
} |
||||
} |
||||
s.mu.Unlock() |
||||
} |
||||
@ -0,0 +1,37 @@
|
||||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// 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 trace |
||||
|
||||
// Status codes for use with Span.SetStatus. These correspond to the status
|
||||
// codes used by gRPC defined here: https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto
|
||||
const ( |
||||
StatusCodeOK = 0 |
||||
StatusCodeCancelled = 1 |
||||
StatusCodeUnknown = 2 |
||||
StatusCodeInvalidArgument = 3 |
||||
StatusCodeDeadlineExceeded = 4 |
||||
StatusCodeNotFound = 5 |
||||
StatusCodeAlreadyExists = 6 |
||||
StatusCodePermissionDenied = 7 |
||||
StatusCodeResourceExhausted = 8 |
||||
StatusCodeFailedPrecondition = 9 |
||||
StatusCodeAborted = 10 |
||||
StatusCodeOutOfRange = 11 |
||||
StatusCodeUnimplemented = 12 |
||||
StatusCodeInternal = 13 |
||||
StatusCodeUnavailable = 14 |
||||
StatusCodeDataLoss = 15 |
||||
StatusCodeUnauthenticated = 16 |
||||
) |
||||
@ -0,0 +1,598 @@
|
||||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// 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 trace |
||||
|
||||
import ( |
||||
"context" |
||||
crand "crypto/rand" |
||||
"encoding/binary" |
||||
"fmt" |
||||
"math/rand" |
||||
"sync" |
||||
"sync/atomic" |
||||
"time" |
||||
|
||||
"go.opencensus.io/internal" |
||||
"go.opencensus.io/trace/tracestate" |
||||
) |
||||
|
||||
// Span represents a span of a trace. It has an associated SpanContext, and
|
||||
// stores data accumulated while the span is active.
|
||||
//
|
||||
// Ideally users should interact with Spans by calling the functions in this
|
||||
// package that take a Context parameter.
|
||||
type Span struct { |
||||
// data contains information recorded about the span.
|
||||
//
|
||||
// It will be non-nil if we are exporting the span or recording events for it.
|
||||
// Otherwise, data is nil, and the Span is simply a carrier for the
|
||||
// SpanContext, so that the trace ID is propagated.
|
||||
data *SpanData |
||||
mu sync.Mutex // protects the contents of *data (but not the pointer value.)
|
||||
spanContext SpanContext |
||||
|
||||
// lruAttributes are capped at configured limit. When the capacity is reached an oldest entry
|
||||
// is removed to create room for a new entry.
|
||||
lruAttributes *lruMap |
||||
|
||||
// annotations are stored in FIFO queue capped by configured limit.
|
||||
annotations *evictedQueue |
||||
|
||||
// messageEvents are stored in FIFO queue capped by configured limit.
|
||||
messageEvents *evictedQueue |
||||
|
||||
// links are stored in FIFO queue capped by configured limit.
|
||||
links *evictedQueue |
||||
|
||||
// spanStore is the spanStore this span belongs to, if any, otherwise it is nil.
|
||||
*spanStore |
||||
endOnce sync.Once |
||||
|
||||
executionTracerTaskEnd func() // ends the execution tracer span
|
||||
} |
||||
|
||||
// IsRecordingEvents returns true if events are being recorded for this span.
|
||||
// Use this check to avoid computing expensive annotations when they will never
|
||||
// be used.
|
||||
func (s *Span) IsRecordingEvents() bool { |
||||
if s == nil { |
||||
return false |
||||
} |
||||
return s.data != nil |
||||
} |
||||
|
||||
// TraceOptions contains options associated with a trace span.
|
||||
type TraceOptions uint32 |
||||
|
||||
// IsSampled returns true if the span will be exported.
|
||||
func (sc SpanContext) IsSampled() bool { |
||||
return sc.TraceOptions.IsSampled() |
||||
} |
||||
|
||||
// setIsSampled sets the TraceOptions bit that determines whether the span will be exported.
|
||||
func (sc *SpanContext) setIsSampled(sampled bool) { |
||||
if sampled { |
||||
sc.TraceOptions |= 1 |
||||
} else { |
||||
sc.TraceOptions &= ^TraceOptions(1) |
||||
} |
||||
} |
||||
|
||||
// IsSampled returns true if the span will be exported.
|
||||
func (t TraceOptions) IsSampled() bool { |
||||
return t&1 == 1 |
||||
} |
||||
|
||||
// SpanContext contains the state that must propagate across process boundaries.
|
||||
//
|
||||
// SpanContext is not an implementation of context.Context.
|
||||
// TODO: add reference to external Census docs for SpanContext.
|
||||
type SpanContext struct { |
||||
TraceID TraceID |
||||
SpanID SpanID |
||||
TraceOptions TraceOptions |
||||
Tracestate *tracestate.Tracestate |
||||
} |
||||
|
||||
type contextKey struct{} |
||||
|
||||
// FromContext returns the Span stored in a context, or nil if there isn't one.
|
||||
func FromContext(ctx context.Context) *Span { |
||||
s, _ := ctx.Value(contextKey{}).(*Span) |
||||
return s |
||||
} |
||||
|
||||
// NewContext returns a new context with the given Span attached.
|
||||
func NewContext(parent context.Context, s *Span) context.Context { |
||||
return context.WithValue(parent, contextKey{}, s) |
||||
} |
||||
|
||||
// All available span kinds. Span kind must be either one of these values.
|
||||
const ( |
||||
SpanKindUnspecified = iota |
||||
SpanKindServer |
||||
SpanKindClient |
||||
) |
||||
|
||||
// StartOptions contains options concerning how a span is started.
|
||||
type StartOptions struct { |
||||
// Sampler to consult for this Span. If provided, it is always consulted.
|
||||
//
|
||||
// If not provided, then the behavior differs based on whether
|
||||
// the parent of this Span is remote, local, or there is no parent.
|
||||
// In the case of a remote parent or no parent, the
|
||||
// default sampler (see Config) will be consulted. Otherwise,
|
||||
// when there is a non-remote parent, no new sampling decision will be made:
|
||||
// we will preserve the sampling of the parent.
|
||||
Sampler Sampler |
||||
|
||||
// SpanKind represents the kind of a span. If none is set,
|
||||
// SpanKindUnspecified is used.
|
||||
SpanKind int |
||||
} |
||||
|
||||
// StartOption apply changes to StartOptions.
|
||||
type StartOption func(*StartOptions) |
||||
|
||||
// WithSpanKind makes new spans to be created with the given kind.
|
||||
func WithSpanKind(spanKind int) StartOption { |
||||
return func(o *StartOptions) { |
||||
o.SpanKind = spanKind |
||||
} |
||||
} |
||||
|
||||
// WithSampler makes new spans to be be created with a custom sampler.
|
||||
// Otherwise, the global sampler is used.
|
||||
func WithSampler(sampler Sampler) StartOption { |
||||
return func(o *StartOptions) { |
||||
o.Sampler = sampler |
||||
} |
||||
} |
||||
|
||||
// StartSpan starts a new child span of the current span in the context. If
|
||||
// there is no span in the context, creates a new trace and span.
|
||||
//
|
||||
// Returned context contains the newly created span. You can use it to
|
||||
// propagate the returned span in process.
|
||||
func StartSpan(ctx context.Context, name string, o ...StartOption) (context.Context, *Span) { |
||||
var opts StartOptions |
||||
var parent SpanContext |
||||
if p := FromContext(ctx); p != nil { |
||||
p.addChild() |
||||
parent = p.spanContext |
||||
} |
||||
for _, op := range o { |
||||
op(&opts) |
||||
} |
||||
span := startSpanInternal(name, parent != SpanContext{}, parent, false, opts) |
||||
|
||||
ctx, end := startExecutionTracerTask(ctx, name) |
||||
span.executionTracerTaskEnd = end |
||||
return NewContext(ctx, span), span |
||||
} |
||||
|
||||
// StartSpanWithRemoteParent starts a new child span of the span from the given parent.
|
||||
//
|
||||
// If the incoming context contains a parent, it ignores. StartSpanWithRemoteParent is
|
||||
// preferred for cases where the parent is propagated via an incoming request.
|
||||
//
|
||||
// Returned context contains the newly created span. You can use it to
|
||||
// propagate the returned span in process.
|
||||
func StartSpanWithRemoteParent(ctx context.Context, name string, parent SpanContext, o ...StartOption) (context.Context, *Span) { |
||||
var opts StartOptions |
||||
for _, op := range o { |
||||
op(&opts) |
||||
} |
||||
span := startSpanInternal(name, parent != SpanContext{}, parent, true, opts) |
||||
ctx, end := startExecutionTracerTask(ctx, name) |
||||
span.executionTracerTaskEnd = end |
||||
return NewContext(ctx, span), span |
||||
} |
||||
|
||||
func startSpanInternal(name string, hasParent bool, parent SpanContext, remoteParent bool, o StartOptions) *Span { |
||||
span := &Span{} |
||||
span.spanContext = parent |
||||
|
||||
cfg := config.Load().(*Config) |
||||
|
||||
if !hasParent { |
||||
span.spanContext.TraceID = cfg.IDGenerator.NewTraceID() |
||||
} |
||||
span.spanContext.SpanID = cfg.IDGenerator.NewSpanID() |
||||
sampler := cfg.DefaultSampler |
||||
|
||||
if !hasParent || remoteParent || o.Sampler != nil { |
||||
// If this span is the child of a local span and no Sampler is set in the
|
||||
// options, keep the parent's TraceOptions.
|
||||
//
|
||||
// Otherwise, consult the Sampler in the options if it is non-nil, otherwise
|
||||
// the default sampler.
|
||||
if o.Sampler != nil { |
||||
sampler = o.Sampler |
||||
} |
||||
span.spanContext.setIsSampled(sampler(SamplingParameters{ |
||||
ParentContext: parent, |
||||
TraceID: span.spanContext.TraceID, |
||||
SpanID: span.spanContext.SpanID, |
||||
Name: name, |
||||
HasRemoteParent: remoteParent}).Sample) |
||||
} |
||||
|
||||
if !internal.LocalSpanStoreEnabled && !span.spanContext.IsSampled() { |
||||
return span |
||||
} |
||||
|
||||
span.data = &SpanData{ |
||||
SpanContext: span.spanContext, |
||||
StartTime: time.Now(), |
||||
SpanKind: o.SpanKind, |
||||
Name: name, |
||||
HasRemoteParent: remoteParent, |
||||
} |
||||
span.lruAttributes = newLruMap(cfg.MaxAttributesPerSpan) |
||||
span.annotations = newEvictedQueue(cfg.MaxAnnotationEventsPerSpan) |
||||
span.messageEvents = newEvictedQueue(cfg.MaxMessageEventsPerSpan) |
||||
span.links = newEvictedQueue(cfg.MaxLinksPerSpan) |
||||
|
||||
if hasParent { |
||||
span.data.ParentSpanID = parent.SpanID |
||||
} |
||||
if internal.LocalSpanStoreEnabled { |
||||
var ss *spanStore |
||||
ss = spanStoreForNameCreateIfNew(name) |
||||
if ss != nil { |
||||
span.spanStore = ss |
||||
ss.add(span) |
||||
} |
||||
} |
||||
|
||||
return span |
||||
} |
||||
|
||||
// End ends the span.
|
||||
func (s *Span) End() { |
||||
if s == nil { |
||||
return |
||||
} |
||||
if s.executionTracerTaskEnd != nil { |
||||
s.executionTracerTaskEnd() |
||||
} |
||||
if !s.IsRecordingEvents() { |
||||
return |
||||
} |
||||
s.endOnce.Do(func() { |
||||
exp, _ := exporters.Load().(exportersMap) |
||||
mustExport := s.spanContext.IsSampled() && len(exp) > 0 |
||||
if s.spanStore != nil || mustExport { |
||||
sd := s.makeSpanData() |
||||
sd.EndTime = internal.MonotonicEndTime(sd.StartTime) |
||||
if s.spanStore != nil { |
||||
s.spanStore.finished(s, sd) |
||||
} |
||||
if mustExport { |
||||
for e := range exp { |
||||
e.ExportSpan(sd) |
||||
} |
||||
} |
||||
} |
||||
}) |
||||
} |
||||
|
||||
// makeSpanData produces a SpanData representing the current state of the Span.
|
||||
// It requires that s.data is non-nil.
|
||||
func (s *Span) makeSpanData() *SpanData { |
||||
var sd SpanData |
||||
s.mu.Lock() |
||||
sd = *s.data |
||||
if s.lruAttributes.simpleLruMap.Len() > 0 { |
||||
sd.Attributes = s.lruAttributesToAttributeMap() |
||||
sd.DroppedAttributeCount = s.lruAttributes.droppedCount |
||||
} |
||||
if len(s.annotations.queue) > 0 { |
||||
sd.Annotations = s.interfaceArrayToAnnotationArray() |
||||
sd.DroppedAnnotationCount = s.annotations.droppedCount |
||||
} |
||||
if len(s.messageEvents.queue) > 0 { |
||||
sd.MessageEvents = s.interfaceArrayToMessageEventArray() |
||||
sd.DroppedMessageEventCount = s.messageEvents.droppedCount |
||||
} |
||||
if len(s.links.queue) > 0 { |
||||
sd.Links = s.interfaceArrayToLinksArray() |
||||
sd.DroppedLinkCount = s.links.droppedCount |
||||
} |
||||
s.mu.Unlock() |
||||
return &sd |
||||
} |
||||
|
||||
// SpanContext returns the SpanContext of the span.
|
||||
func (s *Span) SpanContext() SpanContext { |
||||
if s == nil { |
||||
return SpanContext{} |
||||
} |
||||
return s.spanContext |
||||
} |
||||
|
||||
// SetName sets the name of the span, if it is recording events.
|
||||
func (s *Span) SetName(name string) { |
||||
if !s.IsRecordingEvents() { |
||||
return |
||||
} |
||||
s.mu.Lock() |
||||
s.data.Name = name |
||||
s.mu.Unlock() |
||||
} |
||||
|
||||
// SetStatus sets the status of the span, if it is recording events.
|
||||
func (s *Span) SetStatus(status Status) { |
||||
if !s.IsRecordingEvents() { |
||||
return |
||||
} |
||||
s.mu.Lock() |
||||
s.data.Status = status |
||||
s.mu.Unlock() |
||||
} |
||||
|
||||
func (s *Span) interfaceArrayToLinksArray() []Link { |
||||
linksArr := make([]Link, 0) |
||||
for _, value := range s.links.queue { |
||||
linksArr = append(linksArr, value.(Link)) |
||||
} |
||||
return linksArr |
||||
} |
||||
|
||||
func (s *Span) interfaceArrayToMessageEventArray() []MessageEvent { |
||||
messageEventArr := make([]MessageEvent, 0) |
||||
for _, value := range s.messageEvents.queue { |
||||
messageEventArr = append(messageEventArr, value.(MessageEvent)) |
||||
} |
||||
return messageEventArr |
||||
} |
||||
|
||||
func (s *Span) interfaceArrayToAnnotationArray() []Annotation { |
||||
annotationArr := make([]Annotation, 0) |
||||
for _, value := range s.annotations.queue { |
||||
annotationArr = append(annotationArr, value.(Annotation)) |
||||
} |
||||
return annotationArr |
||||
} |
||||
|
||||
func (s *Span) lruAttributesToAttributeMap() map[string]interface{} { |
||||
attributes := make(map[string]interface{}) |
||||
for _, key := range s.lruAttributes.simpleLruMap.Keys() { |
||||
value, ok := s.lruAttributes.simpleLruMap.Get(key) |
||||
if ok { |
||||
keyStr := key.(string) |
||||
attributes[keyStr] = value |
||||
} |
||||
} |
||||
return attributes |
||||
} |
||||
|
||||
func (s *Span) copyToCappedAttributes(attributes []Attribute) { |
||||
for _, a := range attributes { |
||||
s.lruAttributes.add(a.key, a.value) |
||||
} |
||||
} |
||||
|
||||
func (s *Span) addChild() { |
||||
if !s.IsRecordingEvents() { |
||||
return |
||||
} |
||||
s.mu.Lock() |
||||
s.data.ChildSpanCount++ |
||||
s.mu.Unlock() |
||||
} |
||||
|
||||
// AddAttributes sets attributes in the span.
|
||||
//
|
||||
// Existing attributes whose keys appear in the attributes parameter are overwritten.
|
||||
func (s *Span) AddAttributes(attributes ...Attribute) { |
||||
if !s.IsRecordingEvents() { |
||||
return |
||||
} |
||||
s.mu.Lock() |
||||
s.copyToCappedAttributes(attributes) |
||||
s.mu.Unlock() |
||||
} |
||||
|
||||
// copyAttributes copies a slice of Attributes into a map.
|
||||
func copyAttributes(m map[string]interface{}, attributes []Attribute) { |
||||
for _, a := range attributes { |
||||
m[a.key] = a.value |
||||
} |
||||
} |
||||
|
||||
func (s *Span) lazyPrintfInternal(attributes []Attribute, format string, a ...interface{}) { |
||||
now := time.Now() |
||||
msg := fmt.Sprintf(format, a...) |
||||
var m map[string]interface{} |
||||
s.mu.Lock() |
||||
if len(attributes) != 0 { |
||||
m = make(map[string]interface{}) |
||||
copyAttributes(m, attributes) |
||||
} |
||||
s.annotations.add(Annotation{ |
||||
Time: now, |
||||
Message: msg, |
||||
Attributes: m, |
||||
}) |
||||
s.mu.Unlock() |
||||
} |
||||
|
||||
func (s *Span) printStringInternal(attributes []Attribute, str string) { |
||||
now := time.Now() |
||||
var a map[string]interface{} |
||||
s.mu.Lock() |
||||
if len(attributes) != 0 { |
||||
a = make(map[string]interface{}) |
||||
copyAttributes(a, attributes) |
||||
} |
||||
s.annotations.add(Annotation{ |
||||
Time: now, |
||||
Message: str, |
||||
Attributes: a, |
||||
}) |
||||
s.mu.Unlock() |
||||
} |
||||
|
||||
// Annotate adds an annotation with attributes.
|
||||
// Attributes can be nil.
|
||||
func (s *Span) Annotate(attributes []Attribute, str string) { |
||||
if !s.IsRecordingEvents() { |
||||
return |
||||
} |
||||
s.printStringInternal(attributes, str) |
||||
} |
||||
|
||||
// Annotatef adds an annotation with attributes.
|
||||
func (s *Span) Annotatef(attributes []Attribute, format string, a ...interface{}) { |
||||
if !s.IsRecordingEvents() { |
||||
return |
||||
} |
||||
s.lazyPrintfInternal(attributes, format, a...) |
||||
} |
||||
|
||||
// AddMessageSendEvent adds a message send event to the span.
|
||||
//
|
||||
// messageID is an identifier for the message, which is recommended to be
|
||||
// unique in this span and the same between the send event and the receive
|
||||
// event (this allows to identify a message between the sender and receiver).
|
||||
// For example, this could be a sequence id.
|
||||
func (s *Span) AddMessageSendEvent(messageID, uncompressedByteSize, compressedByteSize int64) { |
||||
if !s.IsRecordingEvents() { |
||||
return |
||||
} |
||||
now := time.Now() |
||||
s.mu.Lock() |
||||
s.messageEvents.add(MessageEvent{ |
||||
Time: now, |
||||
EventType: MessageEventTypeSent, |
||||
MessageID: messageID, |
||||
UncompressedByteSize: uncompressedByteSize, |
||||
CompressedByteSize: compressedByteSize, |
||||
}) |
||||
s.mu.Unlock() |
||||
} |
||||
|
||||
// AddMessageReceiveEvent adds a message receive event to the span.
|
||||
//
|
||||
// messageID is an identifier for the message, which is recommended to be
|
||||
// unique in this span and the same between the send event and the receive
|
||||
// event (this allows to identify a message between the sender and receiver).
|
||||
// For example, this could be a sequence id.
|
||||
func (s *Span) AddMessageReceiveEvent(messageID, uncompressedByteSize, compressedByteSize int64) { |
||||
if !s.IsRecordingEvents() { |
||||
return |
||||
} |
||||
now := time.Now() |
||||
s.mu.Lock() |
||||
s.messageEvents.add(MessageEvent{ |
||||
Time: now, |
||||
EventType: MessageEventTypeRecv, |
||||
MessageID: messageID, |
||||
UncompressedByteSize: uncompressedByteSize, |
||||
CompressedByteSize: compressedByteSize, |
||||
}) |
||||
s.mu.Unlock() |
||||
} |
||||
|
||||
// AddLink adds a link to the span.
|
||||
func (s *Span) AddLink(l Link) { |
||||
if !s.IsRecordingEvents() { |
||||
return |
||||
} |
||||
s.mu.Lock() |
||||
s.links.add(l) |
||||
s.mu.Unlock() |
||||
} |
||||
|
||||
func (s *Span) String() string { |
||||
if s == nil { |
||||
return "<nil>" |
||||
} |
||||
if s.data == nil { |
||||
return fmt.Sprintf("span %s", s.spanContext.SpanID) |
||||
} |
||||
s.mu.Lock() |
||||
str := fmt.Sprintf("span %s %q", s.spanContext.SpanID, s.data.Name) |
||||
s.mu.Unlock() |
||||
return str |
||||
} |
||||
|
||||
var config atomic.Value // access atomically
|
||||
|
||||
func init() { |
||||
gen := &defaultIDGenerator{} |
||||
// initialize traceID and spanID generators.
|
||||
var rngSeed int64 |
||||
for _, p := range []interface{}{ |
||||
&rngSeed, &gen.traceIDAdd, &gen.nextSpanID, &gen.spanIDInc, |
||||
} { |
||||
binary.Read(crand.Reader, binary.LittleEndian, p) |
||||
} |
||||
gen.traceIDRand = rand.New(rand.NewSource(rngSeed)) |
||||
gen.spanIDInc |= 1 |
||||
|
||||
config.Store(&Config{ |
||||
DefaultSampler: ProbabilitySampler(defaultSamplingProbability), |
||||
IDGenerator: gen, |
||||
MaxAttributesPerSpan: DefaultMaxAttributesPerSpan, |
||||
MaxAnnotationEventsPerSpan: DefaultMaxAnnotationEventsPerSpan, |
||||
MaxMessageEventsPerSpan: DefaultMaxMessageEventsPerSpan, |
||||
MaxLinksPerSpan: DefaultMaxLinksPerSpan, |
||||
}) |
||||
} |
||||
|
||||
type defaultIDGenerator struct { |
||||
sync.Mutex |
||||
|
||||
// Please keep these as the first fields
|
||||
// so that these 8 byte fields will be aligned on addresses
|
||||
// divisible by 8, on both 32-bit and 64-bit machines when
|
||||
// performing atomic increments and accesses.
|
||||
// See:
|
||||
// * https://github.com/census-instrumentation/opencensus-go/issues/587
|
||||
// * https://github.com/census-instrumentation/opencensus-go/issues/865
|
||||
// * https://golang.org/pkg/sync/atomic/#pkg-note-BUG
|
||||
nextSpanID uint64 |
||||
spanIDInc uint64 |
||||
|
||||
traceIDAdd [2]uint64 |
||||
traceIDRand *rand.Rand |
||||
} |
||||
|
||||
// NewSpanID returns a non-zero span ID from a randomly-chosen sequence.
|
||||
func (gen *defaultIDGenerator) NewSpanID() [8]byte { |
||||
var id uint64 |
||||
for id == 0 { |
||||
id = atomic.AddUint64(&gen.nextSpanID, gen.spanIDInc) |
||||
} |
||||
var sid [8]byte |
||||
binary.LittleEndian.PutUint64(sid[:], id) |
||||
return sid |
||||
} |
||||
|
||||
// NewTraceID returns a non-zero trace ID from a randomly-chosen sequence.
|
||||
// mu should be held while this function is called.
|
||||
func (gen *defaultIDGenerator) NewTraceID() [16]byte { |
||||
var tid [16]byte |
||||
// Construct the trace ID from two outputs of traceIDRand, with a constant
|
||||
// added to each half for additional entropy.
|
||||
gen.Lock() |
||||
binary.LittleEndian.PutUint64(tid[0:8], gen.traceIDRand.Uint64()+gen.traceIDAdd[0]) |
||||
binary.LittleEndian.PutUint64(tid[8:16], gen.traceIDRand.Uint64()+gen.traceIDAdd[1]) |
||||
gen.Unlock() |
||||
return tid |
||||
} |
||||
@ -0,0 +1,32 @@
|
||||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// 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.11
|
||||
|
||||
package trace |
||||
|
||||
import ( |
||||
"context" |
||||
t "runtime/trace" |
||||
) |
||||
|
||||
func startExecutionTracerTask(ctx context.Context, name string) (context.Context, func()) { |
||||
if !t.IsEnabled() { |
||||
// Avoid additional overhead if
|
||||
// runtime/trace is not enabled.
|
||||
return ctx, func() {} |
||||
} |
||||
nctx, task := t.NewTask(ctx, name) |
||||
return nctx, task.End |
||||
} |
||||
@ -0,0 +1,25 @@
|
||||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// 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.11
|
||||
|
||||
package trace |
||||
|
||||
import ( |
||||
"context" |
||||
) |
||||
|
||||
func startExecutionTracerTask(ctx context.Context, name string) (context.Context, func()) { |
||||
return ctx, func() {} |
||||
} |
||||
@ -0,0 +1,147 @@
|
||||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// 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 tracestate implements support for the Tracestate header of the
|
||||
// W3C TraceContext propagation format.
|
||||
package tracestate |
||||
|
||||
import ( |
||||
"fmt" |
||||
"regexp" |
||||
) |
||||
|
||||
const ( |
||||
keyMaxSize = 256 |
||||
valueMaxSize = 256 |
||||
maxKeyValuePairs = 32 |
||||
) |
||||
|
||||
const ( |
||||
keyWithoutVendorFormat = `[a-z][_0-9a-z\-\*\/]{0,255}` |
||||
keyWithVendorFormat = `[a-z][_0-9a-z\-\*\/]{0,240}@[a-z][_0-9a-z\-\*\/]{0,13}` |
||||
keyFormat = `(` + keyWithoutVendorFormat + `)|(` + keyWithVendorFormat + `)` |
||||
valueFormat = `[\x20-\x2b\x2d-\x3c\x3e-\x7e]{0,255}[\x21-\x2b\x2d-\x3c\x3e-\x7e]` |
||||
) |
||||
|
||||
var keyValidationRegExp = regexp.MustCompile(`^(` + keyFormat + `)$`) |
||||
var valueValidationRegExp = regexp.MustCompile(`^(` + valueFormat + `)$`) |
||||
|
||||
// Tracestate represents tracing-system specific context in a list of key-value pairs. Tracestate allows different
|
||||
// vendors propagate additional information and inter-operate with their legacy Id formats.
|
||||
type Tracestate struct { |
||||
entries []Entry |
||||
} |
||||
|
||||
// Entry represents one key-value pair in a list of key-value pair of Tracestate.
|
||||
type Entry struct { |
||||
// Key is an opaque string up to 256 characters printable. It MUST begin with a lowercase letter,
|
||||
// and can only contain lowercase letters a-z, digits 0-9, underscores _, dashes -, asterisks *, and
|
||||
// forward slashes /.
|
||||
Key string |
||||
|
||||
// Value is an opaque string up to 256 characters printable ASCII RFC0020 characters (i.e., the
|
||||
// range 0x20 to 0x7E) except comma , and =.
|
||||
Value string |
||||
} |
||||
|
||||
// Entries returns a slice of Entry.
|
||||
func (ts *Tracestate) Entries() []Entry { |
||||
if ts == nil { |
||||
return nil |
||||
} |
||||
return ts.entries |
||||
} |
||||
|
||||
func (ts *Tracestate) remove(key string) *Entry { |
||||
for index, entry := range ts.entries { |
||||
if entry.Key == key { |
||||
ts.entries = append(ts.entries[:index], ts.entries[index+1:]...) |
||||
return &entry |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (ts *Tracestate) add(entries []Entry) error { |
||||
for _, entry := range entries { |
||||
ts.remove(entry.Key) |
||||
} |
||||
if len(ts.entries)+len(entries) > maxKeyValuePairs { |
||||
return fmt.Errorf("adding %d key-value pairs to current %d pairs exceeds the limit of %d", |
||||
len(entries), len(ts.entries), maxKeyValuePairs) |
||||
} |
||||
ts.entries = append(entries, ts.entries...) |
||||
return nil |
||||
} |
||||
|
||||
func isValid(entry Entry) bool { |
||||
return keyValidationRegExp.MatchString(entry.Key) && |
||||
valueValidationRegExp.MatchString(entry.Value) |
||||
} |
||||
|
||||
func containsDuplicateKey(entries ...Entry) (string, bool) { |
||||
keyMap := make(map[string]int) |
||||
for _, entry := range entries { |
||||
if _, ok := keyMap[entry.Key]; ok { |
||||
return entry.Key, true |
||||
} |
||||
keyMap[entry.Key] = 1 |
||||
} |
||||
return "", false |
||||
} |
||||
|
||||
func areEntriesValid(entries ...Entry) (*Entry, bool) { |
||||
for _, entry := range entries { |
||||
if !isValid(entry) { |
||||
return &entry, false |
||||
} |
||||
} |
||||
return nil, true |
||||
} |
||||
|
||||
// New creates a Tracestate object from a parent and/or entries (key-value pair).
|
||||
// Entries from the parent are copied if present. The entries passed to this function
|
||||
// are inserted in front of those copied from the parent. If an entry copied from the
|
||||
// parent contains the same key as one of the entry in entries then the entry copied
|
||||
// from the parent is removed. See add func.
|
||||
//
|
||||
// An error is returned with nil Tracestate if
|
||||
// 1. one or more entry in entries is invalid.
|
||||
// 2. two or more entries in the input entries have the same key.
|
||||
// 3. the number of entries combined from the parent and the input entries exceeds maxKeyValuePairs.
|
||||
// (duplicate entry is counted only once).
|
||||
func New(parent *Tracestate, entries ...Entry) (*Tracestate, error) { |
||||
if parent == nil && len(entries) == 0 { |
||||
return nil, nil |
||||
} |
||||
if entry, ok := areEntriesValid(entries...); !ok { |
||||
return nil, fmt.Errorf("key-value pair {%s, %s} is invalid", entry.Key, entry.Value) |
||||
} |
||||
|
||||
if key, duplicate := containsDuplicateKey(entries...); duplicate { |
||||
return nil, fmt.Errorf("contains duplicate keys (%s)", key) |
||||
} |
||||
|
||||
tracestate := Tracestate{} |
||||
|
||||
if parent != nil && len(parent.entries) > 0 { |
||||
tracestate.entries = append([]Entry{}, parent.entries...) |
||||
} |
||||
|
||||
err := tracestate.add(entries) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return &tracestate, nil |
||||
} |
||||
@ -0,0 +1,38 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package google |
||||
|
||||
import ( |
||||
"context" |
||||
"time" |
||||
|
||||
"golang.org/x/oauth2" |
||||
) |
||||
|
||||
// Set at init time by appengine_gen1.go. If nil, we're not on App Engine standard first generation (<= Go 1.9) or App Engine flexible.
|
||||
var appengineTokenFunc func(c context.Context, scopes ...string) (token string, expiry time.Time, err error) |
||||
|
||||
// Set at init time by appengine_gen1.go. If nil, we're not on App Engine standard first generation (<= Go 1.9) or App Engine flexible.
|
||||
var appengineAppIDFunc func(c context.Context) string |
||||
|
||||
// AppEngineTokenSource returns a token source that fetches tokens from either
|
||||
// the current application's service account or from the metadata server,
|
||||
// depending on the App Engine environment. See below for environment-specific
|
||||
// details. If you are implementing a 3-legged OAuth 2.0 flow on App Engine that
|
||||
// involves user accounts, see oauth2.Config instead.
|
||||
//
|
||||
// First generation App Engine runtimes (<= Go 1.9):
|
||||
// AppEngineTokenSource returns a token source that fetches tokens issued to the
|
||||
// current App Engine application's service account. The provided context must have
|
||||
// come from appengine.NewContext.
|
||||
//
|
||||
// Second generation App Engine runtimes (>= Go 1.11) and App Engine flexible:
|
||||
// AppEngineTokenSource is DEPRECATED on second generation runtimes and on the
|
||||
// flexible environment. It delegates to ComputeTokenSource, and the provided
|
||||
// context and scopes are not used. Please use DefaultTokenSource (or ComputeTokenSource,
|
||||
// which DefaultTokenSource will use in this case) instead.
|
||||
func AppEngineTokenSource(ctx context.Context, scope ...string) oauth2.TokenSource { |
||||
return appEngineTokenSource(ctx, scope...) |
||||
} |
||||
@ -0,0 +1,77 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build appengine
|
||||
|
||||
// This file applies to App Engine first generation runtimes (<= Go 1.9).
|
||||
|
||||
package google |
||||
|
||||
import ( |
||||
"context" |
||||
"sort" |
||||
"strings" |
||||
"sync" |
||||
|
||||
"golang.org/x/oauth2" |
||||
"google.golang.org/appengine" |
||||
) |
||||
|
||||
func init() { |
||||
appengineTokenFunc = appengine.AccessToken |
||||
appengineAppIDFunc = appengine.AppID |
||||
} |
||||
|
||||
// See comment on AppEngineTokenSource in appengine.go.
|
||||
func appEngineTokenSource(ctx context.Context, scope ...string) oauth2.TokenSource { |
||||
scopes := append([]string{}, scope...) |
||||
sort.Strings(scopes) |
||||
return &gaeTokenSource{ |
||||
ctx: ctx, |
||||
scopes: scopes, |
||||
key: strings.Join(scopes, " "), |
||||
} |
||||
} |
||||
|
||||
// aeTokens helps the fetched tokens to be reused until their expiration.
|
||||
var ( |
||||
aeTokensMu sync.Mutex |
||||
aeTokens = make(map[string]*tokenLock) // key is space-separated scopes
|
||||
) |
||||
|
||||
type tokenLock struct { |
||||
mu sync.Mutex // guards t; held while fetching or updating t
|
||||
t *oauth2.Token |
||||
} |
||||
|
||||
type gaeTokenSource struct { |
||||
ctx context.Context |
||||
scopes []string |
||||
key string // to aeTokens map; space-separated scopes
|
||||
} |
||||
|
||||
func (ts *gaeTokenSource) Token() (*oauth2.Token, error) { |
||||
aeTokensMu.Lock() |
||||
tok, ok := aeTokens[ts.key] |
||||
if !ok { |
||||
tok = &tokenLock{} |
||||
aeTokens[ts.key] = tok |
||||
} |
||||
aeTokensMu.Unlock() |
||||
|
||||
tok.mu.Lock() |
||||
defer tok.mu.Unlock() |
||||
if tok.t.Valid() { |
||||
return tok.t, nil |
||||
} |
||||
access, exp, err := appengineTokenFunc(ts.ctx, ts.scopes...) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
tok.t = &oauth2.Token{ |
||||
AccessToken: access, |
||||
Expiry: exp, |
||||
} |
||||
return tok.t, nil |
||||
} |
||||
@ -0,0 +1,27 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !appengine
|
||||
|
||||
// This file applies to App Engine second generation runtimes (>= Go 1.11) and App Engine flexible.
|
||||
|
||||
package google |
||||
|
||||
import ( |
||||
"context" |
||||
"log" |
||||
"sync" |
||||
|
||||
"golang.org/x/oauth2" |
||||
) |
||||
|
||||
var logOnce sync.Once // only spam about deprecation once
|
||||
|
||||
// See comment on AppEngineTokenSource in appengine.go.
|
||||
func appEngineTokenSource(ctx context.Context, scope ...string) oauth2.TokenSource { |
||||
logOnce.Do(func() { |
||||
log.Print("google: AppEngineTokenSource is deprecated on App Engine standard second generation runtimes (>= Go 1.11) and App Engine flexible. Please use DefaultTokenSource or ComputeTokenSource.") |
||||
}) |
||||
return ComputeTokenSource("") |
||||
} |
||||
@ -0,0 +1,154 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package google |
||||
|
||||
import ( |
||||
"context" |
||||
"encoding/json" |
||||
"fmt" |
||||
"io/ioutil" |
||||
"net/http" |
||||
"os" |
||||
"path/filepath" |
||||
"runtime" |
||||
|
||||
"cloud.google.com/go/compute/metadata" |
||||
"golang.org/x/oauth2" |
||||
) |
||||
|
||||
// Credentials holds Google credentials, including "Application Default Credentials".
|
||||
// For more details, see:
|
||||
// https://developers.google.com/accounts/docs/application-default-credentials
|
||||
type Credentials struct { |
||||
ProjectID string // may be empty
|
||||
TokenSource oauth2.TokenSource |
||||
|
||||
// JSON contains the raw bytes from a JSON credentials file.
|
||||
// This field may be nil if authentication is provided by the
|
||||
// environment and not with a credentials file, e.g. when code is
|
||||
// running on Google Cloud Platform.
|
||||
JSON []byte |
||||
} |
||||
|
||||
// DefaultCredentials is the old name of Credentials.
|
||||
//
|
||||
// Deprecated: use Credentials instead.
|
||||
type DefaultCredentials = Credentials |
||||
|
||||
// DefaultClient returns an HTTP Client that uses the
|
||||
// DefaultTokenSource to obtain authentication credentials.
|
||||
func DefaultClient(ctx context.Context, scope ...string) (*http.Client, error) { |
||||
ts, err := DefaultTokenSource(ctx, scope...) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return oauth2.NewClient(ctx, ts), nil |
||||
} |
||||
|
||||
// DefaultTokenSource returns the token source for
|
||||
// "Application Default Credentials".
|
||||
// It is a shortcut for FindDefaultCredentials(ctx, scope).TokenSource.
|
||||
func DefaultTokenSource(ctx context.Context, scope ...string) (oauth2.TokenSource, error) { |
||||
creds, err := FindDefaultCredentials(ctx, scope...) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return creds.TokenSource, nil |
||||
} |
||||
|
||||
// FindDefaultCredentials searches for "Application Default Credentials".
|
||||
//
|
||||
// It looks for credentials in the following places,
|
||||
// preferring the first location found:
|
||||
//
|
||||
// 1. A JSON file whose path is specified by the
|
||||
// GOOGLE_APPLICATION_CREDENTIALS environment variable.
|
||||
// 2. A JSON file in a location known to the gcloud command-line tool.
|
||||
// On Windows, this is %APPDATA%/gcloud/application_default_credentials.json.
|
||||
// On other systems, $HOME/.config/gcloud/application_default_credentials.json.
|
||||
// 3. On Google App Engine standard first generation runtimes (<= Go 1.9) it uses
|
||||
// the appengine.AccessToken function.
|
||||
// 4. On Google Compute Engine, Google App Engine standard second generation runtimes
|
||||
// (>= Go 1.11), and Google App Engine flexible environment, it fetches
|
||||
// credentials from the metadata server.
|
||||
func FindDefaultCredentials(ctx context.Context, scopes ...string) (*Credentials, error) { |
||||
// First, try the environment variable.
|
||||
const envVar = "GOOGLE_APPLICATION_CREDENTIALS" |
||||
if filename := os.Getenv(envVar); filename != "" { |
||||
creds, err := readCredentialsFile(ctx, filename, scopes) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("google: error getting credentials using %v environment variable: %v", envVar, err) |
||||
} |
||||
return creds, nil |
||||
} |
||||
|
||||
// Second, try a well-known file.
|
||||
filename := wellKnownFile() |
||||
if creds, err := readCredentialsFile(ctx, filename, scopes); err == nil { |
||||
return creds, nil |
||||
} else if !os.IsNotExist(err) { |
||||
return nil, fmt.Errorf("google: error getting credentials using well-known file (%v): %v", filename, err) |
||||
} |
||||
|
||||
// Third, if we're on a Google App Engine standard first generation runtime (<= Go 1.9)
|
||||
// use those credentials. App Engine standard second generation runtimes (>= Go 1.11)
|
||||
// and App Engine flexible use ComputeTokenSource and the metadata server.
|
||||
if appengineTokenFunc != nil { |
||||
return &DefaultCredentials{ |
||||
ProjectID: appengineAppIDFunc(ctx), |
||||
TokenSource: AppEngineTokenSource(ctx, scopes...), |
||||
}, nil |
||||
} |
||||
|
||||
// Fourth, if we're on Google Compute Engine, an App Engine standard second generation runtime,
|
||||
// or App Engine flexible, use the metadata server.
|
||||
if metadata.OnGCE() { |
||||
id, _ := metadata.ProjectID() |
||||
return &DefaultCredentials{ |
||||
ProjectID: id, |
||||
TokenSource: ComputeTokenSource("", scopes...), |
||||
}, nil |
||||
} |
||||
|
||||
// None are found; return helpful error.
|
||||
const url = "https://developers.google.com/accounts/docs/application-default-credentials" |
||||
return nil, fmt.Errorf("google: could not find default credentials. See %v for more information.", url) |
||||
} |
||||
|
||||
// CredentialsFromJSON obtains Google credentials from a JSON value. The JSON can
|
||||
// represent either a Google Developers Console client_credentials.json file (as in
|
||||
// ConfigFromJSON) or a Google Developers service account key file (as in
|
||||
// JWTConfigFromJSON).
|
||||
func CredentialsFromJSON(ctx context.Context, jsonData []byte, scopes ...string) (*Credentials, error) { |
||||
var f credentialsFile |
||||
if err := json.Unmarshal(jsonData, &f); err != nil { |
||||
return nil, err |
||||
} |
||||
ts, err := f.tokenSource(ctx, append([]string(nil), scopes...)) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return &DefaultCredentials{ |
||||
ProjectID: f.ProjectID, |
||||
TokenSource: ts, |
||||
JSON: jsonData, |
||||
}, nil |
||||
} |
||||
|
||||
func wellKnownFile() string { |
||||
const f = "application_default_credentials.json" |
||||
if runtime.GOOS == "windows" { |
||||
return filepath.Join(os.Getenv("APPDATA"), "gcloud", f) |
||||
} |
||||
return filepath.Join(guessUnixHomeDir(), ".config", "gcloud", f) |
||||
} |
||||
|
||||
func readCredentialsFile(ctx context.Context, filename string, scopes []string) (*DefaultCredentials, error) { |
||||
b, err := ioutil.ReadFile(filename) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return CredentialsFromJSON(ctx, b, scopes...) |
||||
} |
||||
@ -0,0 +1,40 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package google provides support for making OAuth2 authorized and authenticated
|
||||
// HTTP requests to Google APIs. It supports the Web server flow, client-side
|
||||
// credentials, service accounts, Google Compute Engine service accounts, and Google
|
||||
// App Engine service accounts.
|
||||
//
|
||||
// A brief overview of the package follows. For more information, please read
|
||||
// https://developers.google.com/accounts/docs/OAuth2
|
||||
// and
|
||||
// https://developers.google.com/accounts/docs/application-default-credentials.
|
||||
//
|
||||
// OAuth2 Configs
|
||||
//
|
||||
// Two functions in this package return golang.org/x/oauth2.Config values from Google credential
|
||||
// data. Google supports two JSON formats for OAuth2 credentials: one is handled by ConfigFromJSON,
|
||||
// the other by JWTConfigFromJSON. The returned Config can be used to obtain a TokenSource or
|
||||
// create an http.Client.
|
||||
//
|
||||
//
|
||||
// Credentials
|
||||
//
|
||||
// The Credentials type represents Google credentials, including Application Default
|
||||
// Credentials.
|
||||
//
|
||||
// Use FindDefaultCredentials to obtain Application Default Credentials.
|
||||
// FindDefaultCredentials looks in some well-known places for a credentials file, and
|
||||
// will call AppEngineTokenSource or ComputeTokenSource as needed.
|
||||
//
|
||||
// DefaultClient and DefaultTokenSource are convenience methods. They first call FindDefaultCredentials,
|
||||
// then use the credentials to construct an http.Client or an oauth2.TokenSource.
|
||||
//
|
||||
// Use CredentialsFromJSON to obtain credentials from either of the two JSON formats
|
||||
// described in OAuth2 Configs, above. The TokenSource in the returned value is the
|
||||
// same as the one obtained from the oauth2.Config returned from ConfigFromJSON or
|
||||
// JWTConfigFromJSON, but the Credentials may contain additional information
|
||||
// that is useful is some circumstances.
|
||||
package google // import "golang.org/x/oauth2/google"
|
||||
@ -0,0 +1,209 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package google |
||||
|
||||
import ( |
||||
"context" |
||||
"encoding/json" |
||||
"errors" |
||||
"fmt" |
||||
"net/url" |
||||
"strings" |
||||
"time" |
||||
|
||||
"cloud.google.com/go/compute/metadata" |
||||
"golang.org/x/oauth2" |
||||
"golang.org/x/oauth2/jwt" |
||||
) |
||||
|
||||
// Endpoint is Google's OAuth 2.0 endpoint.
|
||||
var Endpoint = oauth2.Endpoint{ |
||||
AuthURL: "https://accounts.google.com/o/oauth2/auth", |
||||
TokenURL: "https://oauth2.googleapis.com/token", |
||||
AuthStyle: oauth2.AuthStyleInParams, |
||||
} |
||||
|
||||
// JWTTokenURL is Google's OAuth 2.0 token URL to use with the JWT flow.
|
||||
const JWTTokenURL = "https://oauth2.googleapis.com/token" |
||||
|
||||
// ConfigFromJSON uses a Google Developers Console client_credentials.json
|
||||
// file to construct a config.
|
||||
// client_credentials.json can be downloaded from
|
||||
// https://console.developers.google.com, under "Credentials". Download the Web
|
||||
// application credentials in the JSON format and provide the contents of the
|
||||
// file as jsonKey.
|
||||
func ConfigFromJSON(jsonKey []byte, scope ...string) (*oauth2.Config, error) { |
||||
type cred struct { |
||||
ClientID string `json:"client_id"` |
||||
ClientSecret string `json:"client_secret"` |
||||
RedirectURIs []string `json:"redirect_uris"` |
||||
AuthURI string `json:"auth_uri"` |
||||
TokenURI string `json:"token_uri"` |
||||
} |
||||
var j struct { |
||||
Web *cred `json:"web"` |
||||
Installed *cred `json:"installed"` |
||||
} |
||||
if err := json.Unmarshal(jsonKey, &j); err != nil { |
||||
return nil, err |
||||
} |
||||
var c *cred |
||||
switch { |
||||
case j.Web != nil: |
||||
c = j.Web |
||||
case j.Installed != nil: |
||||
c = j.Installed |
||||
default: |
||||
return nil, fmt.Errorf("oauth2/google: no credentials found") |
||||
} |
||||
if len(c.RedirectURIs) < 1 { |
||||
return nil, errors.New("oauth2/google: missing redirect URL in the client_credentials.json") |
||||
} |
||||
return &oauth2.Config{ |
||||
ClientID: c.ClientID, |
||||
ClientSecret: c.ClientSecret, |
||||
RedirectURL: c.RedirectURIs[0], |
||||
Scopes: scope, |
||||
Endpoint: oauth2.Endpoint{ |
||||
AuthURL: c.AuthURI, |
||||
TokenURL: c.TokenURI, |
||||
}, |
||||
}, nil |
||||
} |
||||
|
||||
// JWTConfigFromJSON uses a Google Developers service account JSON key file to read
|
||||
// the credentials that authorize and authenticate the requests.
|
||||
// Create a service account on "Credentials" for your project at
|
||||
// https://console.developers.google.com to download a JSON key file.
|
||||
func JWTConfigFromJSON(jsonKey []byte, scope ...string) (*jwt.Config, error) { |
||||
var f credentialsFile |
||||
if err := json.Unmarshal(jsonKey, &f); err != nil { |
||||
return nil, err |
||||
} |
||||
if f.Type != serviceAccountKey { |
||||
return nil, fmt.Errorf("google: read JWT from JSON credentials: 'type' field is %q (expected %q)", f.Type, serviceAccountKey) |
||||
} |
||||
scope = append([]string(nil), scope...) // copy
|
||||
return f.jwtConfig(scope), nil |
||||
} |
||||
|
||||
// JSON key file types.
|
||||
const ( |
||||
serviceAccountKey = "service_account" |
||||
userCredentialsKey = "authorized_user" |
||||
) |
||||
|
||||
// credentialsFile is the unmarshalled representation of a credentials file.
|
||||
type credentialsFile struct { |
||||
Type string `json:"type"` // serviceAccountKey or userCredentialsKey
|
||||
|
||||
// Service Account fields
|
||||
ClientEmail string `json:"client_email"` |
||||
PrivateKeyID string `json:"private_key_id"` |
||||
PrivateKey string `json:"private_key"` |
||||
TokenURL string `json:"token_uri"` |
||||
ProjectID string `json:"project_id"` |
||||
|
||||
// User Credential fields
|
||||
// (These typically come from gcloud auth.)
|
||||
ClientSecret string `json:"client_secret"` |
||||
ClientID string `json:"client_id"` |
||||
RefreshToken string `json:"refresh_token"` |
||||
} |
||||
|
||||
func (f *credentialsFile) jwtConfig(scopes []string) *jwt.Config { |
||||
cfg := &jwt.Config{ |
||||
Email: f.ClientEmail, |
||||
PrivateKey: []byte(f.PrivateKey), |
||||
PrivateKeyID: f.PrivateKeyID, |
||||
Scopes: scopes, |
||||
TokenURL: f.TokenURL, |
||||
} |
||||
if cfg.TokenURL == "" { |
||||
cfg.TokenURL = JWTTokenURL |
||||
} |
||||
return cfg |
||||
} |
||||
|
||||
func (f *credentialsFile) tokenSource(ctx context.Context, scopes []string) (oauth2.TokenSource, error) { |
||||
switch f.Type { |
||||
case serviceAccountKey: |
||||
cfg := f.jwtConfig(scopes) |
||||
return cfg.TokenSource(ctx), nil |
||||
case userCredentialsKey: |
||||
cfg := &oauth2.Config{ |
||||
ClientID: f.ClientID, |
||||
ClientSecret: f.ClientSecret, |
||||
Scopes: scopes, |
||||
Endpoint: Endpoint, |
||||
} |
||||
tok := &oauth2.Token{RefreshToken: f.RefreshToken} |
||||
return cfg.TokenSource(ctx, tok), nil |
||||
case "": |
||||
return nil, errors.New("missing 'type' field in credentials") |
||||
default: |
||||
return nil, fmt.Errorf("unknown credential type: %q", f.Type) |
||||
} |
||||
} |
||||
|
||||
// ComputeTokenSource returns a token source that fetches access tokens
|
||||
// from Google Compute Engine (GCE)'s metadata server. It's only valid to use
|
||||
// this token source if your program is running on a GCE instance.
|
||||
// If no account is specified, "default" is used.
|
||||
// If no scopes are specified, a set of default scopes are automatically granted.
|
||||
// Further information about retrieving access tokens from the GCE metadata
|
||||
// server can be found at https://cloud.google.com/compute/docs/authentication.
|
||||
func ComputeTokenSource(account string, scope ...string) oauth2.TokenSource { |
||||
return oauth2.ReuseTokenSource(nil, computeSource{account: account, scopes: scope}) |
||||
} |
||||
|
||||
type computeSource struct { |
||||
account string |
||||
scopes []string |
||||
} |
||||
|
||||
func (cs computeSource) Token() (*oauth2.Token, error) { |
||||
if !metadata.OnGCE() { |
||||
return nil, errors.New("oauth2/google: can't get a token from the metadata service; not running on GCE") |
||||
} |
||||
acct := cs.account |
||||
if acct == "" { |
||||
acct = "default" |
||||
} |
||||
tokenURI := "instance/service-accounts/" + acct + "/token" |
||||
if len(cs.scopes) > 0 { |
||||
v := url.Values{} |
||||
v.Set("scopes", strings.Join(cs.scopes, ",")) |
||||
tokenURI = tokenURI + "?" + v.Encode() |
||||
} |
||||
tokenJSON, err := metadata.Get(tokenURI) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
var res struct { |
||||
AccessToken string `json:"access_token"` |
||||
ExpiresInSec int `json:"expires_in"` |
||||
TokenType string `json:"token_type"` |
||||
} |
||||
err = json.NewDecoder(strings.NewReader(tokenJSON)).Decode(&res) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("oauth2/google: invalid token JSON from metadata: %v", err) |
||||
} |
||||
if res.ExpiresInSec == 0 || res.AccessToken == "" { |
||||
return nil, fmt.Errorf("oauth2/google: incomplete token received from metadata") |
||||
} |
||||
tok := &oauth2.Token{ |
||||
AccessToken: res.AccessToken, |
||||
TokenType: res.TokenType, |
||||
Expiry: time.Now().Add(time.Duration(res.ExpiresInSec) * time.Second), |
||||
} |
||||
// NOTE(cbro): add hidden metadata about where the token is from.
|
||||
// This is needed for detection by client libraries to know that credentials come from the metadata server.
|
||||
// This may be removed in a future version of this library.
|
||||
return tok.WithExtra(map[string]interface{}{ |
||||
"oauth2.google.tokenSource": "compute-metadata", |
||||
"oauth2.google.serviceAccount": acct, |
||||
}), nil |
||||
} |
||||
@ -0,0 +1,74 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package google |
||||
|
||||
import ( |
||||
"crypto/rsa" |
||||
"fmt" |
||||
"time" |
||||
|
||||
"golang.org/x/oauth2" |
||||
"golang.org/x/oauth2/internal" |
||||
"golang.org/x/oauth2/jws" |
||||
) |
||||
|
||||
// JWTAccessTokenSourceFromJSON uses a Google Developers service account JSON
|
||||
// key file to read the credentials that authorize and authenticate the
|
||||
// requests, and returns a TokenSource that does not use any OAuth2 flow but
|
||||
// instead creates a JWT and sends that as the access token.
|
||||
// The audience is typically a URL that specifies the scope of the credentials.
|
||||
//
|
||||
// Note that this is not a standard OAuth flow, but rather an
|
||||
// optimization supported by a few Google services.
|
||||
// Unless you know otherwise, you should use JWTConfigFromJSON instead.
|
||||
func JWTAccessTokenSourceFromJSON(jsonKey []byte, audience string) (oauth2.TokenSource, error) { |
||||
cfg, err := JWTConfigFromJSON(jsonKey) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("google: could not parse JSON key: %v", err) |
||||
} |
||||
pk, err := internal.ParseKey(cfg.PrivateKey) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("google: could not parse key: %v", err) |
||||
} |
||||
ts := &jwtAccessTokenSource{ |
||||
email: cfg.Email, |
||||
audience: audience, |
||||
pk: pk, |
||||
pkID: cfg.PrivateKeyID, |
||||
} |
||||
tok, err := ts.Token() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return oauth2.ReuseTokenSource(tok, ts), nil |
||||
} |
||||
|
||||
type jwtAccessTokenSource struct { |
||||
email, audience string |
||||
pk *rsa.PrivateKey |
||||
pkID string |
||||
} |
||||
|
||||
func (ts *jwtAccessTokenSource) Token() (*oauth2.Token, error) { |
||||
iat := time.Now() |
||||
exp := iat.Add(time.Hour) |
||||
cs := &jws.ClaimSet{ |
||||
Iss: ts.email, |
||||
Sub: ts.email, |
||||
Aud: ts.audience, |
||||
Iat: iat.Unix(), |
||||
Exp: exp.Unix(), |
||||
} |
||||
hdr := &jws.Header{ |
||||
Algorithm: "RS256", |
||||
Typ: "JWT", |
||||
KeyID: string(ts.pkID), |
||||
} |
||||
msg, err := jws.Encode(hdr, cs, ts.pk) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("google: could not encode JWT: %v", err) |
||||
} |
||||
return &oauth2.Token{AccessToken: msg, TokenType: "Bearer", Expiry: exp}, nil |
||||
} |
||||
@ -0,0 +1,201 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package google |
||||
|
||||
import ( |
||||
"bufio" |
||||
"context" |
||||
"encoding/json" |
||||
"errors" |
||||
"fmt" |
||||
"io" |
||||
"net/http" |
||||
"os" |
||||
"os/user" |
||||
"path/filepath" |
||||
"runtime" |
||||
"strings" |
||||
"time" |
||||
|
||||
"golang.org/x/oauth2" |
||||
) |
||||
|
||||
type sdkCredentials struct { |
||||
Data []struct { |
||||
Credential struct { |
||||
ClientID string `json:"client_id"` |
||||
ClientSecret string `json:"client_secret"` |
||||
AccessToken string `json:"access_token"` |
||||
RefreshToken string `json:"refresh_token"` |
||||
TokenExpiry *time.Time `json:"token_expiry"` |
||||
} `json:"credential"` |
||||
Key struct { |
||||
Account string `json:"account"` |
||||
Scope string `json:"scope"` |
||||
} `json:"key"` |
||||
} |
||||
} |
||||
|
||||
// An SDKConfig provides access to tokens from an account already
|
||||
// authorized via the Google Cloud SDK.
|
||||
type SDKConfig struct { |
||||
conf oauth2.Config |
||||
initialToken *oauth2.Token |
||||
} |
||||
|
||||
// NewSDKConfig creates an SDKConfig for the given Google Cloud SDK
|
||||
// account. If account is empty, the account currently active in
|
||||
// Google Cloud SDK properties is used.
|
||||
// Google Cloud SDK credentials must be created by running `gcloud auth`
|
||||
// before using this function.
|
||||
// The Google Cloud SDK is available at https://cloud.google.com/sdk/.
|
||||
func NewSDKConfig(account string) (*SDKConfig, error) { |
||||
configPath, err := sdkConfigPath() |
||||
if err != nil { |
||||
return nil, fmt.Errorf("oauth2/google: error getting SDK config path: %v", err) |
||||
} |
||||
credentialsPath := filepath.Join(configPath, "credentials") |
||||
f, err := os.Open(credentialsPath) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("oauth2/google: failed to load SDK credentials: %v", err) |
||||
} |
||||
defer f.Close() |
||||
|
||||
var c sdkCredentials |
||||
if err := json.NewDecoder(f).Decode(&c); err != nil { |
||||
return nil, fmt.Errorf("oauth2/google: failed to decode SDK credentials from %q: %v", credentialsPath, err) |
||||
} |
||||
if len(c.Data) == 0 { |
||||
return nil, fmt.Errorf("oauth2/google: no credentials found in %q, run `gcloud auth login` to create one", credentialsPath) |
||||
} |
||||
if account == "" { |
||||
propertiesPath := filepath.Join(configPath, "properties") |
||||
f, err := os.Open(propertiesPath) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("oauth2/google: failed to load SDK properties: %v", err) |
||||
} |
||||
defer f.Close() |
||||
ini, err := parseINI(f) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("oauth2/google: failed to parse SDK properties %q: %v", propertiesPath, err) |
||||
} |
||||
core, ok := ini["core"] |
||||
if !ok { |
||||
return nil, fmt.Errorf("oauth2/google: failed to find [core] section in %v", ini) |
||||
} |
||||
active, ok := core["account"] |
||||
if !ok { |
||||
return nil, fmt.Errorf("oauth2/google: failed to find %q attribute in %v", "account", core) |
||||
} |
||||
account = active |
||||
} |
||||
|
||||
for _, d := range c.Data { |
||||
if account == "" || d.Key.Account == account { |
||||
if d.Credential.AccessToken == "" && d.Credential.RefreshToken == "" { |
||||
return nil, fmt.Errorf("oauth2/google: no token available for account %q", account) |
||||
} |
||||
var expiry time.Time |
||||
if d.Credential.TokenExpiry != nil { |
||||
expiry = *d.Credential.TokenExpiry |
||||
} |
||||
return &SDKConfig{ |
||||
conf: oauth2.Config{ |
||||
ClientID: d.Credential.ClientID, |
||||
ClientSecret: d.Credential.ClientSecret, |
||||
Scopes: strings.Split(d.Key.Scope, " "), |
||||
Endpoint: Endpoint, |
||||
RedirectURL: "oob", |
||||
}, |
||||
initialToken: &oauth2.Token{ |
||||
AccessToken: d.Credential.AccessToken, |
||||
RefreshToken: d.Credential.RefreshToken, |
||||
Expiry: expiry, |
||||
}, |
||||
}, nil |
||||
} |
||||
} |
||||
return nil, fmt.Errorf("oauth2/google: no such credentials for account %q", account) |
||||
} |
||||
|
||||
// Client returns an HTTP client using Google Cloud SDK credentials to
|
||||
// authorize requests. The token will auto-refresh as necessary. The
|
||||
// underlying http.RoundTripper will be obtained using the provided
|
||||
// context. The returned client and its Transport should not be
|
||||
// modified.
|
||||
func (c *SDKConfig) Client(ctx context.Context) *http.Client { |
||||
return &http.Client{ |
||||
Transport: &oauth2.Transport{ |
||||
Source: c.TokenSource(ctx), |
||||
}, |
||||
} |
||||
} |
||||
|
||||
// TokenSource returns an oauth2.TokenSource that retrieve tokens from
|
||||
// Google Cloud SDK credentials using the provided context.
|
||||
// It will returns the current access token stored in the credentials,
|
||||
// and refresh it when it expires, but it won't update the credentials
|
||||
// with the new access token.
|
||||
func (c *SDKConfig) TokenSource(ctx context.Context) oauth2.TokenSource { |
||||
return c.conf.TokenSource(ctx, c.initialToken) |
||||
} |
||||
|
||||
// Scopes are the OAuth 2.0 scopes the current account is authorized for.
|
||||
func (c *SDKConfig) Scopes() []string { |
||||
return c.conf.Scopes |
||||
} |
||||
|
||||
func parseINI(ini io.Reader) (map[string]map[string]string, error) { |
||||
result := map[string]map[string]string{ |
||||
"": {}, // root section
|
||||
} |
||||
scanner := bufio.NewScanner(ini) |
||||
currentSection := "" |
||||
for scanner.Scan() { |
||||
line := strings.TrimSpace(scanner.Text()) |
||||
if strings.HasPrefix(line, ";") { |
||||
// comment.
|
||||
continue |
||||
} |
||||
if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") { |
||||
currentSection = strings.TrimSpace(line[1 : len(line)-1]) |
||||
result[currentSection] = map[string]string{} |
||||
continue |
||||
} |
||||
parts := strings.SplitN(line, "=", 2) |
||||
if len(parts) == 2 && parts[0] != "" { |
||||
result[currentSection][strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1]) |
||||
} |
||||
} |
||||
if err := scanner.Err(); err != nil { |
||||
return nil, fmt.Errorf("error scanning ini: %v", err) |
||||
} |
||||
return result, nil |
||||
} |
||||
|
||||
// sdkConfigPath tries to guess where the gcloud config is located.
|
||||
// It can be overridden during tests.
|
||||
var sdkConfigPath = func() (string, error) { |
||||
if runtime.GOOS == "windows" { |
||||
return filepath.Join(os.Getenv("APPDATA"), "gcloud"), nil |
||||
} |
||||
homeDir := guessUnixHomeDir() |
||||
if homeDir == "" { |
||||
return "", errors.New("unable to get current user home directory: os/user lookup failed; $HOME is empty") |
||||
} |
||||
return filepath.Join(homeDir, ".config", "gcloud"), nil |
||||
} |
||||
|
||||
func guessUnixHomeDir() string { |
||||
// Prefer $HOME over user.Current due to glibc bug: golang.org/issue/13470
|
||||
if v := os.Getenv("HOME"); v != "" { |
||||
return v |
||||
} |
||||
// Else, fall back to user.Current:
|
||||
if u, err := user.Current(); err == nil { |
||||
return u.HomeDir |
||||
} |
||||
return "" |
||||
} |
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue