Skip to content

Commit

Permalink
profiler: support agent-based deployments and have those as default. (D…
Browse files Browse the repository at this point in the history
…ataDog#668)

Changes to profile to default to connecting through the agent, when an API key is not set.
  • Loading branch information
AlexJF authored Jun 10, 2020
1 parent 5dd3603 commit afab3a8
Show file tree
Hide file tree
Showing 11 changed files with 450 additions and 188 deletions.
34 changes: 3 additions & 31 deletions ddtrace/tracer/transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,17 @@
package tracer

import (
"bufio"
"fmt"
"io"
"net"
"net/http"
"os"
"regexp"
"runtime"
"strconv"
"strings"
"time"

"gopkg.in/DataDog/dd-trace-go.v1/internal"
"gopkg.in/DataDog/dd-trace-go.v1/internal/version"
)

Expand Down Expand Up @@ -93,12 +92,8 @@ func newHTTPTransport(addr string, client *http.Client) *httpTransport {
"Datadog-Meta-Tracer-Version": version.Tag,
"Content-Type": "application/msgpack",
}
f, err := os.Open("/proc/self/cgroup")
if err == nil {
if id, ok := readContainerID(f); ok {
defaultHeaders["Datadog-Container-ID"] = id
}
f.Close()
if cid := internal.ContainerID(); cid != "" {
defaultHeaders["Datadog-Container-ID"] = cid
}
return &httpTransport{
traceURL: fmt.Sprintf("http://%s/v0.4/traces", resolveAddr(addr)),
Expand All @@ -107,29 +102,6 @@ func newHTTPTransport(addr string, client *http.Client) *httpTransport {
}
}

var (
// expLine matches a line in the /proc/self/cgroup file. It has a submatch for the last element (path), which contains the container ID.
expLine = regexp.MustCompile(`^\d+:[^:]*:(.+)$`)
// expContainerID matches contained IDs and sources. Source: https://github.com/Qard/container-info/blob/master/index.js
expContainerID = regexp.MustCompile(`([0-9a-f]{8}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{12}|[0-9a-f]{64})(?:.scope)?$`)
)

// readContainerID finds the first container ID reading from r and returns it.
func readContainerID(r io.Reader) (id string, ok bool) {
scn := bufio.NewScanner(r)
for scn.Scan() {
path := expLine.FindStringSubmatch(scn.Text())
if len(path) != 2 {
// invalid entry, continue
continue
}
if id := expContainerID.FindString(path[1]); id != "" {
return id, true
}
}
return "", false
}

func (t *httpTransport) send(p *payload) (body io.ReadCloser, err error) {
req, err := http.NewRequest("POST", t.traceURL, p)
if err != nil {
Expand Down
29 changes: 0 additions & 29 deletions ddtrace/tracer/transport_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,35 +30,6 @@ func TestMain(m *testing.M) {
os.Exit(m.Run())
}

func TestReadContainerID(t *testing.T) {
for in, out := range map[string]string{
`other_line
10:hugetlb:/kubepods/burstable/podfd52ef25-a87d-11e9-9423-0800271a638e/8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa
9:cpuset:/kubepods/burstable/podfd52ef25-a87d-11e9-9423-0800271a638e/8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa
8:pids:/kubepods/burstable/podfd52ef25-a87d-11e9-9423-0800271a638e/8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa
7:freezer:/kubepods/burstable/podfd52ef25-a87d-11e9-9423-0800271a638e/8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa
6:cpu,cpuacct:/kubepods/burstable/podfd52ef25-a87d-11e9-9423-0800271a638e/8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa
5:perf_event:/kubepods/burstable/podfd52ef25-a87d-11e9-9423-0800271a638e/8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa
4:blkio:/kubepods/burstable/podfd52ef25-a87d-11e9-9423-0800271a638e/8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa
3:devices:/kubepods/burstable/podfd52ef25-a87d-11e9-9423-0800271a638e/8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa
2:net_cls,net_prio:/kubepods/burstable/podfd52ef25-a87d-11e9-9423-0800271a638e/8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa`: "8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa",
"10:hugetlb:/kubepods/burstable/podfd52ef25-a87d-11e9-9423-0800271a638e/8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa": "8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa",
"10:hugetlb:/kubepods": "",
} {
id, ok := readContainerID(strings.NewReader(in))
if id != out {
t.Fatalf("%q -> %q", in, out)
}
if out == "" {
if ok {
t.Fatalf("%q: got ok=true", in)
}
} else if !ok {
t.Fatalf("%q: got ok=false", in)
}
}
}

// getTestSpan returns a Span with different fields set
func getTestSpan() *span {
return &span{
Expand Down
63 changes: 63 additions & 0 deletions internal/container.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-2020 Datadog, Inc.

package internal

import (
"bufio"
"io"
"os"
"regexp"
)

const (
// cgroupPath is the path to the cgroup file where we can find the container id if one exists.
cgroupPath = "/proc/self/cgroup"
)

var (
// expLine matches a line in the /proc/self/cgroup file. It has a submatch for the last element (path), which contains the container ID.
expLine = regexp.MustCompile(`^\d+:[^:]*:(.+)$`)
// expContainerID matches contained IDs and sources. Source: https://github.com/Qard/container-info/blob/master/index.js
expContainerID = regexp.MustCompile(`([0-9a-f]{8}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{12}|[0-9a-f]{64})(?:.scope)?$`)

// containerID is the containerID read at init from /proc/self/cgroup
containerID string
)

func init() {
containerID = readContainerID(cgroupPath)
}

// parseContainerID finds the first container ID reading from r and returns it.
func parseContainerID(r io.Reader) string {
scn := bufio.NewScanner(r)
for scn.Scan() {
path := expLine.FindStringSubmatch(scn.Text())
if len(path) != 2 {
// invalid entry, continue
continue
}
if id := expContainerID.FindString(path[1]); id != "" {
return id
}
}
return ""
}

// readContainerID attempts to return the container ID from the provided file path or empty on failure.
func readContainerID(fpath string) string {
f, err := os.Open(fpath)
if err != nil {
return ""
}
defer f.Close()
return parseContainerID(f)
}

// ContainerID attempts to return the container ID from /proc/self/cgroup or empty on failure.
func ContainerID() string {
return containerID
}
60 changes: 60 additions & 0 deletions internal/container_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-2020 Datadog, Inc.

package internal

import (
"io"
"io/ioutil"
"os"
"strings"
"testing"

"github.com/stretchr/testify/assert"
)

func TestReadContainerID(t *testing.T) {
for in, out := range map[string]string{
`other_line
10:hugetlb:/kubepods/burstable/podfd52ef25-a87d-11e9-9423-0800271a638e/8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa
9:cpuset:/kubepods/burstable/podfd52ef25-a87d-11e9-9423-0800271a638e/8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa
8:pids:/kubepods/burstable/podfd52ef25-a87d-11e9-9423-0800271a638e/8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa
7:freezer:/kubepods/burstable/podfd52ef25-a87d-11e9-9423-0800271a638e/8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa
6:cpu,cpuacct:/kubepods/burstable/podfd52ef25-a87d-11e9-9423-0800271a638e/8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa
5:perf_event:/kubepods/burstable/podfd52ef25-a87d-11e9-9423-0800271a638e/8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa
4:blkio:/kubepods/burstable/podfd52ef25-a87d-11e9-9423-0800271a638e/8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa
3:devices:/kubepods/burstable/podfd52ef25-a87d-11e9-9423-0800271a638e/8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa
2:net_cls,net_prio:/kubepods/burstable/podfd52ef25-a87d-11e9-9423-0800271a638e/8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa`: "8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa",
"10:hugetlb:/kubepods/burstable/podfd52ef25-a87d-11e9-9423-0800271a638e/8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa": "8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa",
"10:hugetlb:/kubepods": "",
} {
id := parseContainerID(strings.NewReader(in))
if id != out {
t.Fatalf("%q -> %q", in, out)
}
}
}

func TestReadContainerIDFromCgroup(t *testing.T) {
cid := "8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa"
cgroupContents := "10:hugetlb:/kubepods/burstable/podfd52ef25-a87d-11e9-9423-0800271a638e/" + cid

tmpFile, err := ioutil.TempFile(os.TempDir(), "fake-cgroup-")
if err != nil {
t.Fatalf("failed to create fake cgroup file: %v", err)
}
defer os.Remove(tmpFile.Name())
_, err = io.WriteString(tmpFile, cgroupContents)
if err != nil {
t.Fatalf("failed writing to fake cgroup file: %v", err)
}
err = tmpFile.Close()
if err != nil {
t.Fatalf("failed closing fake cgroup file: %v", err)
}

actualCID := readContainerID(tmpFile.Name())
assert.Equal(t, cid, actualCID)
}
48 changes: 43 additions & 5 deletions profiler/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@ package profiler

import (
"fmt"
"net"
"net/url"
"os"
"path/filepath"
"runtime"
"strings"
"time"

"gopkg.in/DataDog/dd-trace-go.v1/internal/log"
"gopkg.in/DataDog/dd-trace-go.v1/internal/version"

"github.com/DataDog/datadog-go/statsd"
)
Expand All @@ -35,15 +38,21 @@ const (
)

const (
defaultAPIURL = "https://intake.profile.datadoghq.com/v1/input"
defaultEnv = "none"
defaultAPIURL = "https://intake.profile.datadoghq.com/v1/input"
defaultAgentHost = "localhost"
defaultAgentPort = "8126"
defaultEnv = "none"
)

var defaultProfileTypes = []ProfileType{CPUProfile, HeapProfile}

type config struct {
apiKey string
apiURL string
apiKey string
// targetURL is the upload destination URL. It will be set by the profiler on start to either apiURL or agentURL
// based on the other options.
targetURL string
apiURL string // apiURL is the Datadog intake API URL
agentURL string // agentURL is the Datadog agent profiling URL
service, env string
hostname string
statsd StatsdClient
Expand Down Expand Up @@ -84,6 +93,17 @@ func defaultConfig() *config {
c.addProfileType(t)
}

agentHost, agentPort := defaultAgentHost, defaultAgentPort
if v := os.Getenv("DD_AGENT_HOST"); v != "" {
agentHost = v
}
if v := os.Getenv("DD_TRACE_AGENT_PORT"); v != "" {
agentPort = v
}
WithAgentAddr(net.JoinHostPort(agentHost, agentPort))(&c)
if v := os.Getenv("DD_API_KEY"); v != "" {
WithAPIKey(v)(&c)
}
if v := os.Getenv("DD_SITE"); v != "" {
WithSite(v)(&c)
}
Expand All @@ -105,13 +125,31 @@ func defaultConfig() *config {
WithTags(tag)(&c)
}
}
WithTags(
"profiler_version:"+version.Tag,
"runtime_version:"+strings.TrimPrefix(runtime.Version(), "go"),
"runtime_compiler:"+runtime.Compiler,
"runtime_arch:"+runtime.GOARCH,
"runtime_os:"+runtime.GOOS,
)(&c)
// not for public use
if v := os.Getenv("DD_PROFILING_URL"); v != "" {
WithURL(v)(&c)
}
return &c
}

// An Option is used to configure the profiler's behaviour.
type Option func(*config)

// WithAPIKey specifies the API key to use when connecting to the Datadog API.
// WithAgentAddr specifies the address to use when reaching the Datadog Agent.
func WithAgentAddr(hostport string) Option {
return func(cfg *config) {
cfg.agentURL = "http://" + hostport + "/profiling/v1/input"
}
}

// WithAPIKey specifies the API key to use when connecting to the Datadog API directly, skipping the agent.
func WithAPIKey(key string) Option {
return func(cfg *config) {
cfg.apiKey = key
Expand Down
Loading

0 comments on commit afab3a8

Please sign in to comment.