From aef24dd74bdbe631116d518986c6c75347520182 Mon Sep 17 00:00:00 2001 From: Kevin Crawley <3759919+notsureifkevin@users.noreply.github.com> Date: Mon, 18 Feb 2019 09:52:04 -0600 Subject: [PATCH] Instana tracer implementation --- Gopkg.lock | 28 + Gopkg.toml | 4 + cmd/configuration.go | 6 + config/static/static_config.go | 36 +- docs/configuration/tracing.md | 40 +- server/server.go | 3 + tracing/instana/instana.go | 49 ++ .../github.com/instana/go-sensor/LICENSE.md | 21 + vendor/github.com/instana/go-sensor/agent.go | 183 +++++++ .../github.com/instana/go-sensor/context.go | 42 ++ vendor/github.com/instana/go-sensor/eum.go | 37 ++ vendor/github.com/instana/go-sensor/event.go | 78 +++ vendor/github.com/instana/go-sensor/fsm.go | 242 +++++++++ .../github.com/instana/go-sensor/json_span.go | 38 ++ vendor/github.com/instana/go-sensor/log.go | 52 ++ vendor/github.com/instana/go-sensor/meter.go | 157 ++++++ .../github.com/instana/go-sensor/options.go | 12 + .../instana/go-sensor/propagation.go | 165 ++++++ .../github.com/instana/go-sensor/recorder.go | 175 ++++++ vendor/github.com/instana/go-sensor/sensor.go | 76 +++ vendor/github.com/instana/go-sensor/span.go | 253 +++++++++ vendor/github.com/instana/go-sensor/tracer.go | 119 ++++ .../instana/go-sensor/tracer_options.go | 91 ++++ vendor/github.com/instana/go-sensor/util.go | 99 ++++ vendor/github.com/looplab/fsm/LICENSE | 191 +++++++ vendor/github.com/looplab/fsm/errors.go | 100 ++++ vendor/github.com/looplab/fsm/event.go | 62 +++ vendor/github.com/looplab/fsm/fsm.go | 447 +++++++++++++++ vendor/github.com/looplab/fsm/utils.go | 45 ++ .../opentracing/basictracer-go/LICENSE | 21 + .../opentracing/basictracer-go/context.go | 42 ++ .../opentracing/basictracer-go/debug.go | 78 +++ .../opentracing/basictracer-go/event.go | 62 +++ .../opentracing/basictracer-go/propagation.go | 61 +++ .../basictracer-go/propagation_ot.go | 180 +++++++ .../opentracing/basictracer-go/raw.go | 34 ++ .../opentracing/basictracer-go/recorder.go | 60 +++ .../opentracing/basictracer-go/span.go | 274 ++++++++++ .../opentracing/basictracer-go/tracer.go | 262 +++++++++ .../opentracing/basictracer-go/util.go | 25 + .../basictracer-go/wire/carrier.go | 40 ++ .../opentracing/basictracer-go/wire/gen.go | 6 + .../basictracer-go/wire/wire.pb.go | 508 ++++++++++++++++++ 43 files changed, 4502 insertions(+), 2 deletions(-) create mode 100644 tracing/instana/instana.go create mode 100644 vendor/github.com/instana/go-sensor/LICENSE.md create mode 100644 vendor/github.com/instana/go-sensor/agent.go create mode 100644 vendor/github.com/instana/go-sensor/context.go create mode 100644 vendor/github.com/instana/go-sensor/eum.go create mode 100644 vendor/github.com/instana/go-sensor/event.go create mode 100644 vendor/github.com/instana/go-sensor/fsm.go create mode 100644 vendor/github.com/instana/go-sensor/json_span.go create mode 100644 vendor/github.com/instana/go-sensor/log.go create mode 100644 vendor/github.com/instana/go-sensor/meter.go create mode 100644 vendor/github.com/instana/go-sensor/options.go create mode 100644 vendor/github.com/instana/go-sensor/propagation.go create mode 100644 vendor/github.com/instana/go-sensor/recorder.go create mode 100644 vendor/github.com/instana/go-sensor/sensor.go create mode 100644 vendor/github.com/instana/go-sensor/span.go create mode 100644 vendor/github.com/instana/go-sensor/tracer.go create mode 100644 vendor/github.com/instana/go-sensor/tracer_options.go create mode 100644 vendor/github.com/instana/go-sensor/util.go create mode 100644 vendor/github.com/looplab/fsm/LICENSE create mode 100644 vendor/github.com/looplab/fsm/errors.go create mode 100644 vendor/github.com/looplab/fsm/event.go create mode 100644 vendor/github.com/looplab/fsm/fsm.go create mode 100644 vendor/github.com/looplab/fsm/utils.go create mode 100644 vendor/github.com/opentracing/basictracer-go/LICENSE create mode 100644 vendor/github.com/opentracing/basictracer-go/context.go create mode 100644 vendor/github.com/opentracing/basictracer-go/debug.go create mode 100644 vendor/github.com/opentracing/basictracer-go/event.go create mode 100644 vendor/github.com/opentracing/basictracer-go/propagation.go create mode 100644 vendor/github.com/opentracing/basictracer-go/propagation_ot.go create mode 100644 vendor/github.com/opentracing/basictracer-go/raw.go create mode 100644 vendor/github.com/opentracing/basictracer-go/recorder.go create mode 100644 vendor/github.com/opentracing/basictracer-go/span.go create mode 100644 vendor/github.com/opentracing/basictracer-go/tracer.go create mode 100644 vendor/github.com/opentracing/basictracer-go/util.go create mode 100644 vendor/github.com/opentracing/basictracer-go/wire/carrier.go create mode 100644 vendor/github.com/opentracing/basictracer-go/wire/gen.go create mode 100644 vendor/github.com/opentracing/basictracer-go/wire/wire.pb.go diff --git a/Gopkg.lock b/Gopkg.lock index 2e3bde497..8b8a481f5 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -1022,6 +1022,14 @@ revision = "2d474a3089bcfce6b472779be9470a1f0ef3d5e4" version = "v1.3.7" +[[projects]] + digest = "1:78efd72f12ed0244e5fbe82bd0ecdbaf3e21402ee9176525ef1138a2fc0d3b17" + name = "github.com/instana/go-sensor" + packages = ["."] + pruneopts = "NUT" + revision = "493edb42228321483dd4d59ade71ca93fa89d66b" + version = "1.4.12" + [[projects]] digest = "1:ac6d01547ec4f7f673311b4663909269bfb8249952de3279799289467837c3cc" name = "github.com/jmespath/go-jmespath" @@ -1111,6 +1119,14 @@ revision = "d0d31d8ca62fa3f7e4526ca0ce95de81e4ed001e" version = "v0.5.1" +[[projects]] + digest = "1:8520e78cbe9878f6bf0cfdcfaed5761cd575b3568c260a1c891ac1f5c5c6a726" + name = "github.com/looplab/fsm" + packages = ["."] + pruneopts = "NUT" + revision = "84b5307469f859464403f80919467950a79de1b1" + version = "v0.1.0" + [[projects]] digest = "1:196b0d7580e898df15a7cc5371cbfe2b8e22904f5c6c883ed5db0130e551c8fb" name = "github.com/mailgun/minheap" @@ -1327,6 +1343,17 @@ pruneopts = "NUT" revision = "a52f2342449246d5bcc273e65cbdcfa5f7d6c63c" +[[projects]] + digest = "1:cc405544fecfb5a8e0c409127ef67ce3b91d11143a00121e5b822e4f8eabe7d2" + name = "github.com/opentracing/basictracer-go" + packages = [ + ".", + "wire", + ] + pruneopts = "NUT" + revision = "1b32af207119a14b1b231d451df3ed04a72efebf" + version = "v1.0.0" + [[projects]] digest = "1:7da29c22bcc5c2ffb308324377dc00b5084650348c2799e573ed226d8cc9faf0" name = "github.com/opentracing/opentracing-go" @@ -2326,6 +2353,7 @@ "github.com/hashicorp/consul/api", "github.com/hashicorp/go-version", "github.com/influxdata/influxdb/client/v2", + "github.com/instana/go-sensor", "github.com/libkermit/compose/check", "github.com/libkermit/docker", "github.com/libkermit/docker-check", diff --git a/Gopkg.toml b/Gopkg.toml index 54a47ab9e..e7c37b70d 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -255,3 +255,7 @@ [[constraint]] name = "gopkg.in/DataDog/dd-trace-go.v1" version = "1.7.0" + +[[constraint]] + name = "github.com/instana/go-sensor" + version = "1.4.12" diff --git a/cmd/configuration.go b/cmd/configuration.go index d81bc4b9d..3c9f3368e 100644 --- a/cmd/configuration.go +++ b/cmd/configuration.go @@ -24,6 +24,7 @@ import ( "github.com/containous/traefik/provider/marathon" "github.com/containous/traefik/provider/rest" "github.com/containous/traefik/tracing/datadog" + "github.com/containous/traefik/tracing/instana" "github.com/containous/traefik/tracing/jaeger" "github.com/containous/traefik/tracing/zipkin" "github.com/containous/traefik/types" @@ -114,6 +115,11 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration { Debug: false, PrioritySampling: false, }, + Instana: &instana.Config{ + LocalAgentHost: "localhost", + LocalAgentPort: 42699, + LogLevel: "info", + }, } // default ApiConfiguration diff --git a/config/static/static_config.go b/config/static/static_config.go index 0f32190d6..15dc3307e 100644 --- a/config/static/static_config.go +++ b/config/static/static_config.go @@ -27,6 +27,7 @@ import ( "github.com/containous/traefik/provider/rest" "github.com/containous/traefik/tls" "github.com/containous/traefik/tracing/datadog" + "github.com/containous/traefik/tracing/instana" "github.com/containous/traefik/tracing/jaeger" "github.com/containous/traefik/tracing/zipkin" "github.com/containous/traefik/types" @@ -117,12 +118,13 @@ type LifeCycle struct { // Tracing holds the tracing configuration. type Tracing struct { - Backend string `description:"Selects the tracking backend ('jaeger','zipkin', 'datadog')." export:"true"` + Backend string `description:"Selects the tracking backend ('jaeger','zipkin','datadog','instana')." export:"true"` ServiceName string `description:"Set the name for this service" export:"true"` SpanNameLimit int `description:"Set the maximum character limit for Span names (default 0 = no limit)" export:"true"` Jaeger *jaeger.Config `description:"Settings for jaeger"` Zipkin *zipkin.Config `description:"Settings for zipkin"` DataDog *datadog.Config `description:"Settings for DataDog"` + Instana *instana.Config `description:"Settings for Instana"` } // Providers contains providers configuration @@ -244,6 +246,10 @@ func (c *Configuration) initTracing() { log.Warn("DataDog configuration will be ignored") c.Tracing.DataDog = nil } + if c.Tracing.Instana != nil { + log.Warn("Instana configuration will be ignored") + c.Tracing.Instana = nil + } case zipkin.Name: if c.Tracing.Zipkin == nil { c.Tracing.Zipkin = &zipkin.Config{ @@ -262,6 +268,10 @@ func (c *Configuration) initTracing() { log.Warn("DataDog configuration will be ignored") c.Tracing.DataDog = nil } + if c.Tracing.Instana != nil { + log.Warn("Instana configuration will be ignored") + c.Tracing.Instana = nil + } case datadog.Name: if c.Tracing.DataDog == nil { c.Tracing.DataDog = &datadog.Config{ @@ -278,6 +288,30 @@ func (c *Configuration) initTracing() { log.Warn("Jaeger configuration will be ignored") c.Tracing.Jaeger = nil } + if c.Tracing.Instana != nil { + log.Warn("Instana configuration will be ignored") + c.Tracing.Instana = nil + } + case instana.Name: + if c.Tracing.Instana == nil { + c.Tracing.Instana = &instana.Config{ + LocalAgentHost: "localhost", + LocalAgentPort: 42699, + LogLevel: "info", + } + } + if c.Tracing.Zipkin != nil { + log.Warn("Zipkin configuration will be ignored") + c.Tracing.Zipkin = nil + } + if c.Tracing.Jaeger != nil { + log.Warn("Jaeger configuration will be ignored") + c.Tracing.Jaeger = nil + } + if c.Tracing.DataDog != nil { + log.Warn("DataDog configuration will be ignored") + c.Tracing.DataDog = nil + } default: log.Warnf("Unknown tracer %q", c.Tracing.Backend) return diff --git a/docs/configuration/tracing.md b/docs/configuration/tracing.md index 69e12ae77..beec684c2 100644 --- a/docs/configuration/tracing.md +++ b/docs/configuration/tracing.md @@ -4,7 +4,7 @@ The tracing system allows developers to visualize call flows in their infrastruc We use [OpenTracing](http://opentracing.io). It is an open standard designed for distributed tracing. -Traefik supports three tracing backends: Jaeger, Zipkin and DataDog. +Traefik supports four tracing backends: Jaeger, Zipkin, DataDog, and Instana. ## Jaeger @@ -188,3 +188,41 @@ Traefik supports three tracing backends: Jaeger, Zipkin and DataDog. # prioritySampling = false ``` + +## Instana + +```toml +# Tracing definition +[tracing] + # Backend name used to send tracing data + # + # Default: "jaeger" + # + backend = "instana" + # Service name used in Instana backend + # + # Default: "traefik" + # + serviceName = "traefik" + [tracing.instana] + # Local Agent Host instructs reporter to send spans to instana-agent at this address + # + # Default: "127.0.0.1" + # + localAgentHost = "127.0.0.1" + # Local Agent port instructs reporter to send spans to the instana-agent at this port + # + # Default: 42699 + # + localAgentPort = 42699 + # Set Instana tracer log level + # + # Default: info + # Valid values for logLevel field are: + # - error + # - warn + # - debug + # - info + # + logLevel = "info" +``` \ No newline at end of file diff --git a/server/server.go b/server/server.go index 33ceb36a9..1f8a30194 100644 --- a/server/server.go +++ b/server/server.go @@ -21,6 +21,7 @@ import ( "github.com/containous/traefik/server/middleware" "github.com/containous/traefik/tracing" "github.com/containous/traefik/tracing/datadog" + "github.com/containous/traefik/tracing/instana" "github.com/containous/traefik/tracing/jaeger" "github.com/containous/traefik/tracing/zipkin" "github.com/containous/traefik/types" @@ -60,6 +61,8 @@ func setupTracing(conf *static.Tracing) tracing.TrackingBackend { return conf.Zipkin case datadog.Name: return conf.DataDog + case instana.Name: + return conf.Instana default: log.WithoutContext().Warnf("Could not initialize tracing: unknown tracer %q", conf.Backend) return nil diff --git a/tracing/instana/instana.go b/tracing/instana/instana.go new file mode 100644 index 000000000..0a9376f41 --- /dev/null +++ b/tracing/instana/instana.go @@ -0,0 +1,49 @@ +package instana + +import ( + "io" + + "github.com/containous/traefik/log" + "github.com/instana/go-sensor" + "github.com/opentracing/opentracing-go" +) + +// Name sets the name of this tracer +const Name = "instana" + +// Config provides configuration settings for a instana tracer +type Config struct { + LocalAgentHost string `description:"Set instana-agent's host that the reporter will used." export:"false"` + LocalAgentPort int `description:"Set instana-agent's port that the reporter will used." export:"false"` + LogLevel string `description:"Set instana-agent's log level. ('error','warn','info','debug')" export:"false"` +} + +// Setup sets up the tracer +func (c *Config) Setup(serviceName string) (opentracing.Tracer, io.Closer, error) { + // set default logLevel + logLevel := instana.Info + + // check/set logLevel overrides + switch c.LogLevel { + case "error": + logLevel = instana.Error + case "warn": + logLevel = instana.Warn + case "debug": + logLevel = instana.Debug + } + + tracer := instana.NewTracerWithOptions(&instana.Options{ + Service: serviceName, + LogLevel: logLevel, + AgentPort: c.LocalAgentPort, + AgentHost: c.LocalAgentHost, + }) + + // Without this, child spans are getting the NOOP tracer + opentracing.SetGlobalTracer(tracer) + + log.WithoutContext().Debug("Instana tracer configured") + + return tracer, nil, nil +} diff --git a/vendor/github.com/instana/go-sensor/LICENSE.md b/vendor/github.com/instana/go-sensor/LICENSE.md new file mode 100644 index 000000000..b3e7a85f2 --- /dev/null +++ b/vendor/github.com/instana/go-sensor/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Instana + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/instana/go-sensor/agent.go b/vendor/github.com/instana/go-sensor/agent.go new file mode 100644 index 000000000..f2537b77a --- /dev/null +++ b/vendor/github.com/instana/go-sensor/agent.go @@ -0,0 +1,183 @@ +package instana + +import ( + "bytes" + "encoding/json" + "errors" + "io/ioutil" + "net/http" + "os" + "strconv" + "time" +) + +const ( + agentDiscoveryURL = "/com.instana.plugin.golang.discovery" + agentTracesURL = "/com.instana.plugin.golang/traces." + agentDataURL = "/com.instana.plugin.golang." + agentEventURL = "/com.instana.plugin.generic.event" + agentDefaultHost = "localhost" + agentDefaultPort = 42699 + agentHeader = "Instana Agent" +) + +type agentResponse struct { + Pid uint32 `json:"pid"` + HostID string `json:"agentUuid"` +} + +type discoveryS struct { + PID int `json:"pid"` + Name string `json:"name"` + Args []string `json:"args"` + Fd string `json:"fd"` + Inode string `json:"inode"` +} + +type fromS struct { + PID string `json:"e"` + HostID string `json:"h"` +} + +type agentS struct { + sensor *sensorS + fsm *fsmS + from *fromS + host string + client *http.Client +} + +func (r *agentS) init() { + r.client = &http.Client{Timeout: 5 * time.Second} + r.fsm = r.initFsm() + r.setFrom(&fromS{}) +} + +func (r *agentS) makeURL(prefix string) string { + return r.makeHostURL(r.host, prefix) +} + +func (r *agentS) makeHostURL(host string, prefix string) string { + envPort := os.Getenv("INSTANA_AGENT_PORT") + port := agentDefaultPort + if r.sensor.options.AgentPort > 0 { + return r.makeFullURL(host, r.sensor.options.AgentPort, prefix) + } + if envPort == "" { + return r.makeFullURL(host, port, prefix) + } + port, err := strconv.Atoi(envPort) + if err != nil { + return r.makeFullURL(host, agentDefaultPort, prefix) + } + return r.makeFullURL(host, port, prefix) +} + +func (r *agentS) makeFullURL(host string, port int, prefix string) string { + var buffer bytes.Buffer + + buffer.WriteString("http://") + buffer.WriteString(host) + buffer.WriteString(":") + buffer.WriteString(strconv.Itoa(port)) + buffer.WriteString(prefix) + if prefix[len(prefix)-1:] == "." && r.from.PID != "" { + buffer.WriteString(r.from.PID) + } + + return buffer.String() +} + +func (r *agentS) head(url string) (string, error) { + return r.request(url, "HEAD", nil) +} + +func (r *agentS) request(url string, method string, data interface{}) (string, error) { + return r.fullRequestResponse(url, method, data, nil, "") +} + +func (r *agentS) requestResponse(url string, method string, data interface{}, ret interface{}) (string, error) { + return r.fullRequestResponse(url, method, data, ret, "") +} + +func (r *agentS) requestHeader(url string, method string, header string) (string, error) { + return r.fullRequestResponse(url, method, nil, nil, header) +} + +func (r *agentS) fullRequestResponse(url string, method string, data interface{}, body interface{}, header string) (string, error) { + var j []byte + var ret string + var err error + var resp *http.Response + var req *http.Request + if data != nil { + j, err = json.Marshal(data) + } + + if err == nil { + if j != nil { + req, err = http.NewRequest(method, url, bytes.NewBuffer(j)) + } else { + req, err = http.NewRequest(method, url, nil) + } + + if err == nil { + req.Header.Set("Content-Type", "application/json") + resp, err = r.client.Do(req) + if err == nil { + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + err = errors.New(resp.Status) + } else { + + log.debug("agent response:", url, resp.Status) + + if body != nil { + var b []byte + b, err = ioutil.ReadAll(resp.Body) + json.Unmarshal(b, body) + } + + if header != "" { + ret = resp.Header.Get(header) + } + } + } + } + } + + if err != nil { + // Ignore errors while in announced stated (before ready) as + // this is the time where the entity is registering in the Instana + // backend and it will return 404 until it's done. + if !r.sensor.agent.fsm.fsm.Is("announced") { + log.info(err, url) + } + } + + return ret, err +} + +func (r *agentS) setFrom(from *fromS) { + r.from = from +} + +func (r *agentS) setHost(host string) { + r.host = host +} + +func (r *agentS) reset() { + r.fsm.reset() +} + +func (r *sensorS) initAgent() *agentS { + + log.debug("initializing agent") + + ret := new(agentS) + ret.sensor = r + ret.init() + + return ret +} diff --git a/vendor/github.com/instana/go-sensor/context.go b/vendor/github.com/instana/go-sensor/context.go new file mode 100644 index 000000000..c5b86f7dc --- /dev/null +++ b/vendor/github.com/instana/go-sensor/context.go @@ -0,0 +1,42 @@ +package instana + +// SpanContext holds the basic Span metadata. +type SpanContext struct { + // A probabilistically unique identifier for a [multi-span] trace. + TraceID int64 + + // A probabilistically unique identifier for a span. + SpanID int64 + + // Whether the trace is sampled. + Sampled bool + + // The span's associated baggage. + Baggage map[string]string // initialized on first use +} + +// ForeachBaggageItem belongs to the opentracing.SpanContext interface +func (c SpanContext) ForeachBaggageItem(handler func(k, v string) bool) { + for k, v := range c.Baggage { + if !handler(k, v) { + break + } + } +} + +// WithBaggageItem returns an entirely new SpanContext with the +// given key:value baggage pair set. +func (c SpanContext) WithBaggageItem(key, val string) SpanContext { + var newBaggage map[string]string + if c.Baggage == nil { + newBaggage = map[string]string{key: val} + } else { + newBaggage = make(map[string]string, len(c.Baggage)+1) + for k, v := range c.Baggage { + newBaggage[k] = v + } + newBaggage[key] = val + } + // Use positional parameters so the compiler will help catch new fields. + return SpanContext{c.TraceID, c.SpanID, c.Sampled, newBaggage} +} diff --git a/vendor/github.com/instana/go-sensor/eum.go b/vendor/github.com/instana/go-sensor/eum.go new file mode 100644 index 000000000..776b58a93 --- /dev/null +++ b/vendor/github.com/instana/go-sensor/eum.go @@ -0,0 +1,37 @@ +package instana + +import ( + "bytes" + "io/ioutil" + "strings" +) + +const eumTemplate string = "eum.js" + +// EumSnippet generates javascript code to initialize JavaScript agent +func EumSnippet(apiKey string, traceID string, meta map[string]string) string { + + if len(apiKey) == 0 || len(traceID) == 0 { + return "" + } + + b, err := ioutil.ReadFile(eumTemplate) + + if err != nil { + return "" + } + + var snippet = string(b) + var metaBuffer bytes.Buffer + + snippet = strings.Replace(snippet, "$apiKey", apiKey, -1) + snippet = strings.Replace(snippet, "$traceId", traceID, -1) + + for key, value := range meta { + metaBuffer.WriteString(" ineum('meta', '" + key + "', '" + value + "');\n") + } + + snippet = strings.Replace(snippet, "$meta", metaBuffer.String(), -1) + + return snippet +} diff --git a/vendor/github.com/instana/go-sensor/event.go b/vendor/github.com/instana/go-sensor/event.go new file mode 100644 index 000000000..a76084b77 --- /dev/null +++ b/vendor/github.com/instana/go-sensor/event.go @@ -0,0 +1,78 @@ +package instana + +import ( + "time" +) + +// EventData is the construct serialized for the host agent +type EventData struct { + Title string `json:"title"` + Text string `json:"text"` + // Duration in milliseconds + Duration int `json:"duration"` + // Severity with value of -1, 5, 10 : see type severity + Severity int `json:"severity"` + Plugin string `json:"plugin,omitempty"` + ID string `json:"id,omitempty"` + Host string `json:"host"` +} + +type severity int + +//Severity values for events sent to the instana agent +const ( + SeverityChange severity = -1 + SeverityWarning severity = 5 + SeverityCritical severity = 10 +) + +// Defaults for the Event API +const ( + ServicePlugin = "com.instana.forge.connection.http.logical.LogicalWebApp" + ServiceHost = "" +) + +// SendDefaultServiceEvent sends a default event which already contains the service and host +func SendDefaultServiceEvent(title string, text string, sev severity, duration time.Duration) { + if sensor == nil { + // Since no sensor was initialized, there is no default service (as + // configured on the sensor) so we send blank. + SendServiceEvent("", title, text, sev, duration) + } else { + SendServiceEvent(sensor.serviceName, title, text, sev, duration) + } +} + +// SendServiceEvent send an event on a specific service +func SendServiceEvent(service string, title string, text string, sev severity, duration time.Duration) { + sendEvent(&EventData{ + Title: title, + Text: text, + Severity: int(sev), + Plugin: ServicePlugin, + ID: service, + Host: ServiceHost, + Duration: int(duration / time.Millisecond), + }) +} + +// SendHostEvent send an event on the current host +func SendHostEvent(title string, text string, sev severity, duration time.Duration) { + sendEvent(&EventData{ + Title: title, + Text: text, + Duration: int(duration / time.Millisecond), + Severity: int(sev), + }) +} + +func sendEvent(event *EventData) { + if sensor == nil { + // If the sensor hasn't initialized we do so here so that we properly + // discover where the host agent may be as it varies between a + // normal host, docker, kubernetes etc.. + InitSensor(&Options{}) + } + //we do fire & forget here, because the whole pid dance isn't necessary to send events + go sensor.agent.request(sensor.agent.makeURL(agentEventURL), "POST", event) +} diff --git a/vendor/github.com/instana/go-sensor/fsm.go b/vendor/github.com/instana/go-sensor/fsm.go new file mode 100644 index 000000000..3225098da --- /dev/null +++ b/vendor/github.com/instana/go-sensor/fsm.go @@ -0,0 +1,242 @@ +package instana + +import ( + "bufio" + "fmt" + "net" + "os" + "os/exec" + "regexp" + "strconv" + "time" + + f "github.com/looplab/fsm" +) + +const ( + eInit = "init" + eLookup = "lookup" + eAnnounce = "announce" + eTest = "test" + + retryPeriod = 30 * 1000 + maximumRetries = 2 +) + +type fsmS struct { + agent *agentS + fsm *f.FSM + timer *time.Timer + retries int +} + +func (r *fsmS) init() { + + log.warn("Stan is on the scene. Starting Instana instrumentation.") + log.debug("initializing fsm") + + r.fsm = f.NewFSM( + "none", + f.Events{ + {Name: eInit, Src: []string{"none", "unannounced", "announced", "ready"}, Dst: "init"}, + {Name: eLookup, Src: []string{"init"}, Dst: "unannounced"}, + {Name: eAnnounce, Src: []string{"unannounced"}, Dst: "announced"}, + {Name: eTest, Src: []string{"announced"}, Dst: "ready"}}, + f.Callbacks{ + "init": r.lookupAgentHost, + "enter_unannounced": r.announceSensor, + "enter_announced": r.testAgent}) + + r.retries = maximumRetries + r.fsm.Event(eInit) +} + +func (r *fsmS) scheduleRetry(e *f.Event, cb func(e *f.Event)) { + r.timer = time.NewTimer(retryPeriod * time.Millisecond) + go func() { + <-r.timer.C + cb(e) + }() +} + +func (r *fsmS) lookupAgentHost(e *f.Event) { + cb := func(b bool, host string) { + if b { + r.lookupSuccess(host) + } else { + gateway := r.getDefaultGateway() + if gateway != "" { + go r.checkHost(gateway, func(b bool, host string) { + if b { + r.lookupSuccess(host) + } else { + log.error("Cannot connect to the agent through localhost or default gateway. Scheduling retry.") + r.scheduleRetry(e, r.lookupAgentHost) + } + }) + } else { + log.error("Default gateway not available. Scheduling retry") + r.scheduleRetry(e, r.lookupAgentHost) + } + } + } + hostNames := []string{ + r.agent.sensor.options.AgentHost, + os.Getenv("INSTANA_AGENT_HOST"), + agentDefaultHost, + } + for _, name := range hostNames { + if name != "" { + go r.checkHost(name, cb) + return + } + } +} + +func (r *fsmS) getDefaultGateway() string { + out, _ := exec.Command("/bin/sh", "-c", "/sbin/ip route | awk '/default/' | cut -d ' ' -f 3 | tr -d '\n'").Output() + + log.debug("checking default gateway", string(out[:])) + + return string(out[:]) +} + +func (r *fsmS) checkHost(host string, cb func(b bool, host string)) { + log.debug("checking host", host) + + header, err := r.agent.requestHeader(r.agent.makeHostURL(host, "/"), "GET", "Server") + + cb(err == nil && header == agentHeader, host) +} + +func (r *fsmS) lookupSuccess(host string) { + log.debug("agent lookup success", host) + + r.agent.setHost(host) + r.retries = maximumRetries + r.fsm.Event(eLookup) +} + +func (r *fsmS) announceSensor(e *f.Event) { + cb := func(b bool, from *fromS) { + if b { + log.info("Host agent available. We're in business. Announced pid:", from.PID) + r.agent.setFrom(from) + r.retries = maximumRetries + r.fsm.Event(eAnnounce) + } else { + log.error("Cannot announce sensor. Scheduling retry.") + r.retries-- + if r.retries > 0 { + r.scheduleRetry(e, r.announceSensor) + } else { + r.fsm.Event(eInit) + } + } + } + + log.debug("announcing sensor to the agent") + + go func(cb func(b bool, from *fromS)) { + defer func() { + if r := recover(); r != nil { + log.debug("Announce recovered:", r) + } + }() + + pid := 0 + schedFile := fmt.Sprintf("/proc/%d/sched", os.Getpid()) + if _, err := os.Stat(schedFile); err == nil { + sf, err := os.Open(schedFile) + defer sf.Close() + if err == nil { + fscanner := bufio.NewScanner(sf) + fscanner.Scan() + primaLinea := fscanner.Text() + + r := regexp.MustCompile("\\((\\d+),") + match := r.FindStringSubmatch(primaLinea) + i, err := strconv.Atoi(match[1]) + if err == nil { + pid = i + } + } + } + + if pid == 0 { + pid = os.Getpid() + } + + d := &discoveryS{PID: pid} + d.Name, d.Args = getCommandLine() + + if _, err := os.Stat("/proc"); err == nil { + if addr, err := net.ResolveTCPAddr("tcp", r.agent.host+":42699"); err == nil { + if tcpConn, err := net.DialTCP("tcp", nil, addr); err == nil { + defer tcpConn.Close() + + f, err := tcpConn.File() + + if err != nil { + log.error(err) + } else { + d.Fd = fmt.Sprintf("%v", f.Fd()) + + link := fmt.Sprintf("/proc/%d/fd/%d", os.Getpid(), f.Fd()) + if _, err := os.Stat(link); err == nil { + d.Inode, _ = os.Readlink(link) + } + } + } + } + } + + ret := &agentResponse{} + _, err := r.agent.requestResponse(r.agent.makeURL(agentDiscoveryURL), "PUT", d, ret) + cb(err == nil, + &fromS{ + PID: strconv.Itoa(int(ret.Pid)), + HostID: ret.HostID}) + }(cb) +} + +func (r *fsmS) testAgent(e *f.Event) { + cb := func(b bool) { + if b { + r.retries = maximumRetries + r.fsm.Event(eTest) + } else { + log.debug("Agent is not yet ready. Scheduling retry.") + r.retries-- + if r.retries > 0 { + r.scheduleRetry(e, r.testAgent) + } else { + r.fsm.Event(eInit) + } + } + } + + log.debug("testing communication with the agent") + + go func(cb func(b bool)) { + _, err := r.agent.head(r.agent.makeURL(agentDataURL)) + cb(err == nil) + }(cb) +} + +func (r *fsmS) reset() { + r.retries = maximumRetries + r.fsm.Event(eInit) +} + +func (r *agentS) initFsm() *fsmS { + ret := new(fsmS) + ret.agent = r + ret.init() + + return ret +} + +func (r *agentS) canSend() bool { + return r.fsm.fsm.Current() == "ready" +} diff --git a/vendor/github.com/instana/go-sensor/json_span.go b/vendor/github.com/instana/go-sensor/json_span.go new file mode 100644 index 000000000..a39938329 --- /dev/null +++ b/vendor/github.com/instana/go-sensor/json_span.go @@ -0,0 +1,38 @@ +package instana + +import ( + ot "github.com/opentracing/opentracing-go" +) + +type jsonSpan struct { + TraceID int64 `json:"t"` + ParentID *int64 `json:"p,omitempty"` + SpanID int64 `json:"s"` + Timestamp uint64 `json:"ts"` + Duration uint64 `json:"d"` + Name string `json:"n"` + From *fromS `json:"f"` + Error bool `json:"error"` + Ec int `json:"ec,omitempty"` + Lang string `json:"ta,omitempty"` + Data *jsonData `json:"data"` +} + +type jsonData struct { + Service string `json:"service,omitempty"` + SDK *jsonSDKData `json:"sdk"` +} + +type jsonCustomData struct { + Tags ot.Tags `json:"tags,omitempty"` + Logs map[uint64]map[string]interface{} `json:"logs,omitempty"` + Baggage map[string]string `json:"baggage,omitempty"` +} + +type jsonSDKData struct { + Name string `json:"name"` + Type string `json:"type,omitempty"` + Arguments string `json:"arguments,omitempty"` + Return string `json:"return,omitempty"` + Custom *jsonCustomData `json:"custom,omitempty"` +} diff --git a/vendor/github.com/instana/go-sensor/log.go b/vendor/github.com/instana/go-sensor/log.go new file mode 100644 index 000000000..f126eeb2b --- /dev/null +++ b/vendor/github.com/instana/go-sensor/log.go @@ -0,0 +1,52 @@ +package instana + +import ( + l "log" +) + +// Valid log levels +const ( + Error = 0 + Warn = 1 + Info = 2 + Debug = 3 +) + +type logS struct { + sensor *sensorS +} + +var log *logS + +func (r *logS) makeV(prefix string, v ...interface{}) []interface{} { + return append([]interface{}{prefix}, v...) +} + +func (r *logS) debug(v ...interface{}) { + if r.sensor.options.LogLevel >= Debug { + l.Println(r.makeV("DEBUG: instana:", v...)...) + } +} + +func (r *logS) info(v ...interface{}) { + if r.sensor.options.LogLevel >= Info { + l.Println(r.makeV("INFO: instana:", v...)...) + } +} + +func (r *logS) warn(v ...interface{}) { + if r.sensor.options.LogLevel >= Warn { + l.Println(r.makeV("WARN: instana:", v...)...) + } +} + +func (r *logS) error(v ...interface{}) { + if r.sensor.options.LogLevel >= Error { + l.Println(r.makeV("ERROR: instana:", v...)...) + } +} + +func (r *sensorS) initLog() { + log = new(logS) + log.sensor = r +} diff --git a/vendor/github.com/instana/go-sensor/meter.go b/vendor/github.com/instana/go-sensor/meter.go new file mode 100644 index 000000000..4452cabfc --- /dev/null +++ b/vendor/github.com/instana/go-sensor/meter.go @@ -0,0 +1,157 @@ +package instana + +import ( + "runtime" + "strconv" + "time" +) + +const ( + // SnapshotPeriod is the amount of time in seconds between snapshot reports. + SnapshotPeriod = 600 +) + +// SnapshotS struct to hold snapshot data. +type SnapshotS struct { + Name string `json:"name"` + Version string `json:"version"` + Root string `json:"goroot"` + MaxProcs int `json:"maxprocs"` + Compiler string `json:"compiler"` + NumCPU int `json:"cpu"` +} + +// MemoryS struct to hold snapshot data. +type MemoryS struct { + Alloc uint64 `json:"alloc"` + TotalAlloc uint64 `json:"total_alloc"` + Sys uint64 `json:"sys"` + Lookups uint64 `json:"lookups"` + Mallocs uint64 `json:"mallocs"` + Frees uint64 `json:"frees"` + HeapAlloc uint64 `json:"heap_alloc"` + HeapSys uint64 `json:"heap_sys"` + HeapIdle uint64 `json:"heap_idle"` + HeapInuse uint64 `json:"heap_in_use"` + HeapReleased uint64 `json:"heap_released"` + HeapObjects uint64 `json:"heap_objects"` + PauseTotalNs uint64 `json:"pause_total_ns"` + PauseNs uint64 `json:"pause_ns"` + NumGC uint32 `json:"num_gc"` + GCCPUFraction float64 `json:"gc_cpu_fraction"` +} + +// MetricsS struct to hold snapshot data. +type MetricsS struct { + CgoCall int64 `json:"cgo_call"` + Goroutine int `json:"goroutine"` + Memory *MemoryS `json:"memory"` +} + +// EntityData struct to hold snapshot data. +type EntityData struct { + PID int `json:"pid"` + Snapshot *SnapshotS `json:"snapshot,omitempty"` + Metrics *MetricsS `json:"metrics"` +} + +type meterS struct { + sensor *sensorS + numGC uint32 + ticker *time.Ticker + snapshotCountdown int +} + +func (r *meterS) init() { + r.ticker = time.NewTicker(1 * time.Second) + go func() { + r.snapshotCountdown = 1 + for range r.ticker.C { + if r.sensor.agent.canSend() { + r.snapshotCountdown-- + var s *SnapshotS + if r.snapshotCountdown == 0 { + r.snapshotCountdown = SnapshotPeriod + s = r.collectSnapshot() + log.debug("collected snapshot") + } else { + s = nil + } + + pid, _ := strconv.Atoi(r.sensor.agent.from.PID) + d := &EntityData{ + PID: pid, + Snapshot: s, + Metrics: r.collectMetrics()} + + go r.send(d) + } + } + }() +} + +func (r *meterS) send(d *EntityData) { + _, err := r.sensor.agent.request(r.sensor.agent.makeURL(agentDataURL), "POST", d) + + if err != nil { + r.sensor.agent.reset() + } +} + +func (r *meterS) collectMemoryMetrics() *MemoryS { + var memStats runtime.MemStats + runtime.ReadMemStats(&memStats) + ret := &MemoryS{ + Alloc: memStats.Alloc, + TotalAlloc: memStats.TotalAlloc, + Sys: memStats.Sys, + Lookups: memStats.Lookups, + Mallocs: memStats.Mallocs, + Frees: memStats.Frees, + HeapAlloc: memStats.HeapAlloc, + HeapSys: memStats.HeapSys, + HeapIdle: memStats.HeapIdle, + HeapInuse: memStats.HeapInuse, + HeapReleased: memStats.HeapReleased, + HeapObjects: memStats.HeapObjects, + PauseTotalNs: memStats.PauseTotalNs, + NumGC: memStats.NumGC, + GCCPUFraction: memStats.GCCPUFraction} + + if r.numGC < memStats.NumGC { + ret.PauseNs = memStats.PauseNs[(memStats.NumGC+255)%256] + r.numGC = memStats.NumGC + } else { + ret.PauseNs = 0 + } + + return ret +} + +func (r *meterS) collectMetrics() *MetricsS { + return &MetricsS{ + CgoCall: runtime.NumCgoCall(), + Goroutine: runtime.NumGoroutine(), + Memory: r.collectMemoryMetrics()} +} + +func (r *meterS) collectSnapshot() *SnapshotS { + return &SnapshotS{ + Name: r.sensor.serviceName, + Version: runtime.Version(), + Root: runtime.GOROOT(), + MaxProcs: runtime.GOMAXPROCS(0), + Compiler: runtime.Compiler, + NumCPU: runtime.NumCPU()} +} + +func (r *sensorS) initMeter() *meterS { + + log.debug("initializing meter") + + ret := new(meterS) + ret.sensor = r + ret.init() + + return ret +} diff --git a/vendor/github.com/instana/go-sensor/options.go b/vendor/github.com/instana/go-sensor/options.go new file mode 100644 index 000000000..dccac24da --- /dev/null +++ b/vendor/github.com/instana/go-sensor/options.go @@ -0,0 +1,12 @@ +package instana + +// Options allows the user to configure the to-be-initialized +// sensor +type Options struct { + Service string + AgentHost string + AgentPort int + MaxBufferedSpans int + ForceTransmissionStartingAt int + LogLevel int +} diff --git a/vendor/github.com/instana/go-sensor/propagation.go b/vendor/github.com/instana/go-sensor/propagation.go new file mode 100644 index 000000000..8470369ac --- /dev/null +++ b/vendor/github.com/instana/go-sensor/propagation.go @@ -0,0 +1,165 @@ +package instana + +import ( + "net/http" + "strconv" + "strings" + + ot "github.com/opentracing/opentracing-go" +) + +type textMapPropagator struct { + tracer *tracerS +} + +// Instana header constants +const ( + // FieldT Trace ID header + FieldT = "x-instana-t" + // FieldS Span ID header + FieldS = "x-instana-s" + // FieldL Level header + FieldL = "x-instana-l" + // FieldB OT Baggage header + FieldB = "x-instana-b-" + fieldCount = 2 +) + +func (r *textMapPropagator) inject(spanContext ot.SpanContext, opaqueCarrier interface{}) error { + sc, ok := spanContext.(SpanContext) + if !ok { + return ot.ErrInvalidSpanContext + } + + roCarrier, ok := opaqueCarrier.(ot.TextMapReader) + if !ok { + return ot.ErrInvalidCarrier + } + + // Handle pre-existing case-sensitive keys + var ( + exstfieldT = FieldT + exstfieldS = FieldS + exstfieldL = FieldL + exstfieldB = FieldB + ) + + roCarrier.ForeachKey(func(k, v string) error { + switch strings.ToLower(k) { + case FieldT: + exstfieldT = k + case FieldS: + exstfieldS = k + case FieldL: + exstfieldL = k + default: + if strings.HasPrefix(strings.ToLower(k), FieldB) { + exstfieldB = string([]rune(k)[0:len(FieldB)]) + } + } + return nil + }) + + carrier, ok := opaqueCarrier.(ot.TextMapWriter) + if !ok { + return ot.ErrInvalidCarrier + } + + hhcarrier, ok := opaqueCarrier.(ot.HTTPHeadersCarrier) + if ok { + // If http.Headers has pre-existing keys, calling Set() like we do + // below will just append to those existing values and break context + // propagation. So defend against that case, we delete any pre-existing + // keys entirely first. + y := http.Header(hhcarrier) + y.Del(exstfieldT) + y.Del(exstfieldS) + y.Del(exstfieldL) + + for key := range y { + if strings.HasPrefix(strings.ToLower(key), FieldB) { + y.Del(key) + } + } + } + + if instanaTID, err := ID2Header(sc.TraceID); err == nil { + carrier.Set(exstfieldT, instanaTID) + } else { + log.debug(err) + } + if instanaSID, err := ID2Header(sc.SpanID); err == nil { + carrier.Set(exstfieldS, instanaSID) + } else { + log.debug(err) + } + carrier.Set(exstfieldL, strconv.Itoa(1)) + + for k, v := range sc.Baggage { + carrier.Set(exstfieldB+k, v) + } + return nil +} + +func (r *textMapPropagator) extract(opaqueCarrier interface{}) (ot.SpanContext, error) { + carrier, ok := opaqueCarrier.(ot.TextMapReader) + if !ok { + return nil, ot.ErrInvalidCarrier + } + + fieldCount := 0 + var traceID, spanID int64 + var err error + baggage := make(map[string]string) + err = carrier.ForeachKey(func(k, v string) error { + switch strings.ToLower(k) { + case FieldT: + fieldCount++ + traceID, err = Header2ID(v) + if err != nil { + return ot.ErrSpanContextCorrupted + } + case FieldS: + fieldCount++ + spanID, err = Header2ID(v) + if err != nil { + return ot.ErrSpanContextCorrupted + } + default: + lk := strings.ToLower(k) + + if strings.HasPrefix(lk, FieldB) { + baggage[strings.TrimPrefix(lk, FieldB)] = v + } + } + + return nil + }) + + return r.finishExtract(err, fieldCount, traceID, spanID, baggage) +} + +func (r *textMapPropagator) finishExtract(err error, + fieldCount int, + traceID int64, + spanID int64, + baggage map[string]string) (ot.SpanContext, error) { + if err != nil { + return nil, err + } + + if fieldCount < 2 { + if fieldCount == 0 { + return nil, ot.ErrSpanContextNotFound + } + + return nil, ot.ErrSpanContextCorrupted + } + + return SpanContext{ + TraceID: traceID, + SpanID: spanID, + Sampled: false, + Baggage: baggage, + }, nil +} diff --git a/vendor/github.com/instana/go-sensor/recorder.go b/vendor/github.com/instana/go-sensor/recorder.go new file mode 100644 index 000000000..56ba8cff5 --- /dev/null +++ b/vendor/github.com/instana/go-sensor/recorder.go @@ -0,0 +1,175 @@ +package instana + +import ( + "sync" + "time" +) + +// A SpanRecorder handles all of the `RawSpan` data generated via an +// associated `Tracer` (see `NewStandardTracer`) instance. It also names +// the containing process and provides access to a straightforward tag map. +type SpanRecorder interface { + // Implementations must determine whether and where to store `span`. + RecordSpan(span *spanS) +} + +// Recorder accepts spans, processes and queues them +// for delivery to the backend. +type Recorder struct { + sync.RWMutex + spans []jsonSpan + testMode bool +} + +// NewRecorder Establish a Recorder span recorder +func NewRecorder() *Recorder { + r := new(Recorder) + r.init() + return r +} + +// NewTestRecorder Establish a new span recorder used for testing +func NewTestRecorder() *Recorder { + r := new(Recorder) + r.testMode = true + r.init() + return r +} + +func (r *Recorder) init() { + r.clearQueuedSpans() + + if r.testMode { + return + } + + ticker := time.NewTicker(1 * time.Second) + go func() { + for range ticker.C { + if sensor.agent.canSend() { + r.send() + } + } + }() +} + +// RecordSpan accepts spans to be recorded and and added to the span queue +// for eventual reporting to the host agent. +func (r *Recorder) RecordSpan(span *spanS) { + // If we're not announced and not in test mode then just + // return + if !r.testMode && !sensor.agent.canSend() { + return + } + + var data = &jsonData{} + kind := span.getSpanKind() + + data.SDK = &jsonSDKData{ + Name: span.Operation, + Type: kind, + Custom: &jsonCustomData{Tags: span.Tags, Logs: span.collectLogs()}} + + baggage := make(map[string]string) + span.context.ForeachBaggageItem(func(k string, v string) bool { + baggage[k] = v + + return true + }) + + if len(baggage) > 0 { + data.SDK.Custom.Baggage = baggage + } + + data.Service = sensor.serviceName + + var parentID *int64 + if span.ParentSpanID == 0 { + parentID = nil + } else { + parentID = &span.ParentSpanID + } + + r.Lock() + defer r.Unlock() + + if len(r.spans) == sensor.options.MaxBufferedSpans { + r.spans = r.spans[1:] + } + + r.spans = append(r.spans, jsonSpan{ + TraceID: span.context.TraceID, + ParentID: parentID, + SpanID: span.context.SpanID, + Timestamp: uint64(span.Start.UnixNano()) / uint64(time.Millisecond), + Duration: uint64(span.Duration) / uint64(time.Millisecond), + Name: "sdk", + Error: span.Error, + Ec: span.Ec, + Lang: "go", + From: sensor.agent.from, + Data: data}) + + if r.testMode || !sensor.agent.canSend() { + return + } + + if len(r.spans) >= sensor.options.ForceTransmissionStartingAt { + log.debug("Forcing spans to agent. Count:", len(r.spans)) + go r.send() + } +} + +// QueuedSpansCount returns the number of queued spans +// Used only in tests currently. +func (r *Recorder) QueuedSpansCount() int { + r.RLock() + defer r.RUnlock() + return len(r.spans) +} + +// GetQueuedSpans returns a copy of the queued spans and clears the queue. +func (r *Recorder) GetQueuedSpans() []jsonSpan { + r.Lock() + defer r.Unlock() + + // Copy queued spans + queuedSpans := make([]jsonSpan, len(r.spans)) + copy(queuedSpans, r.spans) + + // and clear out the source + r.clearQueuedSpans() + return queuedSpans +} + +// clearQueuedSpans brings the span queue to empty/0/nada +// This function doesn't take the Lock so make sure to have +// the write lock before calling. +// This is meant to be called from GetQueuedSpans which handles +// locking. +func (r *Recorder) clearQueuedSpans() { + var mbs int + + if len(r.spans) > 0 { + if sensor != nil { + mbs = sensor.options.MaxBufferedSpans + } else { + mbs = DefaultMaxBufferedSpans + } + r.spans = make([]jsonSpan, 0, mbs) + } +} + +// Retrieve the queued spans and post them to the host agent asynchronously. +func (r *Recorder) send() { + spansToSend := r.GetQueuedSpans() + if len(spansToSend) > 0 { + go func() { + _, err := sensor.agent.request(sensor.agent.makeURL(agentTracesURL), "POST", spansToSend) + if err != nil { + log.debug("Posting traces failed in send(): ", err) + sensor.agent.reset() + } + }() + } +} diff --git a/vendor/github.com/instana/go-sensor/sensor.go b/vendor/github.com/instana/go-sensor/sensor.go new file mode 100644 index 000000000..d902950ad --- /dev/null +++ b/vendor/github.com/instana/go-sensor/sensor.go @@ -0,0 +1,76 @@ +package instana + +import ( + "os" + "path/filepath" +) + +const ( + DefaultMaxBufferedSpans = 1000 + DefaultForceSpanSendAt = 500 +) + +type sensorS struct { + meter *meterS + agent *agentS + options *Options + serviceName string +} + +var sensor *sensorS + +func (r *sensorS) init(options *Options) { + //sensor can be initialized explicit or implicit through OpenTracing global init + if r.meter == nil { + r.setOptions(options) + r.configureServiceName() + r.agent = r.initAgent() + r.meter = r.initMeter() + } +} + +func (r *sensorS) setOptions(options *Options) { + r.options = options + if r.options == nil { + r.options = &Options{} + } + + if r.options.MaxBufferedSpans == 0 { + r.options.MaxBufferedSpans = DefaultMaxBufferedSpans + } + + if r.options.ForceTransmissionStartingAt == 0 { + r.options.ForceTransmissionStartingAt = DefaultForceSpanSendAt + } +} + +func (r *sensorS) getOptions() *Options { + return r.options +} + +func (r *sensorS) configureServiceName() { + if r.options != nil { + r.serviceName = r.options.Service + } + + if r.serviceName == "" { + r.serviceName = filepath.Base(os.Args[0]) + } +} + +// InitSensor Intializes the sensor (without tracing) to begin collecting +// and reporting metrics. +func InitSensor(options *Options) { + if sensor == nil { + sensor = new(sensorS) + // If this environment variable is set, then override log level + _, ok := os.LookupEnv("INSTANA_DEV") + if ok { + options.LogLevel = Debug + } + + sensor.initLog() + sensor.init(options) + log.debug("initialized sensor") + } +} diff --git a/vendor/github.com/instana/go-sensor/span.go b/vendor/github.com/instana/go-sensor/span.go new file mode 100644 index 000000000..9fb991f7f --- /dev/null +++ b/vendor/github.com/instana/go-sensor/span.go @@ -0,0 +1,253 @@ +package instana + +import ( + "fmt" + "os" + "sync" + "time" + + ot "github.com/opentracing/opentracing-go" + "github.com/opentracing/opentracing-go/ext" + otlog "github.com/opentracing/opentracing-go/log" +) + +type spanS struct { + tracer *tracerS + sync.Mutex + + context SpanContext + ParentSpanID int64 + Operation string + Start time.Time + Duration time.Duration + Tags ot.Tags + Logs []ot.LogRecord + Error bool + Ec int +} + +func (r *spanS) BaggageItem(key string) string { + r.Lock() + defer r.Unlock() + + return r.context.Baggage[key] +} + +func (r *spanS) SetBaggageItem(key, val string) ot.Span { + if r.trim() { + return r + } + + r.Lock() + defer r.Unlock() + r.context = r.context.WithBaggageItem(key, val) + + return r +} + +func (r *spanS) Context() ot.SpanContext { + return r.context +} + +func (r *spanS) Finish() { + r.FinishWithOptions(ot.FinishOptions{}) +} + +func (r *spanS) FinishWithOptions(opts ot.FinishOptions) { + finishTime := opts.FinishTime + if finishTime.IsZero() { + finishTime = time.Now() + } + + duration := finishTime.Sub(r.Start) + r.Lock() + defer r.Unlock() + for _, lr := range opts.LogRecords { + r.appendLog(lr) + } + + for _, ld := range opts.BulkLogData { + r.appendLog(ld.ToLogRecord()) + } + + r.Duration = duration + r.tracer.options.Recorder.RecordSpan(r) +} + +func (r *spanS) appendLog(lr ot.LogRecord) { + maxLogs := r.tracer.options.MaxLogsPerSpan + if maxLogs == 0 || len(r.Logs) < maxLogs { + r.Logs = append(r.Logs, lr) + } +} + +func (r *spanS) Log(ld ot.LogData) { + r.Lock() + defer r.Unlock() + if r.trim() || r.tracer.options.DropAllLogs { + return + } + + if ld.Timestamp.IsZero() { + ld.Timestamp = time.Now() + } + + r.appendLog(ld.ToLogRecord()) +} + +func (r *spanS) trim() bool { + return !r.context.Sampled && r.tracer.options.TrimUnsampledSpans +} + +func (r *spanS) LogEvent(event string) { + r.Log(ot.LogData{ + Event: event}) +} + +func (r *spanS) LogEventWithPayload(event string, payload interface{}) { + r.Log(ot.LogData{ + Event: event, + Payload: payload}) +} + +func (r *spanS) LogFields(fields ...otlog.Field) { + + for _, v := range fields { + // If this tag indicates an error, increase the error count + if v.Key() == "error" { + r.Error = true + r.Ec++ + } + } + + lr := ot.LogRecord{ + Fields: fields, + } + + r.Lock() + defer r.Unlock() + if r.trim() || r.tracer.options.DropAllLogs { + return + } + + if lr.Timestamp.IsZero() { + lr.Timestamp = time.Now() + } + + r.appendLog(lr) +} + +func (r *spanS) LogKV(keyValues ...interface{}) { + fields, err := otlog.InterleavedKVToFields(keyValues...) + if err != nil { + r.LogFields(otlog.Error(err), otlog.String("function", "LogKV")) + + return + } + + r.LogFields(fields...) +} + +func (r *spanS) SetOperationName(operationName string) ot.Span { + r.Lock() + defer r.Unlock() + r.Operation = operationName + + return r +} + +func (r *spanS) SetTag(key string, value interface{}) ot.Span { + r.Lock() + defer r.Unlock() + if r.trim() { + return r + } + + if r.Tags == nil { + r.Tags = ot.Tags{} + } + + // If this tag indicates an error, increase the error count + if key == "error" { + r.Error = true + r.Ec++ + } + + r.Tags[key] = value + + return r +} + +func (r *spanS) Tracer() ot.Tracer { + return r.tracer +} + +func (r *spanS) getTag(tag string) interface{} { + var x, ok = r.Tags[tag] + if !ok { + x = "" + } + return x +} + +func (r *spanS) getIntTag(tag string) int { + d := r.Tags[tag] + if d == nil { + return -1 + } + + x, ok := d.(int) + if !ok { + return -1 + } + + return x +} + +func (r *spanS) getStringTag(tag string) string { + d := r.Tags[tag] + if d == nil { + return "" + } + return fmt.Sprint(d) +} + +func (r *spanS) getHostName() string { + hostTag := r.getStringTag(string(ext.PeerHostname)) + if hostTag != "" { + return hostTag + } + + h, err := os.Hostname() + if err != nil { + h = "localhost" + } + return h +} + +func (r *spanS) getSpanKind() string { + kind := r.getStringTag(string(ext.SpanKind)) + + switch kind { + case string(ext.SpanKindRPCServerEnum), "consumer", "entry": + return "entry" + case string(ext.SpanKindRPCClientEnum), "producer", "exit": + return "exit" + } + return "" +} + +func (r *spanS) collectLogs() map[uint64]map[string]interface{} { + logs := make(map[uint64]map[string]interface{}) + for _, l := range r.Logs { + if _, ok := logs[uint64(l.Timestamp.UnixNano())/uint64(time.Millisecond)]; !ok { + logs[uint64(l.Timestamp.UnixNano())/uint64(time.Millisecond)] = make(map[string]interface{}) + } + + for _, f := range l.Fields { + logs[uint64(l.Timestamp.UnixNano())/uint64(time.Millisecond)][f.Key()] = f.Value() + } + } + + return logs +} diff --git a/vendor/github.com/instana/go-sensor/tracer.go b/vendor/github.com/instana/go-sensor/tracer.go new file mode 100644 index 000000000..bcfca3ffb --- /dev/null +++ b/vendor/github.com/instana/go-sensor/tracer.go @@ -0,0 +1,119 @@ +package instana + +import ( + "time" + + ot "github.com/opentracing/opentracing-go" +) + +const ( + // MaxLogsPerSpan The maximum number of logs allowed on a span. + MaxLogsPerSpan = 2 +) + +type tracerS struct { + options TracerOptions + textPropagator *textMapPropagator +} + +func (r *tracerS) Inject(sc ot.SpanContext, format interface{}, carrier interface{}) error { + switch format { + case ot.TextMap, ot.HTTPHeaders: + return r.textPropagator.inject(sc, carrier) + } + + return ot.ErrUnsupportedFormat +} + +func (r *tracerS) Extract(format interface{}, carrier interface{}) (ot.SpanContext, error) { + switch format { + case ot.TextMap, ot.HTTPHeaders: + return r.textPropagator.extract(carrier) + } + + return nil, ot.ErrUnsupportedFormat +} + +func (r *tracerS) StartSpan(operationName string, opts ...ot.StartSpanOption) ot.Span { + sso := ot.StartSpanOptions{} + for _, o := range opts { + o.Apply(&sso) + } + + return r.StartSpanWithOptions(operationName, sso) +} + +func (r *tracerS) StartSpanWithOptions(operationName string, opts ot.StartSpanOptions) ot.Span { + startTime := opts.StartTime + if startTime.IsZero() { + startTime = time.Now() + } + + tags := opts.Tags + span := &spanS{} +Loop: + for _, ref := range opts.References { + switch ref.Type { + case ot.ChildOfRef, ot.FollowsFromRef: + refCtx := ref.ReferencedContext.(SpanContext) + span.context.TraceID = refCtx.TraceID + span.context.SpanID = randomID() + span.context.Sampled = refCtx.Sampled + span.ParentSpanID = refCtx.SpanID + if l := len(refCtx.Baggage); l > 0 { + span.context.Baggage = make(map[string]string, l) + for k, v := range refCtx.Baggage { + span.context.Baggage[k] = v + } + } + + break Loop + } + } + + if span.context.TraceID == 0 { + span.context.SpanID = randomID() + span.context.TraceID = span.context.SpanID + span.context.Sampled = r.options.ShouldSample(span.context.TraceID) + } + + return r.startSpanInternal(span, operationName, startTime, tags) +} + +func (r *tracerS) startSpanInternal(span *spanS, operationName string, startTime time.Time, tags ot.Tags) ot.Span { + span.tracer = r + span.Operation = operationName + span.Start = startTime + span.Duration = -1 + span.Tags = tags + + return span +} + +func shouldSample(traceID int64) bool { + return false +} + +// NewTracer Get a new Tracer with the default options applied. +func NewTracer() ot.Tracer { + return NewTracerWithOptions(&Options{}) +} + +// NewTracerWithOptions Get a new Tracer with the specified options. +func NewTracerWithOptions(options *Options) ot.Tracer { + InitSensor(options) + + return NewTracerWithEverything(options, NewRecorder()) +} + +// NewTracerWithEverything Get a new Tracer with the works. +func NewTracerWithEverything(options *Options, recorder SpanRecorder) ot.Tracer { + InitSensor(options) + ret := &tracerS{options: TracerOptions{ + Recorder: recorder, + ShouldSample: shouldSample, + MaxLogsPerSpan: MaxLogsPerSpan}} + ret.textPropagator = &textMapPropagator{ret} + + return ret +} diff --git a/vendor/github.com/instana/go-sensor/tracer_options.go b/vendor/github.com/instana/go-sensor/tracer_options.go new file mode 100644 index 000000000..2c4c29edd --- /dev/null +++ b/vendor/github.com/instana/go-sensor/tracer_options.go @@ -0,0 +1,91 @@ +package instana + +import ( + bt "github.com/opentracing/basictracer-go" + opentracing "github.com/opentracing/opentracing-go" +) + +// Tracer extends the opentracing.Tracer interface +type Tracer interface { + opentracing.Tracer + + // Options gets the Options used in New() or NewWithOptions(). + Options() TracerOptions +} + +// TracerOptions allows creating a customized Tracer via NewWithOptions. The object +// must not be updated when there is an active tracer using it. +type TracerOptions struct { + // ShouldSample is a function which is called when creating a new Span and + // determines whether that Span is sampled. The randomized TraceID is supplied + // to allow deterministic sampling decisions to be made across different nodes. + // For example, + // + // func(traceID uint64) { return traceID % 64 == 0 } + // + // samples every 64th trace on average. + ShouldSample func(traceID int64) bool + // TrimUnsampledSpans turns potentially expensive operations on unsampled + // Spans into no-ops. More precisely, tags and log events are silently + // discarded. If NewSpanEventListener is set, the callbacks will still fire. + TrimUnsampledSpans bool + // Recorder receives Spans which have been finished. + Recorder SpanRecorder + // NewSpanEventListener can be used to enhance the tracer by effectively + // attaching external code to trace events. See NetTraceIntegrator for a + // practical example, and event.go for the list of possible events. + NewSpanEventListener func() func(bt.SpanEvent) + // DropAllLogs turns log events on all Spans into no-ops. + // If NewSpanEventListener is set, the callbacks will still fire. + DropAllLogs bool + // MaxLogsPerSpan limits the number of Logs in a span (if set to a nonzero + // value). If a span has more logs than this value, logs are dropped as + // necessary (and replaced with a log describing how many were dropped). + // + // About half of the MaxLogPerSpan logs kept are the oldest logs, and about + // half are the newest logs. + // + // If NewSpanEventListener is set, the callbacks will still fire for all log + // events. This value is ignored if DropAllLogs is true. + MaxLogsPerSpan int + // DebugAssertSingleGoroutine internally records the ID of the goroutine + // creating each Span and verifies that no operation is carried out on + // it on a different goroutine. + // Provided strictly for development purposes. + // Passing Spans between goroutine without proper synchronization often + // results in use-after-Finish() errors. For a simple example, consider the + // following pseudocode: + // + // func (s *Server) Handle(req http.Request) error { + // sp := s.StartSpan("server") + // defer sp.Finish() + // wait := s.queueProcessing(opentracing.ContextWithSpan(context.Background(), sp), req) + // select { + // case resp := <-wait: + // return resp.Error + // case <-time.After(10*time.Second): + // sp.LogEvent("timed out waiting for processing") + // return ErrTimedOut + // } + // } + // + // This looks reasonable at first, but a request which spends more than ten + // seconds in the queue is abandoned by the main goroutine and its trace + // finished, leading to use-after-finish when the request is finally + // processed. Note also that even joining on to a finished Span via + // StartSpanWithOptions constitutes an illegal operation. + // + // Code bases which do not require (or decide they do not want) Spans to + // be passed across goroutine boundaries can run with this flag enabled in + // tests to increase their chances of spotting wrong-doers. + DebugAssertSingleGoroutine bool + // DebugAssertUseAfterFinish is provided strictly for development purposes. + // When set, it attempts to exacerbate issues emanating from use of Spans + // after calling Finish by running additional assertions. + DebugAssertUseAfterFinish bool + // EnableSpanPool enables the use of a pool, so that the tracer reuses spans + // after Finish has been called on it. Adds a slight performance gain as it + // reduces allocations. However, if you have any use-after-finish race + // conditions the code may panic. + EnableSpanPool bool +} diff --git a/vendor/github.com/instana/go-sensor/util.go b/vendor/github.com/instana/go-sensor/util.go new file mode 100644 index 000000000..de43444e7 --- /dev/null +++ b/vendor/github.com/instana/go-sensor/util.go @@ -0,0 +1,99 @@ +package instana + +import ( + "bytes" + "encoding/binary" + "errors" + "io/ioutil" + "math/rand" + "os" + "strconv" + "strings" + "sync" + "time" +) + +var ( + seededIDGen = rand.New(rand.NewSource(time.Now().UnixNano())) + seededIDLock sync.Mutex +) + +func randomID() int64 { + seededIDLock.Lock() + defer seededIDLock.Unlock() + return int64(seededIDGen.Int63()) +} + +// ID2Header converts an Instana ID to a value that can be used in +// context propagation (such as HTTP headers). More specifically, +// this converts a signed 64 bit integer into an unsigned hex string. +func ID2Header(id int64) (string, error) { + // FIXME: We're assuming LittleEndian here + + // Write out _signed_ 64bit integer to byte buffer + buf := new(bytes.Buffer) + if err := binary.Write(buf, binary.LittleEndian, id); err == nil { + // Read bytes back into _unsigned_ 64 bit integer + var unsigned uint64 + if err = binary.Read(buf, binary.LittleEndian, &unsigned); err == nil { + // Convert uint64 to hex string equivalent and return that + return strconv.FormatUint(unsigned, 16), nil + } + log.debug(err) + } else { + log.debug(err) + } + return "", errors.New("context corrupted; could not convert value") +} + +// Header2ID converts an header context value into an Instana ID. More +// specifically, this converts an unsigned 64 bit hex value into a signed +// 64bit integer. +func Header2ID(header string) (int64, error) { + // FIXME: We're assuming LittleEndian here + + // Parse unsigned 64 bit hex string into unsigned 64 bit base 10 integer + if unsignedID, err := strconv.ParseUint(header, 16, 64); err == nil { + // Write out _unsigned_ 64bit integer to byte buffer + buf := new(bytes.Buffer) + if err = binary.Write(buf, binary.LittleEndian, unsignedID); err == nil { + // Read bytes back into _signed_ 64 bit integer + var signedID int64 + if err = binary.Read(buf, binary.LittleEndian, &signedID); err == nil { + // The success case + return signedID, nil + } + log.debug(err) + } else { + log.debug(err) + } + } else { + log.debug(err) + } + return int64(0), errors.New("context corrupted; could not convert value") +} + +func getCommandLine() (string, []string) { + var cmdlinePath string = "/proc/" + strconv.Itoa(os.Getpid()) + "/cmdline" + + cmdline, err := ioutil.ReadFile(cmdlinePath) + + if err != nil { + log.debug("No /proc. Returning OS reported cmdline") + return os.Args[0], os.Args[1:] + } + + parts := strings.FieldsFunc(string(cmdline), func(c rune) bool { + if c == '\u0000' { + return true + } + return false + }) + log.debug("cmdline says:", parts[0], parts[1:]) + return parts[0], parts[1:] +} + +func abs(x int64) int64 { + y := x >> 63 + return (x + y) ^ y +} diff --git a/vendor/github.com/looplab/fsm/LICENSE b/vendor/github.com/looplab/fsm/LICENSE new file mode 100644 index 000000000..37ec93a14 --- /dev/null +++ b/vendor/github.com/looplab/fsm/LICENSE @@ -0,0 +1,191 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, "control" means (i) the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising +permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +"Object" form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +"submitted" means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +2. Grant of Copyright License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +3. Grant of Patent License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of +this License; and +You must cause any modified files to carry prominent notices stating that You +changed the files; and +You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +If the Work includes a "NOTICE" text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +5. Submission of Contributions. + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +6. Trademarks. + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +8. Limitation of Liability. + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets "[]" replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same "printed page" as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/looplab/fsm/errors.go b/vendor/github.com/looplab/fsm/errors.go new file mode 100644 index 000000000..9c32a498d --- /dev/null +++ b/vendor/github.com/looplab/fsm/errors.go @@ -0,0 +1,100 @@ +// Copyright (c) 2013 - Max Persson +// +// 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 fsm + +// InvalidEventError is returned by FSM.Event() when the event cannot be called +// in the current state. +type InvalidEventError struct { + Event string + State string +} + +func (e InvalidEventError) Error() string { + return "event " + e.Event + " inappropriate in current state " + e.State +} + +// UnknownEventError is returned by FSM.Event() when the event is not defined. +type UnknownEventError struct { + Event string +} + +func (e UnknownEventError) Error() string { + return "event " + e.Event + " does not exist" +} + +// InTransitionError is returned by FSM.Event() when an asynchronous transition +// is already in progress. +type InTransitionError struct { + Event string +} + +func (e InTransitionError) Error() string { + return "event " + e.Event + " inappropriate because previous transition did not complete" +} + +// NotInTransitionError is returned by FSM.Transition() when an asynchronous +// transition is not in progress. +type NotInTransitionError struct{} + +func (e NotInTransitionError) Error() string { + return "transition inappropriate because no state change in progress" +} + +// NoTransitionError is returned by FSM.Event() when no transition have happened, +// for example if the source and destination states are the same. +type NoTransitionError struct { + Err error +} + +func (e NoTransitionError) Error() string { + if e.Err != nil { + return "no transition with error: " + e.Err.Error() + } + return "no transition" +} + +// CanceledError is returned by FSM.Event() when a callback have canceled a +// transition. +type CanceledError struct { + Err error +} + +func (e CanceledError) Error() string { + if e.Err != nil { + return "transition canceled with error: " + e.Err.Error() + } + return "transition canceled" +} + +// AsyncError is returned by FSM.Event() when a callback have initiated an +// asynchronous state transition. +type AsyncError struct { + Err error +} + +func (e AsyncError) Error() string { + if e.Err != nil { + return "async started with error: " + e.Err.Error() + } + return "async started" +} + +// InternalError is returned by FSM.Event() and should never occur. It is a +// probably because of a bug. +type InternalError struct{} + +func (e InternalError) Error() string { + return "internal error on state transition" +} diff --git a/vendor/github.com/looplab/fsm/event.go b/vendor/github.com/looplab/fsm/event.go new file mode 100644 index 000000000..fdca2d0c2 --- /dev/null +++ b/vendor/github.com/looplab/fsm/event.go @@ -0,0 +1,62 @@ +// Copyright (c) 2013 - Max Persson +// +// 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 fsm + +// Event is the info that get passed as a reference in the callbacks. +type Event struct { + // FSM is a reference to the current FSM. + FSM *FSM + + // Event is the event name. + Event string + + // Src is the state before the transition. + Src string + + // Dst is the state after the transition. + Dst string + + // Err is an optional error that can be returned from a callback. + Err error + + // Args is a optinal list of arguments passed to the callback. + Args []interface{} + + // canceled is an internal flag set if the transition is canceled. + canceled bool + + // async is an internal flag set if the transition should be asynchronous + async bool +} + +// Cancel can be called in before_ or leave_ to cancel the +// current transition before it happens. It takes an opitonal error, which will +// overwrite e.Err if set before. +func (e *Event) Cancel(err ...error) { + e.canceled = true + + if len(err) > 0 { + e.Err = err[0] + } +} + +// Async can be called in leave_ to do an asynchronous state transition. +// +// The current state transition will be on hold in the old state until a final +// call to Transition is made. This will comlete the transition and possibly +// call the other callbacks. +func (e *Event) Async() { + e.async = true +} diff --git a/vendor/github.com/looplab/fsm/fsm.go b/vendor/github.com/looplab/fsm/fsm.go new file mode 100644 index 000000000..9da9db39a --- /dev/null +++ b/vendor/github.com/looplab/fsm/fsm.go @@ -0,0 +1,447 @@ +// Copyright (c) 2013 - Max Persson +// +// 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 fsm implements a finite state machine. +// +// It is heavily based on two FSM implementations: +// +// Javascript Finite State Machine +// https://github.com/jakesgordon/javascript-state-machine +// +// Fysom for Python +// https://github.com/oxplot/fysom (forked at https://github.com/mriehl/fysom) +// +package fsm + +import ( + "strings" + "sync" +) + +// transitioner is an interface for the FSM's transition function. +type transitioner interface { + transition(*FSM) error +} + +// FSM is the state machine that holds the current state. +// +// It has to be created with NewFSM to function properly. +type FSM struct { + // current is the state that the FSM is currently in. + current string + + // transitions maps events and source states to destination states. + transitions map[eKey]string + + // callbacks maps events and targers to callback functions. + callbacks map[cKey]Callback + + // transition is the internal transition functions used either directly + // or when Transition is called in an asynchronous state transition. + transition func() + // transitionerObj calls the FSM's transition() function. + transitionerObj transitioner + + // stateMu guards access to the current state. + stateMu sync.RWMutex + // eventMu guards access to Event() and Transition(). + eventMu sync.Mutex +} + +// EventDesc represents an event when initializing the FSM. +// +// The event can have one or more source states that is valid for performing +// the transition. If the FSM is in one of the source states it will end up in +// the specified destination state, calling all defined callbacks as it goes. +type EventDesc struct { + // Name is the event name used when calling for a transition. + Name string + + // Src is a slice of source states that the FSM must be in to perform a + // state transition. + Src []string + + // Dst is the destination state that the FSM will be in if the transition + // succeds. + Dst string +} + +// Callback is a function type that callbacks should use. Event is the current +// event info as the callback happens. +type Callback func(*Event) + +// Events is a shorthand for defining the transition map in NewFSM. +type Events []EventDesc + +// Callbacks is a shorthand for defining the callbacks in NewFSM.a +type Callbacks map[string]Callback + +// NewFSM constructs a FSM from events and callbacks. +// +// The events and transitions are specified as a slice of Event structs +// specified as Events. Each Event is mapped to one or more internal +// transitions from Event.Src to Event.Dst. +// +// Callbacks are added as a map specified as Callbacks where the key is parsed +// as the callback event as follows, and called in the same order: +// +// 1. before_ - called before event named +// +// 2. before_event - called before all events +// +// 3. leave_ - called before leaving +// +// 4. leave_state - called before leaving all states +// +// 5. enter_ - called after entering +// +// 6. enter_state - called after entering all states +// +// 7. after_ - called after event named +// +// 8. after_event - called after all events +// +// There are also two short form versions for the most commonly used callbacks. +// They are simply the name of the event or state: +// +// 1. - called after entering +// +// 2. - called after event named +// +// If both a shorthand version and a full version is specified it is undefined +// which version of the callback will end up in the internal map. This is due +// to the psuedo random nature of Go maps. No checking for multiple keys is +// currently performed. +func NewFSM(initial string, events []EventDesc, callbacks map[string]Callback) *FSM { + f := &FSM{ + transitionerObj: &transitionerStruct{}, + current: initial, + transitions: make(map[eKey]string), + callbacks: make(map[cKey]Callback), + } + + // Build transition map and store sets of all events and states. + allEvents := make(map[string]bool) + allStates := make(map[string]bool) + for _, e := range events { + for _, src := range e.Src { + f.transitions[eKey{e.Name, src}] = e.Dst + allStates[src] = true + allStates[e.Dst] = true + } + allEvents[e.Name] = true + } + + // Map all callbacks to events/states. + for name, fn := range callbacks { + var target string + var callbackType int + + switch { + case strings.HasPrefix(name, "before_"): + target = strings.TrimPrefix(name, "before_") + if target == "event" { + target = "" + callbackType = callbackBeforeEvent + } else if _, ok := allEvents[target]; ok { + callbackType = callbackBeforeEvent + } + case strings.HasPrefix(name, "leave_"): + target = strings.TrimPrefix(name, "leave_") + if target == "state" { + target = "" + callbackType = callbackLeaveState + } else if _, ok := allStates[target]; ok { + callbackType = callbackLeaveState + } + case strings.HasPrefix(name, "enter_"): + target = strings.TrimPrefix(name, "enter_") + if target == "state" { + target = "" + callbackType = callbackEnterState + } else if _, ok := allStates[target]; ok { + callbackType = callbackEnterState + } + case strings.HasPrefix(name, "after_"): + target = strings.TrimPrefix(name, "after_") + if target == "event" { + target = "" + callbackType = callbackAfterEvent + } else if _, ok := allEvents[target]; ok { + callbackType = callbackAfterEvent + } + default: + target = name + if _, ok := allStates[target]; ok { + callbackType = callbackEnterState + } else if _, ok := allEvents[target]; ok { + callbackType = callbackAfterEvent + } + } + + if callbackType != callbackNone { + f.callbacks[cKey{target, callbackType}] = fn + } + } + + return f +} + +// Current returns the current state of the FSM. +func (f *FSM) Current() string { + f.stateMu.RLock() + defer f.stateMu.RUnlock() + return f.current +} + +// Is returns true if state is the current state. +func (f *FSM) Is(state string) bool { + f.stateMu.RLock() + defer f.stateMu.RUnlock() + return state == f.current +} + +// SetState allows the user to move to the given state from current state. +// The call does not trigger any callbacks, if defined. +func (f *FSM) SetState(state string) { + f.stateMu.Lock() + defer f.stateMu.Unlock() + f.current = state + return +} + +// Can returns true if event can occur in the current state. +func (f *FSM) Can(event string) bool { + f.stateMu.RLock() + defer f.stateMu.RUnlock() + _, ok := f.transitions[eKey{event, f.current}] + return ok && (f.transition == nil) +} + +// AvailableTransitions returns a list of transitions avilable in the +// current state. +func (f *FSM) AvailableTransitions() []string { + f.stateMu.RLock() + defer f.stateMu.RUnlock() + var transitions []string + for key := range f.transitions { + if key.src == f.current { + transitions = append(transitions, key.event) + } + } + return transitions +} + +// Cannot returns true if event can not occure in the current state. +// It is a convenience method to help code read nicely. +func (f *FSM) Cannot(event string) bool { + return !f.Can(event) +} + +// Event initiates a state transition with the named event. +// +// The call takes a variable number of arguments that will be passed to the +// callback, if defined. +// +// It will return nil if the state change is ok or one of these errors: +// +// - event X inappropriate because previous transition did not complete +// +// - event X inappropriate in current state Y +// +// - event X does not exist +// +// - internal error on state transition +// +// The last error should never occur in this situation and is a sign of an +// internal bug. +func (f *FSM) Event(event string, args ...interface{}) error { + f.eventMu.Lock() + defer f.eventMu.Unlock() + + f.stateMu.RLock() + defer f.stateMu.RUnlock() + + if f.transition != nil { + return InTransitionError{event} + } + + dst, ok := f.transitions[eKey{event, f.current}] + if !ok { + for ekey := range f.transitions { + if ekey.event == event { + return InvalidEventError{event, f.current} + } + } + return UnknownEventError{event} + } + + e := &Event{f, event, f.current, dst, nil, args, false, false} + + err := f.beforeEventCallbacks(e) + if err != nil { + return err + } + + if f.current == dst { + f.afterEventCallbacks(e) + return NoTransitionError{e.Err} + } + + // Setup the transition, call it later. + f.transition = func() { + f.stateMu.Lock() + f.current = dst + f.stateMu.Unlock() + + f.enterStateCallbacks(e) + f.afterEventCallbacks(e) + } + + if err = f.leaveStateCallbacks(e); err != nil { + if _, ok := err.(CanceledError); ok { + f.transition = nil + } + return err + } + + // Perform the rest of the transition, if not asynchronous. + f.stateMu.RUnlock() + err = f.doTransition() + f.stateMu.RLock() + if err != nil { + return InternalError{} + } + + return e.Err +} + +// Transition wraps transitioner.transition. +func (f *FSM) Transition() error { + f.eventMu.Lock() + defer f.eventMu.Unlock() + return f.doTransition() +} + +// doTransition wraps transitioner.transition. +func (f *FSM) doTransition() error { + return f.transitionerObj.transition(f) +} + +// transitionerStruct is the default implementation of the transitioner +// interface. Other implementations can be swapped in for testing. +type transitionerStruct struct{} + +// Transition completes an asynchrounous state change. +// +// The callback for leave_ must prviously have called Async on its +// event to have initiated an asynchronous state transition. +func (t transitionerStruct) transition(f *FSM) error { + if f.transition == nil { + return NotInTransitionError{} + } + f.transition() + f.transition = nil + return nil +} + +// beforeEventCallbacks calls the before_ callbacks, first the named then the +// general version. +func (f *FSM) beforeEventCallbacks(e *Event) error { + if fn, ok := f.callbacks[cKey{e.Event, callbackBeforeEvent}]; ok { + fn(e) + if e.canceled { + return CanceledError{e.Err} + } + } + if fn, ok := f.callbacks[cKey{"", callbackBeforeEvent}]; ok { + fn(e) + if e.canceled { + return CanceledError{e.Err} + } + } + return nil +} + +// leaveStateCallbacks calls the leave_ callbacks, first the named then the +// general version. +func (f *FSM) leaveStateCallbacks(e *Event) error { + if fn, ok := f.callbacks[cKey{f.current, callbackLeaveState}]; ok { + fn(e) + if e.canceled { + return CanceledError{e.Err} + } else if e.async { + return AsyncError{e.Err} + } + } + if fn, ok := f.callbacks[cKey{"", callbackLeaveState}]; ok { + fn(e) + if e.canceled { + return CanceledError{e.Err} + } else if e.async { + return AsyncError{e.Err} + } + } + return nil +} + +// enterStateCallbacks calls the enter_ callbacks, first the named then the +// general version. +func (f *FSM) enterStateCallbacks(e *Event) { + if fn, ok := f.callbacks[cKey{f.current, callbackEnterState}]; ok { + fn(e) + } + if fn, ok := f.callbacks[cKey{"", callbackEnterState}]; ok { + fn(e) + } +} + +// afterEventCallbacks calls the after_ callbacks, first the named then the +// general version. +func (f *FSM) afterEventCallbacks(e *Event) { + if fn, ok := f.callbacks[cKey{e.Event, callbackAfterEvent}]; ok { + fn(e) + } + if fn, ok := f.callbacks[cKey{"", callbackAfterEvent}]; ok { + fn(e) + } +} + +const ( + callbackNone int = iota + callbackBeforeEvent + callbackLeaveState + callbackEnterState + callbackAfterEvent +) + +// cKey is a struct key used for keeping the callbacks mapped to a target. +type cKey struct { + // target is either the name of a state or an event depending on which + // callback type the key refers to. It can also be "" for a non-targeted + // callback like before_event. + target string + + // callbackType is the situation when the callback will be run. + callbackType int +} + +// eKey is a struct key used for storing the transition map. +type eKey struct { + // event is the name of the event that the keys refers to. + event string + + // src is the source from where the event can transition. + src string +} diff --git a/vendor/github.com/looplab/fsm/utils.go b/vendor/github.com/looplab/fsm/utils.go new file mode 100644 index 000000000..e49892ecd --- /dev/null +++ b/vendor/github.com/looplab/fsm/utils.go @@ -0,0 +1,45 @@ +package fsm + +import ( + "bytes" + "fmt" +) + +// Visualize outputs a visualization of a FSM in Graphviz format. +func Visualize(fsm *FSM) string { + var buf bytes.Buffer + + states := make(map[string]int) + + buf.WriteString(fmt.Sprintf(`digraph fsm {`)) + buf.WriteString("\n") + + // make sure the initial state is at top + for k, v := range fsm.transitions { + if k.src == fsm.current { + states[k.src]++ + states[v]++ + buf.WriteString(fmt.Sprintf(` "%s" -> "%s" [ label = "%s" ];`, k.src, v, k.event)) + buf.WriteString("\n") + } + } + + for k, v := range fsm.transitions { + if k.src != fsm.current { + states[k.src]++ + states[v]++ + buf.WriteString(fmt.Sprintf(` "%s" -> "%s" [ label = "%s" ];`, k.src, v, k.event)) + buf.WriteString("\n") + } + } + + buf.WriteString("\n") + + for k := range states { + buf.WriteString(fmt.Sprintf(` "%s";`, k)) + buf.WriteString("\n") + } + buf.WriteString(fmt.Sprintln("}")) + + return buf.String() +} diff --git a/vendor/github.com/opentracing/basictracer-go/LICENSE b/vendor/github.com/opentracing/basictracer-go/LICENSE new file mode 100644 index 000000000..148509a40 --- /dev/null +++ b/vendor/github.com/opentracing/basictracer-go/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 The OpenTracing Authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/opentracing/basictracer-go/context.go b/vendor/github.com/opentracing/basictracer-go/context.go new file mode 100644 index 000000000..a2db9ffe8 --- /dev/null +++ b/vendor/github.com/opentracing/basictracer-go/context.go @@ -0,0 +1,42 @@ +package basictracer + +// SpanContext holds the basic Span metadata. +type SpanContext struct { + // A probabilistically unique identifier for a [multi-span] trace. + TraceID uint64 + + // A probabilistically unique identifier for a span. + SpanID uint64 + + // Whether the trace is sampled. + Sampled bool + + // The span's associated baggage. + Baggage map[string]string // initialized on first use +} + +// ForeachBaggageItem belongs to the opentracing.SpanContext interface +func (c SpanContext) ForeachBaggageItem(handler func(k, v string) bool) { + for k, v := range c.Baggage { + if !handler(k, v) { + break + } + } +} + +// WithBaggageItem returns an entirely new basictracer SpanContext with the +// given key:value baggage pair set. +func (c SpanContext) WithBaggageItem(key, val string) SpanContext { + var newBaggage map[string]string + if c.Baggage == nil { + newBaggage = map[string]string{key: val} + } else { + newBaggage = make(map[string]string, len(c.Baggage)+1) + for k, v := range c.Baggage { + newBaggage[k] = v + } + newBaggage[key] = val + } + // Use positional parameters so the compiler will help catch new fields. + return SpanContext{c.TraceID, c.SpanID, c.Sampled, newBaggage} +} diff --git a/vendor/github.com/opentracing/basictracer-go/debug.go b/vendor/github.com/opentracing/basictracer-go/debug.go new file mode 100644 index 000000000..817061927 --- /dev/null +++ b/vendor/github.com/opentracing/basictracer-go/debug.go @@ -0,0 +1,78 @@ +package basictracer + +import ( + "bytes" + "fmt" + "runtime" + "strconv" + "sync" +) + +const debugGoroutineIDTag = "_initial_goroutine" + +type errAssertionFailed struct { + span *spanImpl + msg string +} + +// Error implements the error interface. +func (err *errAssertionFailed) Error() string { + return fmt.Sprintf("%s:\n%+v", err.msg, err.span) +} + +func (s *spanImpl) Lock() { + s.Mutex.Lock() + s.maybeAssertSanityLocked() +} + +func (s *spanImpl) maybeAssertSanityLocked() { + if s.tracer == nil { + s.Mutex.Unlock() + panic(&errAssertionFailed{span: s, msg: "span used after call to Finish()"}) + } + if s.tracer.options.DebugAssertSingleGoroutine { + startID := curGoroutineID() + curID, ok := s.raw.Tags[debugGoroutineIDTag].(uint64) + if !ok { + // This is likely invoked in the context of the SetTag which sets + // debugGoroutineTag. + return + } + if startID != curID { + s.Mutex.Unlock() + panic(&errAssertionFailed{ + span: s, + msg: fmt.Sprintf("span started on goroutine %d, but now running on %d", startID, curID), + }) + } + } +} + +var goroutineSpace = []byte("goroutine ") +var littleBuf = sync.Pool{ + New: func() interface{} { + buf := make([]byte, 64) + return &buf + }, +} + +// Credit to @bradfitz: +// https://github.com/golang/net/blob/master/http2/gotrack.go#L51 +func curGoroutineID() uint64 { + bp := littleBuf.Get().(*[]byte) + defer littleBuf.Put(bp) + b := *bp + b = b[:runtime.Stack(b, false)] + // Parse the 4707 out of "goroutine 4707 [" + b = bytes.TrimPrefix(b, goroutineSpace) + i := bytes.IndexByte(b, ' ') + if i < 0 { + panic(fmt.Sprintf("No space found in %q", b)) + } + b = b[:i] + n, err := strconv.ParseUint(string(b), 10, 64) + if err != nil { + panic(fmt.Sprintf("Failed to parse goroutine ID out of %q: %v", b, err)) + } + return n +} diff --git a/vendor/github.com/opentracing/basictracer-go/event.go b/vendor/github.com/opentracing/basictracer-go/event.go new file mode 100644 index 000000000..e074fcc7d --- /dev/null +++ b/vendor/github.com/opentracing/basictracer-go/event.go @@ -0,0 +1,62 @@ +package basictracer + +import "github.com/opentracing/opentracing-go" + +// A SpanEvent is emitted when a mutating command is called on a Span. +type SpanEvent interface{} + +// EventCreate is emitted when a Span is created. +type EventCreate struct{ OperationName string } + +// EventTag is received when SetTag is called. +type EventTag struct { + Key string + Value interface{} +} + +// EventBaggage is received when SetBaggageItem is called. +type EventBaggage struct { + Key, Value string +} + +// EventLogFields is received when LogFields or LogKV is called. +type EventLogFields opentracing.LogRecord + +// EventLog is received when Log (or one of its derivatives) is called. +// +// DEPRECATED +type EventLog opentracing.LogData + +// EventFinish is received when Finish is called. +type EventFinish RawSpan + +func (s *spanImpl) onCreate(opName string) { + if s.event != nil { + s.event(EventCreate{OperationName: opName}) + } +} +func (s *spanImpl) onTag(key string, value interface{}) { + if s.event != nil { + s.event(EventTag{Key: key, Value: value}) + } +} +func (s *spanImpl) onLog(ld opentracing.LogData) { + if s.event != nil { + s.event(EventLog(ld)) + } +} +func (s *spanImpl) onLogFields(lr opentracing.LogRecord) { + if s.event != nil { + s.event(EventLogFields(lr)) + } +} +func (s *spanImpl) onBaggage(key, value string) { + if s.event != nil { + s.event(EventBaggage{Key: key, Value: value}) + } +} +func (s *spanImpl) onFinish(sp RawSpan) { + if s.event != nil { + s.event(EventFinish(sp)) + } +} diff --git a/vendor/github.com/opentracing/basictracer-go/propagation.go b/vendor/github.com/opentracing/basictracer-go/propagation.go new file mode 100644 index 000000000..15970c696 --- /dev/null +++ b/vendor/github.com/opentracing/basictracer-go/propagation.go @@ -0,0 +1,61 @@ +package basictracer + +import opentracing "github.com/opentracing/opentracing-go" + +type accessorPropagator struct { + tracer *tracerImpl +} + +// DelegatingCarrier is a flexible carrier interface which can be implemented +// by types which have a means of storing the trace metadata and already know +// how to serialize themselves (for example, protocol buffers). +type DelegatingCarrier interface { + SetState(traceID, spanID uint64, sampled bool) + State() (traceID, spanID uint64, sampled bool) + SetBaggageItem(key, value string) + GetBaggage(func(key, value string)) +} + +func (p *accessorPropagator) Inject( + spanContext opentracing.SpanContext, + carrier interface{}, +) error { + dc, ok := carrier.(DelegatingCarrier) + if !ok || dc == nil { + return opentracing.ErrInvalidCarrier + } + sc, ok := spanContext.(SpanContext) + if !ok { + return opentracing.ErrInvalidSpanContext + } + dc.SetState(sc.TraceID, sc.SpanID, sc.Sampled) + for k, v := range sc.Baggage { + dc.SetBaggageItem(k, v) + } + return nil +} + +func (p *accessorPropagator) Extract( + carrier interface{}, +) (opentracing.SpanContext, error) { + dc, ok := carrier.(DelegatingCarrier) + if !ok || dc == nil { + return nil, opentracing.ErrInvalidCarrier + } + + traceID, spanID, sampled := dc.State() + sc := SpanContext{ + TraceID: traceID, + SpanID: spanID, + Sampled: sampled, + Baggage: nil, + } + dc.GetBaggage(func(k, v string) { + if sc.Baggage == nil { + sc.Baggage = map[string]string{} + } + sc.Baggage[k] = v + }) + + return sc, nil +} diff --git a/vendor/github.com/opentracing/basictracer-go/propagation_ot.go b/vendor/github.com/opentracing/basictracer-go/propagation_ot.go new file mode 100644 index 000000000..61362ceec --- /dev/null +++ b/vendor/github.com/opentracing/basictracer-go/propagation_ot.go @@ -0,0 +1,180 @@ +package basictracer + +import ( + "encoding/binary" + "io" + "strconv" + "strings" + + "github.com/gogo/protobuf/proto" + "github.com/opentracing/basictracer-go/wire" + opentracing "github.com/opentracing/opentracing-go" +) + +type textMapPropagator struct { + tracer *tracerImpl +} +type binaryPropagator struct { + tracer *tracerImpl +} + +const ( + prefixTracerState = "ot-tracer-" + prefixBaggage = "ot-baggage-" + + tracerStateFieldCount = 3 + fieldNameTraceID = prefixTracerState + "traceid" + fieldNameSpanID = prefixTracerState + "spanid" + fieldNameSampled = prefixTracerState + "sampled" +) + +func (p *textMapPropagator) Inject( + spanContext opentracing.SpanContext, + opaqueCarrier interface{}, +) error { + sc, ok := spanContext.(SpanContext) + if !ok { + return opentracing.ErrInvalidSpanContext + } + carrier, ok := opaqueCarrier.(opentracing.TextMapWriter) + if !ok { + return opentracing.ErrInvalidCarrier + } + carrier.Set(fieldNameTraceID, strconv.FormatUint(sc.TraceID, 16)) + carrier.Set(fieldNameSpanID, strconv.FormatUint(sc.SpanID, 16)) + carrier.Set(fieldNameSampled, strconv.FormatBool(sc.Sampled)) + + for k, v := range sc.Baggage { + carrier.Set(prefixBaggage+k, v) + } + return nil +} + +func (p *textMapPropagator) Extract( + opaqueCarrier interface{}, +) (opentracing.SpanContext, error) { + carrier, ok := opaqueCarrier.(opentracing.TextMapReader) + if !ok { + return nil, opentracing.ErrInvalidCarrier + } + requiredFieldCount := 0 + var traceID, spanID uint64 + var sampled bool + var err error + decodedBaggage := make(map[string]string) + err = carrier.ForeachKey(func(k, v string) error { + switch strings.ToLower(k) { + case fieldNameTraceID: + traceID, err = strconv.ParseUint(v, 16, 64) + if err != nil { + return opentracing.ErrSpanContextCorrupted + } + case fieldNameSpanID: + spanID, err = strconv.ParseUint(v, 16, 64) + if err != nil { + return opentracing.ErrSpanContextCorrupted + } + case fieldNameSampled: + sampled, err = strconv.ParseBool(v) + if err != nil { + return opentracing.ErrSpanContextCorrupted + } + default: + lowercaseK := strings.ToLower(k) + if strings.HasPrefix(lowercaseK, prefixBaggage) { + decodedBaggage[strings.TrimPrefix(lowercaseK, prefixBaggage)] = v + } + // Balance off the requiredFieldCount++ just below... + requiredFieldCount-- + } + requiredFieldCount++ + return nil + }) + if err != nil { + return nil, err + } + if requiredFieldCount < tracerStateFieldCount { + if requiredFieldCount == 0 { + return nil, opentracing.ErrSpanContextNotFound + } + return nil, opentracing.ErrSpanContextCorrupted + } + + return SpanContext{ + TraceID: traceID, + SpanID: spanID, + Sampled: sampled, + Baggage: decodedBaggage, + }, nil +} + +func (p *binaryPropagator) Inject( + spanContext opentracing.SpanContext, + opaqueCarrier interface{}, +) error { + sc, ok := spanContext.(SpanContext) + if !ok { + return opentracing.ErrInvalidSpanContext + } + carrier, ok := opaqueCarrier.(io.Writer) + if !ok { + return opentracing.ErrInvalidCarrier + } + + state := wire.TracerState{} + state.TraceId = sc.TraceID + state.SpanId = sc.SpanID + state.Sampled = sc.Sampled + state.BaggageItems = sc.Baggage + + b, err := proto.Marshal(&state) + if err != nil { + return err + } + + // Write the length of the marshalled binary to the writer. + length := uint32(len(b)) + if err := binary.Write(carrier, binary.BigEndian, &length); err != nil { + return err + } + + _, err = carrier.Write(b) + return err +} + +func (p *binaryPropagator) Extract( + opaqueCarrier interface{}, +) (opentracing.SpanContext, error) { + carrier, ok := opaqueCarrier.(io.Reader) + if !ok { + return nil, opentracing.ErrInvalidCarrier + } + + // Read the length of marshalled binary. io.ReadAll isn't that performant + // since it keeps resizing the underlying buffer as it encounters more bytes + // to read. By reading the length, we can allocate a fixed sized buf and read + // the exact amount of bytes into it. + var length uint32 + if err := binary.Read(carrier, binary.BigEndian, &length); err != nil { + return nil, opentracing.ErrSpanContextCorrupted + } + buf := make([]byte, length) + if n, err := carrier.Read(buf); err != nil { + if n > 0 { + return nil, opentracing.ErrSpanContextCorrupted + } + return nil, opentracing.ErrSpanContextNotFound + } + + ctx := wire.TracerState{} + if err := proto.Unmarshal(buf, &ctx); err != nil { + return nil, opentracing.ErrSpanContextCorrupted + } + + return SpanContext{ + TraceID: ctx.TraceId, + SpanID: ctx.SpanId, + Sampled: ctx.Sampled, + Baggage: ctx.BaggageItems, + }, nil +} diff --git a/vendor/github.com/opentracing/basictracer-go/raw.go b/vendor/github.com/opentracing/basictracer-go/raw.go new file mode 100644 index 000000000..e038b3694 --- /dev/null +++ b/vendor/github.com/opentracing/basictracer-go/raw.go @@ -0,0 +1,34 @@ +package basictracer + +import ( + "time" + + opentracing "github.com/opentracing/opentracing-go" +) + +// RawSpan encapsulates all state associated with a (finished) Span. +type RawSpan struct { + // Those recording the RawSpan should also record the contents of its + // SpanContext. + Context SpanContext + + // The SpanID of this SpanContext's first intra-trace reference (i.e., + // "parent"), or 0 if there is no parent. + ParentSpanID uint64 + + // The name of the "operation" this span is an instance of. (Called a "span + // name" in some implementations) + Operation string + + // We store rather than so that only + // one of the timestamps has global clock uncertainty issues. + Start time.Time + Duration time.Duration + + // Essentially an extension mechanism. Can be used for many purposes, + // not to be enumerated here. + Tags opentracing.Tags + + // The span's "microlog". + Logs []opentracing.LogRecord +} diff --git a/vendor/github.com/opentracing/basictracer-go/recorder.go b/vendor/github.com/opentracing/basictracer-go/recorder.go new file mode 100644 index 000000000..d6a0856c3 --- /dev/null +++ b/vendor/github.com/opentracing/basictracer-go/recorder.go @@ -0,0 +1,60 @@ +package basictracer + +import "sync" + +// A SpanRecorder handles all of the `RawSpan` data generated via an +// associated `Tracer` (see `NewStandardTracer`) instance. It also names +// the containing process and provides access to a straightforward tag map. +type SpanRecorder interface { + // Implementations must determine whether and where to store `span`. + RecordSpan(span RawSpan) +} + +// InMemorySpanRecorder is a simple thread-safe implementation of +// SpanRecorder that stores all reported spans in memory, accessible +// via reporter.GetSpans(). It is primarily intended for testing purposes. +type InMemorySpanRecorder struct { + sync.RWMutex + spans []RawSpan +} + +// NewInMemoryRecorder creates new InMemorySpanRecorder +func NewInMemoryRecorder() *InMemorySpanRecorder { + return new(InMemorySpanRecorder) +} + +// RecordSpan implements the respective method of SpanRecorder. +func (r *InMemorySpanRecorder) RecordSpan(span RawSpan) { + r.Lock() + defer r.Unlock() + r.spans = append(r.spans, span) +} + +// GetSpans returns a copy of the array of spans accumulated so far. +func (r *InMemorySpanRecorder) GetSpans() []RawSpan { + r.RLock() + defer r.RUnlock() + spans := make([]RawSpan, len(r.spans)) + copy(spans, r.spans) + return spans +} + +// GetSampledSpans returns a slice of spans accumulated so far which were sampled. +func (r *InMemorySpanRecorder) GetSampledSpans() []RawSpan { + r.RLock() + defer r.RUnlock() + spans := make([]RawSpan, 0, len(r.spans)) + for _, span := range r.spans { + if span.Context.Sampled { + spans = append(spans, span) + } + } + return spans +} + +// Reset clears the internal array of spans. +func (r *InMemorySpanRecorder) Reset() { + r.Lock() + defer r.Unlock() + r.spans = nil +} diff --git a/vendor/github.com/opentracing/basictracer-go/span.go b/vendor/github.com/opentracing/basictracer-go/span.go new file mode 100644 index 000000000..fdceba5bc --- /dev/null +++ b/vendor/github.com/opentracing/basictracer-go/span.go @@ -0,0 +1,274 @@ +package basictracer + +import ( + "sync" + "time" + + opentracing "github.com/opentracing/opentracing-go" + "github.com/opentracing/opentracing-go/ext" + "github.com/opentracing/opentracing-go/log" +) + +// Span provides access to the essential details of the span, for use +// by basictracer consumers. These methods may only be called prior +// to (*opentracing.Span).Finish(). +type Span interface { + opentracing.Span + + // Operation names the work done by this span instance + Operation() string + + // Start indicates when the span began + Start() time.Time +} + +// Implements the `Span` interface. Created via tracerImpl (see +// `basictracer.New()`). +type spanImpl struct { + tracer *tracerImpl + event func(SpanEvent) + sync.Mutex // protects the fields below + raw RawSpan + // The number of logs dropped because of MaxLogsPerSpan. + numDroppedLogs int +} + +var spanPool = &sync.Pool{New: func() interface{} { + return &spanImpl{} +}} + +func (s *spanImpl) reset() { + s.tracer, s.event = nil, nil + // Note: Would like to do the following, but then the consumer of RawSpan + // (the recorder) needs to make sure that they're not holding on to the + // baggage or logs when they return (i.e. they need to copy if they care): + // + // logs, baggage := s.raw.Logs[:0], s.raw.Baggage + // for k := range baggage { + // delete(baggage, k) + // } + // s.raw.Logs, s.raw.Baggage = logs, baggage + // + // That's likely too much to ask for. But there is some magic we should + // be able to do with `runtime.SetFinalizer` to reclaim that memory into + // a buffer pool when GC considers them unreachable, which should ease + // some of the load. Hard to say how quickly that would be in practice + // though. + s.raw = RawSpan{ + Context: SpanContext{}, + } +} + +func (s *spanImpl) SetOperationName(operationName string) opentracing.Span { + s.Lock() + defer s.Unlock() + s.raw.Operation = operationName + return s +} + +func (s *spanImpl) trim() bool { + return !s.raw.Context.Sampled && s.tracer.options.TrimUnsampledSpans +} + +func (s *spanImpl) SetTag(key string, value interface{}) opentracing.Span { + defer s.onTag(key, value) + s.Lock() + defer s.Unlock() + if key == string(ext.SamplingPriority) { + if v, ok := value.(uint16); ok { + s.raw.Context.Sampled = v != 0 + return s + } + } + if s.trim() { + return s + } + + if s.raw.Tags == nil { + s.raw.Tags = opentracing.Tags{} + } + s.raw.Tags[key] = value + return s +} + +func (s *spanImpl) LogKV(keyValues ...interface{}) { + fields, err := log.InterleavedKVToFields(keyValues...) + if err != nil { + s.LogFields(log.Error(err), log.String("function", "LogKV")) + return + } + s.LogFields(fields...) +} + +func (s *spanImpl) appendLog(lr opentracing.LogRecord) { + maxLogs := s.tracer.options.MaxLogsPerSpan + if maxLogs == 0 || len(s.raw.Logs) < maxLogs { + s.raw.Logs = append(s.raw.Logs, lr) + return + } + + // We have too many logs. We don't touch the first numOld logs; we treat the + // rest as a circular buffer and overwrite the oldest log among those. + numOld := (maxLogs - 1) / 2 + numNew := maxLogs - numOld + s.raw.Logs[numOld+s.numDroppedLogs%numNew] = lr + s.numDroppedLogs++ +} + +func (s *spanImpl) LogFields(fields ...log.Field) { + lr := opentracing.LogRecord{ + Fields: fields, + } + defer s.onLogFields(lr) + s.Lock() + defer s.Unlock() + if s.trim() || s.tracer.options.DropAllLogs { + return + } + if lr.Timestamp.IsZero() { + lr.Timestamp = time.Now() + } + s.appendLog(lr) +} + +func (s *spanImpl) LogEvent(event string) { + s.Log(opentracing.LogData{ + Event: event, + }) +} + +func (s *spanImpl) LogEventWithPayload(event string, payload interface{}) { + s.Log(opentracing.LogData{ + Event: event, + Payload: payload, + }) +} + +func (s *spanImpl) Log(ld opentracing.LogData) { + defer s.onLog(ld) + s.Lock() + defer s.Unlock() + if s.trim() || s.tracer.options.DropAllLogs { + return + } + + if ld.Timestamp.IsZero() { + ld.Timestamp = time.Now() + } + + s.appendLog(ld.ToLogRecord()) +} + +func (s *spanImpl) Finish() { + s.FinishWithOptions(opentracing.FinishOptions{}) +} + +// rotateLogBuffer rotates the records in the buffer: records 0 to pos-1 move at +// the end (i.e. pos circular left shifts). +func rotateLogBuffer(buf []opentracing.LogRecord, pos int) { + // This algorithm is described in: + // http://www.cplusplus.com/reference/algorithm/rotate + for first, middle, next := 0, pos, pos; first != middle; { + buf[first], buf[next] = buf[next], buf[first] + first++ + next++ + if next == len(buf) { + next = middle + } else if first == middle { + middle = next + } + } +} + +func (s *spanImpl) FinishWithOptions(opts opentracing.FinishOptions) { + finishTime := opts.FinishTime + if finishTime.IsZero() { + finishTime = time.Now() + } + duration := finishTime.Sub(s.raw.Start) + + s.Lock() + defer s.Unlock() + + for _, lr := range opts.LogRecords { + s.appendLog(lr) + } + for _, ld := range opts.BulkLogData { + s.appendLog(ld.ToLogRecord()) + } + + if s.numDroppedLogs > 0 { + // We dropped some log events, which means that we used part of Logs as a + // circular buffer (see appendLog). De-circularize it. + numOld := (len(s.raw.Logs) - 1) / 2 + numNew := len(s.raw.Logs) - numOld + rotateLogBuffer(s.raw.Logs[numOld:], s.numDroppedLogs%numNew) + + // Replace the log in the middle (the oldest "new" log) with information + // about the dropped logs. This means that we are effectively dropping one + // more "new" log. + numDropped := s.numDroppedLogs + 1 + s.raw.Logs[numOld] = opentracing.LogRecord{ + // Keep the timestamp of the last dropped event. + Timestamp: s.raw.Logs[numOld].Timestamp, + Fields: []log.Field{ + log.String("event", "dropped Span logs"), + log.Int("dropped_log_count", numDropped), + log.String("component", "basictracer"), + }, + } + } + + s.raw.Duration = duration + + s.onFinish(s.raw) + s.tracer.options.Recorder.RecordSpan(s.raw) + + // Last chance to get options before the span is possibly reset. + poolEnabled := s.tracer.options.EnableSpanPool + if s.tracer.options.DebugAssertUseAfterFinish { + // This makes it much more likely to catch a panic on any subsequent + // operation since s.tracer is accessed on every call to `Lock`. + // We don't call `reset()` here to preserve the logs in the Span + // which are printed when the assertion triggers. + s.tracer = nil + } + + if poolEnabled { + spanPool.Put(s) + } +} + +func (s *spanImpl) Tracer() opentracing.Tracer { + return s.tracer +} + +func (s *spanImpl) Context() opentracing.SpanContext { + return s.raw.Context +} + +func (s *spanImpl) SetBaggageItem(key, val string) opentracing.Span { + s.onBaggage(key, val) + if s.trim() { + return s + } + + s.Lock() + defer s.Unlock() + s.raw.Context = s.raw.Context.WithBaggageItem(key, val) + return s +} + +func (s *spanImpl) BaggageItem(key string) string { + s.Lock() + defer s.Unlock() + return s.raw.Context.Baggage[key] +} + +func (s *spanImpl) Operation() string { + return s.raw.Operation +} + +func (s *spanImpl) Start() time.Time { + return s.raw.Start +} diff --git a/vendor/github.com/opentracing/basictracer-go/tracer.go b/vendor/github.com/opentracing/basictracer-go/tracer.go new file mode 100644 index 000000000..3e03696e6 --- /dev/null +++ b/vendor/github.com/opentracing/basictracer-go/tracer.go @@ -0,0 +1,262 @@ +package basictracer + +import ( + "time" + + opentracing "github.com/opentracing/opentracing-go" +) + +// Tracer extends the opentracing.Tracer interface with methods to +// probe implementation state, for use by basictracer consumers. +type Tracer interface { + opentracing.Tracer + + // Options gets the Options used in New() or NewWithOptions(). + Options() Options +} + +// Options allows creating a customized Tracer via NewWithOptions. The object +// must not be updated when there is an active tracer using it. +type Options struct { + // ShouldSample is a function which is called when creating a new Span and + // determines whether that Span is sampled. The randomized TraceID is supplied + // to allow deterministic sampling decisions to be made across different nodes. + // For example, + // + // func(traceID uint64) { return traceID % 64 == 0 } + // + // samples every 64th trace on average. + ShouldSample func(traceID uint64) bool + // TrimUnsampledSpans turns potentially expensive operations on unsampled + // Spans into no-ops. More precisely, tags and log events are silently + // discarded. If NewSpanEventListener is set, the callbacks will still fire. + TrimUnsampledSpans bool + // Recorder receives Spans which have been finished. + Recorder SpanRecorder + // NewSpanEventListener can be used to enhance the tracer by effectively + // attaching external code to trace events. See NetTraceIntegrator for a + // practical example, and event.go for the list of possible events. + NewSpanEventListener func() func(SpanEvent) + // DropAllLogs turns log events on all Spans into no-ops. + // If NewSpanEventListener is set, the callbacks will still fire. + DropAllLogs bool + // MaxLogsPerSpan limits the number of Logs in a span (if set to a nonzero + // value). If a span has more logs than this value, logs are dropped as + // necessary (and replaced with a log describing how many were dropped). + // + // About half of the MaxLogPerSpan logs kept are the oldest logs, and about + // half are the newest logs. + // + // If NewSpanEventListener is set, the callbacks will still fire for all log + // events. This value is ignored if DropAllLogs is true. + MaxLogsPerSpan int + // DebugAssertSingleGoroutine internally records the ID of the goroutine + // creating each Span and verifies that no operation is carried out on + // it on a different goroutine. + // Provided strictly for development purposes. + // Passing Spans between goroutine without proper synchronization often + // results in use-after-Finish() errors. For a simple example, consider the + // following pseudocode: + // + // func (s *Server) Handle(req http.Request) error { + // sp := s.StartSpan("server") + // defer sp.Finish() + // wait := s.queueProcessing(opentracing.ContextWithSpan(context.Background(), sp), req) + // select { + // case resp := <-wait: + // return resp.Error + // case <-time.After(10*time.Second): + // sp.LogEvent("timed out waiting for processing") + // return ErrTimedOut + // } + // } + // + // This looks reasonable at first, but a request which spends more than ten + // seconds in the queue is abandoned by the main goroutine and its trace + // finished, leading to use-after-finish when the request is finally + // processed. Note also that even joining on to a finished Span via + // StartSpanWithOptions constitutes an illegal operation. + // + // Code bases which do not require (or decide they do not want) Spans to + // be passed across goroutine boundaries can run with this flag enabled in + // tests to increase their chances of spotting wrong-doers. + DebugAssertSingleGoroutine bool + // DebugAssertUseAfterFinish is provided strictly for development purposes. + // When set, it attempts to exacerbate issues emanating from use of Spans + // after calling Finish by running additional assertions. + DebugAssertUseAfterFinish bool + // EnableSpanPool enables the use of a pool, so that the tracer reuses spans + // after Finish has been called on it. Adds a slight performance gain as it + // reduces allocations. However, if you have any use-after-finish race + // conditions the code may panic. + EnableSpanPool bool +} + +// DefaultOptions returns an Options object with a 1 in 64 sampling rate and +// all options disabled. A Recorder needs to be set manually before using the +// returned object with a Tracer. +func DefaultOptions() Options { + return Options{ + ShouldSample: func(traceID uint64) bool { return traceID%64 == 0 }, + MaxLogsPerSpan: 100, + } +} + +// NewWithOptions creates a customized Tracer. +func NewWithOptions(opts Options) opentracing.Tracer { + rval := &tracerImpl{options: opts} + rval.textPropagator = &textMapPropagator{rval} + rval.binaryPropagator = &binaryPropagator{rval} + rval.accessorPropagator = &accessorPropagator{rval} + return rval +} + +// New creates and returns a standard Tracer which defers completed Spans to +// `recorder`. +// Spans created by this Tracer support the ext.SamplingPriority tag: Setting +// ext.SamplingPriority causes the Span to be Sampled from that point on. +func New(recorder SpanRecorder) opentracing.Tracer { + opts := DefaultOptions() + opts.Recorder = recorder + return NewWithOptions(opts) +} + +// Implements the `Tracer` interface. +type tracerImpl struct { + options Options + textPropagator *textMapPropagator + binaryPropagator *binaryPropagator + accessorPropagator *accessorPropagator +} + +func (t *tracerImpl) StartSpan( + operationName string, + opts ...opentracing.StartSpanOption, +) opentracing.Span { + sso := opentracing.StartSpanOptions{} + for _, o := range opts { + o.Apply(&sso) + } + return t.StartSpanWithOptions(operationName, sso) +} + +func (t *tracerImpl) getSpan() *spanImpl { + if t.options.EnableSpanPool { + sp := spanPool.Get().(*spanImpl) + sp.reset() + return sp + } + return &spanImpl{} +} + +func (t *tracerImpl) StartSpanWithOptions( + operationName string, + opts opentracing.StartSpanOptions, +) opentracing.Span { + // Start time. + startTime := opts.StartTime + if startTime.IsZero() { + startTime = time.Now() + } + + // Tags. + tags := opts.Tags + + // Build the new span. This is the only allocation: We'll return this as + // an opentracing.Span. + sp := t.getSpan() + // Look for a parent in the list of References. + // + // TODO: would be nice if basictracer did something with all + // References, not just the first one. +ReferencesLoop: + for _, ref := range opts.References { + switch ref.Type { + case opentracing.ChildOfRef, + opentracing.FollowsFromRef: + + refCtx := ref.ReferencedContext.(SpanContext) + sp.raw.Context.TraceID = refCtx.TraceID + sp.raw.Context.SpanID = randomID() + sp.raw.Context.Sampled = refCtx.Sampled + sp.raw.ParentSpanID = refCtx.SpanID + + if l := len(refCtx.Baggage); l > 0 { + sp.raw.Context.Baggage = make(map[string]string, l) + for k, v := range refCtx.Baggage { + sp.raw.Context.Baggage[k] = v + } + } + break ReferencesLoop + } + } + if sp.raw.Context.TraceID == 0 { + // No parent Span found; allocate new trace and span ids and determine + // the Sampled status. + sp.raw.Context.TraceID, sp.raw.Context.SpanID = randomID2() + sp.raw.Context.Sampled = t.options.ShouldSample(sp.raw.Context.TraceID) + } + + return t.startSpanInternal( + sp, + operationName, + startTime, + tags, + ) +} + +func (t *tracerImpl) startSpanInternal( + sp *spanImpl, + operationName string, + startTime time.Time, + tags opentracing.Tags, +) opentracing.Span { + sp.tracer = t + if t.options.NewSpanEventListener != nil { + sp.event = t.options.NewSpanEventListener() + } + sp.raw.Operation = operationName + sp.raw.Start = startTime + sp.raw.Duration = -1 + sp.raw.Tags = tags + if t.options.DebugAssertSingleGoroutine { + sp.SetTag(debugGoroutineIDTag, curGoroutineID()) + } + defer sp.onCreate(operationName) + return sp +} + +type delegatorType struct{} + +// Delegator is the format to use for DelegatingCarrier. +var Delegator delegatorType + +func (t *tracerImpl) Inject(sc opentracing.SpanContext, format interface{}, carrier interface{}) error { + switch format { + case opentracing.TextMap, opentracing.HTTPHeaders: + return t.textPropagator.Inject(sc, carrier) + case opentracing.Binary: + return t.binaryPropagator.Inject(sc, carrier) + } + if _, ok := format.(delegatorType); ok { + return t.accessorPropagator.Inject(sc, carrier) + } + return opentracing.ErrUnsupportedFormat +} + +func (t *tracerImpl) Extract(format interface{}, carrier interface{}) (opentracing.SpanContext, error) { + switch format { + case opentracing.TextMap, opentracing.HTTPHeaders: + return t.textPropagator.Extract(carrier) + case opentracing.Binary: + return t.binaryPropagator.Extract(carrier) + } + if _, ok := format.(delegatorType); ok { + return t.accessorPropagator.Extract(carrier) + } + return nil, opentracing.ErrUnsupportedFormat +} + +func (t *tracerImpl) Options() Options { + return t.options +} diff --git a/vendor/github.com/opentracing/basictracer-go/util.go b/vendor/github.com/opentracing/basictracer-go/util.go new file mode 100644 index 000000000..0f4ed8ac9 --- /dev/null +++ b/vendor/github.com/opentracing/basictracer-go/util.go @@ -0,0 +1,25 @@ +package basictracer + +import ( + "math/rand" + "sync" + "time" +) + +var ( + seededIDGen = rand.New(rand.NewSource(time.Now().UnixNano())) + // The golang rand generators are *not* intrinsically thread-safe. + seededIDLock sync.Mutex +) + +func randomID() uint64 { + seededIDLock.Lock() + defer seededIDLock.Unlock() + return uint64(seededIDGen.Int63()) +} + +func randomID2() (uint64, uint64) { + seededIDLock.Lock() + defer seededIDLock.Unlock() + return uint64(seededIDGen.Int63()), uint64(seededIDGen.Int63()) +} diff --git a/vendor/github.com/opentracing/basictracer-go/wire/carrier.go b/vendor/github.com/opentracing/basictracer-go/wire/carrier.go new file mode 100644 index 000000000..12ec98e90 --- /dev/null +++ b/vendor/github.com/opentracing/basictracer-go/wire/carrier.go @@ -0,0 +1,40 @@ +package wire + +// ProtobufCarrier is a DelegatingCarrier that uses protocol buffers as the +// the underlying datastructure. The reason for implementing DelagatingCarrier +// is to allow for end users to serialize the underlying protocol buffers using +// jsonpb or any other serialization forms they want. +type ProtobufCarrier TracerState + +// SetState set's the tracer state. +func (p *ProtobufCarrier) SetState(traceID, spanID uint64, sampled bool) { + p.TraceId = traceID + p.SpanId = spanID + p.Sampled = sampled +} + +// State returns the tracer state. +func (p *ProtobufCarrier) State() (traceID, spanID uint64, sampled bool) { + traceID = p.TraceId + spanID = p.SpanId + sampled = p.Sampled + return traceID, spanID, sampled +} + +// SetBaggageItem sets a baggage item. +func (p *ProtobufCarrier) SetBaggageItem(key, value string) { + if p.BaggageItems == nil { + p.BaggageItems = map[string]string{key: value} + return + } + + p.BaggageItems[key] = value +} + +// GetBaggage iterates over each baggage item and executes the callback with +// the key:value pair. +func (p *ProtobufCarrier) GetBaggage(f func(k, v string)) { + for k, v := range p.BaggageItems { + f(k, v) + } +} diff --git a/vendor/github.com/opentracing/basictracer-go/wire/gen.go b/vendor/github.com/opentracing/basictracer-go/wire/gen.go new file mode 100644 index 000000000..ce48e8b8d --- /dev/null +++ b/vendor/github.com/opentracing/basictracer-go/wire/gen.go @@ -0,0 +1,6 @@ +package wire + +//go:generate protoc --gogofaster_out=$GOPATH/src/github.com/opentracing/basictracer-go/wire wire.proto + +// Run `go get github.com/gogo/protobuf/protoc-gen-gogofaster` to install the +// gogofaster generator binary. diff --git a/vendor/github.com/opentracing/basictracer-go/wire/wire.pb.go b/vendor/github.com/opentracing/basictracer-go/wire/wire.pb.go new file mode 100644 index 000000000..b78d7ea91 --- /dev/null +++ b/vendor/github.com/opentracing/basictracer-go/wire/wire.pb.go @@ -0,0 +1,508 @@ +// Code generated by protoc-gen-gogo. +// source: wire.proto +// DO NOT EDIT! + +/* + Package wire is a generated protocol buffer package. + + It is generated from these files: + wire.proto + + It has these top-level messages: + TracerState +*/ +package wire + +import proto "github.com/gogo/protobuf/proto" +import fmt "fmt" +import math "math" + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +const _ = proto.GoGoProtoPackageIsVersion1 + +type TracerState struct { + TraceId uint64 `protobuf:"fixed64,1,opt,name=trace_id,json=traceId,proto3" json:"trace_id,omitempty"` + SpanId uint64 `protobuf:"fixed64,2,opt,name=span_id,json=spanId,proto3" json:"span_id,omitempty"` + Sampled bool `protobuf:"varint,3,opt,name=sampled,proto3" json:"sampled,omitempty"` + BaggageItems map[string]string `protobuf:"bytes,4,rep,name=baggage_items,json=baggageItems" json:"baggage_items,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (m *TracerState) Reset() { *m = TracerState{} } +func (m *TracerState) String() string { return proto.CompactTextString(m) } +func (*TracerState) ProtoMessage() {} +func (*TracerState) Descriptor() ([]byte, []int) { return fileDescriptorWire, []int{0} } + +func (m *TracerState) GetBaggageItems() map[string]string { + if m != nil { + return m.BaggageItems + } + return nil +} + +func init() { + proto.RegisterType((*TracerState)(nil), "basictracer_go.wire.TracerState") +} +func (m *TracerState) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *TracerState) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.TraceId != 0 { + data[i] = 0x9 + i++ + i = encodeFixed64Wire(data, i, uint64(m.TraceId)) + } + if m.SpanId != 0 { + data[i] = 0x11 + i++ + i = encodeFixed64Wire(data, i, uint64(m.SpanId)) + } + if m.Sampled { + data[i] = 0x18 + i++ + if m.Sampled { + data[i] = 1 + } else { + data[i] = 0 + } + i++ + } + if len(m.BaggageItems) > 0 { + for k, _ := range m.BaggageItems { + data[i] = 0x22 + i++ + v := m.BaggageItems[k] + mapSize := 1 + len(k) + sovWire(uint64(len(k))) + 1 + len(v) + sovWire(uint64(len(v))) + i = encodeVarintWire(data, i, uint64(mapSize)) + data[i] = 0xa + i++ + i = encodeVarintWire(data, i, uint64(len(k))) + i += copy(data[i:], k) + data[i] = 0x12 + i++ + i = encodeVarintWire(data, i, uint64(len(v))) + i += copy(data[i:], v) + } + } + return i, nil +} + +func encodeFixed64Wire(data []byte, offset int, v uint64) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + data[offset+4] = uint8(v >> 32) + data[offset+5] = uint8(v >> 40) + data[offset+6] = uint8(v >> 48) + data[offset+7] = uint8(v >> 56) + return offset + 8 +} +func encodeFixed32Wire(data []byte, offset int, v uint32) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + return offset + 4 +} +func encodeVarintWire(data []byte, offset int, v uint64) int { + for v >= 1<<7 { + data[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + data[offset] = uint8(v) + return offset + 1 +} +func (m *TracerState) Size() (n int) { + var l int + _ = l + if m.TraceId != 0 { + n += 9 + } + if m.SpanId != 0 { + n += 9 + } + if m.Sampled { + n += 2 + } + if len(m.BaggageItems) > 0 { + for k, v := range m.BaggageItems { + _ = k + _ = v + mapEntrySize := 1 + len(k) + sovWire(uint64(len(k))) + 1 + len(v) + sovWire(uint64(len(v))) + n += mapEntrySize + 1 + sovWire(uint64(mapEntrySize)) + } + } + return n +} + +func sovWire(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozWire(x uint64) (n int) { + return sovWire(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *TracerState) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowWire + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: TracerState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: TracerState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field TraceId", wireType) + } + m.TraceId = 0 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 8 + m.TraceId = uint64(data[iNdEx-8]) + m.TraceId |= uint64(data[iNdEx-7]) << 8 + m.TraceId |= uint64(data[iNdEx-6]) << 16 + m.TraceId |= uint64(data[iNdEx-5]) << 24 + m.TraceId |= uint64(data[iNdEx-4]) << 32 + m.TraceId |= uint64(data[iNdEx-3]) << 40 + m.TraceId |= uint64(data[iNdEx-2]) << 48 + m.TraceId |= uint64(data[iNdEx-1]) << 56 + case 2: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field SpanId", wireType) + } + m.SpanId = 0 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 8 + m.SpanId = uint64(data[iNdEx-8]) + m.SpanId |= uint64(data[iNdEx-7]) << 8 + m.SpanId |= uint64(data[iNdEx-6]) << 16 + m.SpanId |= uint64(data[iNdEx-5]) << 24 + m.SpanId |= uint64(data[iNdEx-4]) << 32 + m.SpanId |= uint64(data[iNdEx-3]) << 40 + m.SpanId |= uint64(data[iNdEx-2]) << 48 + m.SpanId |= uint64(data[iNdEx-1]) << 56 + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Sampled", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowWire + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.Sampled = bool(v != 0) + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BaggageItems", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowWire + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthWire + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var keykey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowWire + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + keykey |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + var stringLenmapkey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowWire + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLenmapkey |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapkey := int(stringLenmapkey) + if intStringLenmapkey < 0 { + return ErrInvalidLengthWire + } + postStringIndexmapkey := iNdEx + intStringLenmapkey + if postStringIndexmapkey > l { + return io.ErrUnexpectedEOF + } + mapkey := string(data[iNdEx:postStringIndexmapkey]) + iNdEx = postStringIndexmapkey + var valuekey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowWire + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + valuekey |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + var stringLenmapvalue uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowWire + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLenmapvalue |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapvalue := int(stringLenmapvalue) + if intStringLenmapvalue < 0 { + return ErrInvalidLengthWire + } + postStringIndexmapvalue := iNdEx + intStringLenmapvalue + if postStringIndexmapvalue > l { + return io.ErrUnexpectedEOF + } + mapvalue := string(data[iNdEx:postStringIndexmapvalue]) + iNdEx = postStringIndexmapvalue + if m.BaggageItems == nil { + m.BaggageItems = make(map[string]string) + } + m.BaggageItems[mapkey] = mapvalue + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipWire(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthWire + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipWire(data []byte) (n int, err error) { + l := len(data) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowWire + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowWire + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if data[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowWire + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthWire + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowWire + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipWire(data[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthWire = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowWire = fmt.Errorf("proto: integer overflow") +) + +var fileDescriptorWire = []byte{ + // 234 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x2a, 0xcf, 0x2c, 0x4a, + 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x4e, 0x4a, 0x2c, 0xce, 0x4c, 0x2e, 0x29, 0x4a, + 0x4c, 0x4e, 0x2d, 0x8a, 0x4f, 0xcf, 0xd7, 0x03, 0x49, 0x29, 0x7d, 0x65, 0xe4, 0xe2, 0x0e, 0x01, + 0x0b, 0x05, 0x97, 0x24, 0x96, 0xa4, 0x0a, 0x49, 0x72, 0x71, 0x80, 0x55, 0xc4, 0x67, 0xa6, 0x48, + 0x30, 0x2a, 0x30, 0x6a, 0xb0, 0x05, 0xb1, 0x83, 0xf9, 0x9e, 0x29, 0x42, 0xe2, 0x5c, 0xec, 0xc5, + 0x05, 0x89, 0x79, 0x20, 0x19, 0x26, 0xb0, 0x0c, 0x1b, 0x88, 0x0b, 0x94, 0x90, 0x00, 0x4a, 0x24, + 0xe6, 0x16, 0xe4, 0xa4, 0xa6, 0x48, 0x30, 0x03, 0x25, 0x38, 0x82, 0x60, 0x5c, 0xa1, 0x70, 0x2e, + 0xde, 0xa4, 0xc4, 0xf4, 0xf4, 0xc4, 0x74, 0xa0, 0x79, 0x25, 0xa9, 0xb9, 0xc5, 0x12, 0x2c, 0x0a, + 0xcc, 0x1a, 0xdc, 0x46, 0x46, 0x7a, 0x58, 0x9c, 0xa2, 0x87, 0xe4, 0x0c, 0x3d, 0x27, 0x88, 0x2e, + 0x4f, 0x90, 0x26, 0xd7, 0xbc, 0x92, 0xa2, 0xca, 0x20, 0x9e, 0x24, 0x24, 0x21, 0x29, 0x7b, 0x2e, + 0x41, 0x0c, 0x25, 0x42, 0x02, 0x5c, 0xcc, 0xd9, 0xa9, 0x95, 0x60, 0x67, 0x73, 0x06, 0x81, 0x98, + 0x42, 0x22, 0x5c, 0xac, 0x65, 0x89, 0x39, 0xa5, 0xa9, 0x60, 0x07, 0x73, 0x06, 0x41, 0x38, 0x56, + 0x4c, 0x16, 0x8c, 0x4e, 0x62, 0x27, 0x1e, 0xc9, 0x31, 0x5e, 0x00, 0xe2, 0x07, 0x40, 0x3c, 0xe1, + 0xb1, 0x1c, 0x43, 0x14, 0x0b, 0xc8, 0x11, 0x49, 0x6c, 0xe0, 0xb0, 0x32, 0x06, 0x04, 0x00, 0x00, + 0xff, 0xff, 0x0a, 0x20, 0x89, 0x38, 0x39, 0x01, 0x00, 0x00, +}