datadog timeline 功能原理

285 阅读2分钟

在 felixge 的这篇文章中,介绍了 datadog 的 timeline 功能:

blog.felixge.de/debug-go-re…

复现

从 github 上 clone content-addressable-store 并运行:

https://github.com/DataDog/content-addressable-store
go build
DD_PROFILING_EXECUTION_TRACE_PERIOD=1m ./content-addressable-store

随意发送请求。

打开 datadog 页面,从 timeline 功能上可以发现。

goroutine 在不同阶段的耗时以及对应的堆栈:

  • syscall
  • blocking
  • waiting
  • stop thw world (gc)

截屏2024-07-15 上午11.13.33.png

在右上角点击下载数据。 截屏2024-07-15 上午11.14.29.png

使用 go tool trace 打开:

go tool trace /Users/jie.yang05/Downloads/20240715-030903_content-addressable-store_AZC0XnD1AADKLJyEuUszPQAA/go.trace

选择 user define task 区域

截屏2024-07-15 上午11.16.08.png

选择这条 5ms 的数据,点击 task:

截屏2024-07-15 上午11.20.17.png

刚才 datadog 那个请求对应的 trace:

截屏2024-07-15 上午11.22.02.png

原理。

go 原生的 runtime trace 功能,会在 goroutine 状态变化的时候记录调用堆栈。

datadog 在 start trace 的时候,将 span id 塞进 runtime trace 中,以进行数据关联。

github.com/DataDog/dd-…

	}
	span.goExecTraced = true
	// Task name is the resource (operationName) of the span, e.g.
	// "POST /foo/bar" (http) or "/foo/pkg.Method" (grpc).
	taskName := span.Resource
	// If the resource could contain PII (e.g. SQL query that's not using bind
	// arguments), play it safe and just use the span type as the taskName,
	// e.g. "sql".
	if !spanResourcePIISafe(span) {
		taskName = span.Type
	}
	end := noopTaskEnd
	if !globalinternal.IsExecutionTraced(ctx) {
		var task *rt.Task
		ctx, task = rt.NewTask(ctx, taskName)
		end = task.End
	} else {
		// We only want to skip task creation for this particular span,
		// not necessarily for child spans which can come from different
		// integrations. So update this context to be "not" execution
		// traced so that derived contexts used by child spans don't get
		// skipped.
		ctx = globalinternal.WithExecutionNotTraced(ctx)
	}
	var b [8]byte
	binary.LittleEndian.PutUint64(b[:], span.SpanID)
	// TODO: can we make string(b[:]) not allocate? e.g. with unsafe
	// shenanigans? rt.Log won't retain the message string, though perhaps
	// we can't assume that will always be the case.
	rt.Log(ctx, "datadog.uint64_span_id", string(b[:]))
	return ctx, end
}

优化。

如何进行数据的折叠以及显示优化。 使用 golang.org/x/exp/trace 读取 trace 数据。

实际上会发现一个 trace id 表示的 goroutine 会有非常多的 goroutine 状态转换,这些数据在渲染的时候需要经过良好的过滤,才能最终对用户有价值。

这种 timeline 功能,实际很像,对每个 syscall 都进行一个 fprobe 获取调用堆栈。 无法获取运行时候的调用堆栈。

运行时候的开销,可以借助 go 的 cpu profiler, 将 trace id 塞进 goroutine 的 label 中实现,以进行关联。