Skip to content

Commit

Permalink
fix: Trace rules, attributes, select and constraints.
Browse files Browse the repository at this point in the history
- Fix rules to use OTEL naming, handle missing attributes.
- Add default "select" clause in store if none provided.
- Add constraints to tempo requests.
- Enable domain smoke tests
  • Loading branch information
alanconway committed Nov 12, 2024
1 parent 333e47c commit 5efb889
Show file tree
Hide file tree
Showing 10 changed files with 145 additions and 256 deletions.
14 changes: 12 additions & 2 deletions doc/gen/domains.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -293,9 +293,19 @@ stores:
== Query


Query selector is a link:https://grafana.com/docs/tempo/latest/traceql/[TraceQL] query string.
Query selector has two forms: a link:https://grafana.com/docs/tempo/latest/traceql/[TraceQL] query string, or a list of trace IDs.

Example: `trace:span:{resource.kubernetes.namespace.name=~".+"}`
A link:https://grafana.com/docs/tempo/latest/traceql/[TraceQL] query selects spans from many traces that match the query criteria. Example:

----
`trace:span:{resource.kubernetes.namespace.name="korrel8r"}`
----

A trace-id query is a list of hexadecimal trace IDs. It returns all the spans included by each trace. Example:

----
`trace:span:a7880cc221e84e0d07b15993358811b7,b7880cc221e84e0d07b15993358811b7
----


See Go documentation for https://pkg.go.dev/github.com/korrel8r/korrel8r/pkg/domains/trace/#Query[Query]
Expand Down
13 changes: 11 additions & 2 deletions etc/korrel8r/rules/trace.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

rules:
- name: TraceToPod
start:
Expand All @@ -7,7 +8,11 @@ rules:
classes: [Pod]
result:
query: |-
k8s:Pod:{namespace: "{{.Attributes.k8s_namespace_name}}", name: "{{.Attributes.k8s_pod_name}}"}
{{- $namespace := get .Attributes "k8s.namespace.name"}}
{{- $name := get .Attributes "k8s.pod.name" }}
{{- if all $namespace $name -}}
k8s:Pod.v1.:{namespace: "{{$namespace}}", name: "{{$name}}"}
{{- end -}}
- name: PodToTrace
start:
Expand All @@ -17,4 +22,8 @@ rules:
domain: trace
result:
query: |-
trace:trace:{resource.k8s.namespace.name="{{.Namespace}}" && resource.k8s.pod.name="{{.Name}}"}
trace:span:{
{{- with .Namespace}}resource.k8s.namespace.name="{{.}}"{{end}}
{{- if and .Namespace .Name}}&&{{end}}
{{- with .Name}}resource.k8s.pod.name="{{.}}"{{end -}}
}
14 changes: 12 additions & 2 deletions etc/korrel8r/rules/trace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func Test_TraceToPod(t *testing.T) {
rule: "TraceToPod",
start: &trace.Span{
Context: trace.SpanContext{TraceID: "232323", SpanID: "3d48369744164bd0"},
Attributes: map[string]any{"k8s_namespace_name": "tracing-app-k6", "k8s_pod_name": "bar"},
Attributes: map[string]any{"k8s.namespace.name": "tracing-app-k6", "k8s.pod.name": "bar"},
},
want: `k8s:Pod.v1.:{"namespace":"tracing-app-k6","name":"bar"}`,
},
Expand All @@ -47,7 +47,17 @@ func Test_TraceFromPod(t *testing.T) {
{
rule: "PodToTrace",
start: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"}},
want: `trace:trace:{resource.k8s.namespace.name="bar" && resource.k8s.pod.name="foo"}`,
want: `trace:span:{resource.k8s.namespace.name="bar"&&resource.k8s.pod.name="foo"}`,
},
{
rule: "PodToTrace",
start: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "bar"}},
want: `trace:span:{resource.k8s.namespace.name="bar"}`,
},
{
rule: "PodToTrace",
start: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}},
want: `trace:span:{resource.k8s.pod.name="foo"}`,
},
} {
t.Run(x.rule, func(t *testing.T) {
Expand Down
61 changes: 57 additions & 4 deletions pkg/domains/trace/tempo.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@ package trace

import (
"context"
"fmt"
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
"time"

"github.com/korrel8r/korrel8r/pkg/korrel8r"
"github.com/korrel8r/korrel8r/pkg/korrel8r/impl"
Expand Down Expand Up @@ -52,17 +57,65 @@ func newClient(c *http.Client, base *url.URL) *client { return &client{hc: c, ba

// GetStack uses the TempoStack tenant API to get tracees for a TraceQL query with a Constraint.
func (c *client) GetStack(ctx context.Context, traceQL string, constraint *korrel8r.Constraint, collect func(*Span)) error {
return c.get(ctx, traceQL, collect)
return c.get(ctx, traceQL, constraint, collect)
}

const ( // Tempo query keywords and field names
query = "q"
statusAttr = "status"
)

func (c *client) get(ctx context.Context, traceQL string, collect func(*Span)) error {
var (
hasSelect = regexp.MustCompile(`\| *select *\(`)
defaultAttributes = strings.Join([]string{
"resource.http.method",
"resource.http.status_code",
"resource.http.target",
"resource.http.url",
"resource.k8s.deployment.name",
"resource.k8s.namespace.name",
"resource.k8s.node.name",
"resource.k8s.pod.ip",
"resource.k8s.pod.name",
"resource.k8s.pod.uid",
"resource.net.host.name",
"resource.net.host.port",
"resource.net.peer.name",
"resource.net.peer.port",
"resource.service.name",
}, ",")
)

// defaultSelect adds a default select statement to the query if there isn't one already.
func defaultSelect(traceQL string) string {
if hasSelect.FindString(traceQL) == "" {
return fmt.Sprintf("%v|select(%v)", traceQL, defaultAttributes)
}
return traceQL
}

func formatTime(t time.Time) string { return strconv.FormatInt(t.UTC().Unix(), 10) }

func (c *client) get(ctx context.Context, traceQL string, constraint *korrel8r.Constraint, collect func(*Span)) error {
// FIXME constraint
u := *c.base // Copy, don't modify base.
u.RawQuery = url.Values{query: []string{traceQL}}.Encode()
v := url.Values{query: []string{defaultSelect(traceQL)}}
if limit := constraint.GetLimit(); limit > 0 {
v.Add("limit", strconv.Itoa(limit)) // Limit is max number of traces, not spans.
}
start, end := constraint.GetStart(), constraint.GetEnd()
if !end.IsZero() {
v.Add("end", formatTime(end))
}
if !start.IsZero() {
v.Add("start", formatTime(start))
if end.IsZero() { // Can't have start without end.
v.Add("end", formatTime(time.Now()))
}
}

u.RawQuery = v.Encode()

var response tempoResponse
if err := impl.Get(ctx, &u, c.hc, &response); err != nil {
return err
Expand Down Expand Up @@ -95,7 +148,7 @@ func (tt *tempoTrace) collect(spans tempoSpanSet, collect func(*Span)) {
Status: Status{Code: StatusUnset}, // Default
}
span.Attributes = ts.Attributes.Map()
span.Attributes[otel.AttrServiceName] = tt.RootServiceName
span.Attributes[otel.AttrServiceName] = tt.RootServiceName // FIXME
// Tempo HTTP API stores span status description as "status" attribute.
// Move it to the status field and deduce the status code.
span.Status.Description, _ = span.Attributes[statusAttr].(string)
Expand Down
Loading

0 comments on commit 5efb889

Please sign in to comment.