1
0
Fork 0

Instana tracer implementation

This commit is contained in:
Kevin Crawley 2019-02-18 09:52:04 -06:00 committed by Traefiker Bot
parent c2c6aee18a
commit aef24dd74b
43 changed files with 4502 additions and 2 deletions

21
vendor/github.com/instana/go-sensor/LICENSE.md generated vendored Normal file
View file

@ -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.

183
vendor/github.com/instana/go-sensor/agent.go generated vendored Normal file
View file

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

42
vendor/github.com/instana/go-sensor/context.go generated vendored Normal file
View file

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

37
vendor/github.com/instana/go-sensor/eum.go generated vendored Normal file
View file

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

78
vendor/github.com/instana/go-sensor/event.go generated vendored Normal file
View file

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

242
vendor/github.com/instana/go-sensor/fsm.go generated vendored Normal file
View file

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

38
vendor/github.com/instana/go-sensor/json_span.go generated vendored Normal file
View file

@ -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"`
}

52
vendor/github.com/instana/go-sensor/log.go generated vendored Normal file
View file

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

157
vendor/github.com/instana/go-sensor/meter.go generated vendored Normal file
View file

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

12
vendor/github.com/instana/go-sensor/options.go generated vendored Normal file
View file

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

165
vendor/github.com/instana/go-sensor/propagation.go generated vendored Normal file
View file

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

175
vendor/github.com/instana/go-sensor/recorder.go generated vendored Normal file
View file

@ -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()
}
}()
}
}

76
vendor/github.com/instana/go-sensor/sensor.go generated vendored Normal file
View file

@ -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")
}
}

253
vendor/github.com/instana/go-sensor/span.go generated vendored Normal file
View file

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

119
vendor/github.com/instana/go-sensor/tracer.go generated vendored Normal file
View file

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

91
vendor/github.com/instana/go-sensor/tracer_options.go generated vendored Normal file
View file

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

99
vendor/github.com/instana/go-sensor/util.go generated vendored Normal file
View file

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