Instana tracer implementation
This commit is contained in:
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
21
vendor/github.com/instana/go-sensor/LICENSE.md
generated
vendored
Normal 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
183
vendor/github.com/instana/go-sensor/agent.go
generated
vendored
Normal 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
42
vendor/github.com/instana/go-sensor/context.go
generated
vendored
Normal 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
37
vendor/github.com/instana/go-sensor/eum.go
generated
vendored
Normal 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
78
vendor/github.com/instana/go-sensor/event.go
generated
vendored
Normal 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
242
vendor/github.com/instana/go-sensor/fsm.go
generated
vendored
Normal 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
38
vendor/github.com/instana/go-sensor/json_span.go
generated
vendored
Normal 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
52
vendor/github.com/instana/go-sensor/log.go
generated
vendored
Normal 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
157
vendor/github.com/instana/go-sensor/meter.go
generated
vendored
Normal 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
12
vendor/github.com/instana/go-sensor/options.go
generated
vendored
Normal 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
165
vendor/github.com/instana/go-sensor/propagation.go
generated
vendored
Normal 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
175
vendor/github.com/instana/go-sensor/recorder.go
generated
vendored
Normal 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
76
vendor/github.com/instana/go-sensor/sensor.go
generated
vendored
Normal 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
253
vendor/github.com/instana/go-sensor/span.go
generated
vendored
Normal 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
119
vendor/github.com/instana/go-sensor/tracer.go
generated
vendored
Normal 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
91
vendor/github.com/instana/go-sensor/tracer_options.go
generated
vendored
Normal 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
99
vendor/github.com/instana/go-sensor/util.go
generated
vendored
Normal 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
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue