mirror of
https://github.com/hanzoai/traces.git
synced 2026-10-30 18:20:41 +00:00
351 lines
11 KiB
Go
351 lines
11 KiB
Go
package traceql
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
)
|
|
|
|
type Operands []Static
|
|
|
|
type Condition struct {
|
|
Attribute Attribute
|
|
Op Operator
|
|
Operands Operands
|
|
// Callback is used in the parquetquery.CallbackPredicate to determine if the iterator should be stopped.
|
|
// It's used to limit the overhead of fetching exemplars by stopping the iterator early.
|
|
CallBack func() bool
|
|
|
|
// Hints
|
|
Precision time.Duration
|
|
}
|
|
|
|
func SearchMetaConditions() []Condition {
|
|
return []Condition{
|
|
{Attribute: NewIntrinsic(IntrinsicTraceRootService), Op: OpNone},
|
|
{Attribute: NewIntrinsic(IntrinsicTraceRootSpan), Op: OpNone},
|
|
{Attribute: NewIntrinsic(IntrinsicTraceDuration), Op: OpNone},
|
|
{Attribute: NewIntrinsic(IntrinsicTraceID), Op: OpNone},
|
|
{Attribute: NewIntrinsic(IntrinsicTraceStartTime), Op: OpNone},
|
|
{Attribute: NewIntrinsic(IntrinsicSpanID), Op: OpNone},
|
|
{Attribute: NewIntrinsic(IntrinsicSpanStartTime), Op: OpNone},
|
|
{Attribute: NewIntrinsic(IntrinsicDuration), Op: OpNone},
|
|
{Attribute: NewIntrinsic(IntrinsicServiceStats), Op: OpNone},
|
|
}
|
|
}
|
|
|
|
func ExemplarMetaConditions(cb func() bool) []Condition {
|
|
return []Condition{
|
|
{Attribute: NewIntrinsic(IntrinsicTraceID), Op: OpNone, CallBack: cb},
|
|
}
|
|
}
|
|
|
|
func SearchMetaConditionsWithout(remove []Condition, allConditions bool) []Condition {
|
|
metaConds := SearchMetaConditions()
|
|
return metaConditionsWithout(metaConds, remove, allConditions)
|
|
}
|
|
|
|
func metaConditionsWithout(metaConds, remove []Condition, allConditions bool) []Condition {
|
|
retConds := make([]Condition, 0, len(metaConds))
|
|
for _, c := range metaConds {
|
|
// if we can't find c in the remove conditions then add it to retConds
|
|
found := false
|
|
for _, r := range remove {
|
|
if r.Attribute == c.Attribute {
|
|
// We can reuse the existing condition of a metadata field in two cases:
|
|
// (1) OpNone, since it has no filtering it will always return a value, there is no need to read it again.
|
|
// (2) AllConditions - No matter the operation, it will return a value for all results.
|
|
// If neither of those apply then we have to select
|
|
// the metadata field again without filtering.
|
|
if r.Op == OpNone || allConditions {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if !found {
|
|
retConds = append(retConds, c)
|
|
}
|
|
}
|
|
|
|
return retConds
|
|
}
|
|
|
|
func ExemplarMetaConditionsWithout(cb func() bool, remove []Condition, allConditions bool) []Condition {
|
|
return metaConditionsWithout(ExemplarMetaConditions(cb), remove, allConditions)
|
|
}
|
|
|
|
// SecondPassFn is a method that is called in between the first and second
|
|
// pass of a fetch spans request. See below.
|
|
type SecondPassFn func(*Spanset) ([]*Spanset, error)
|
|
|
|
type FetchSpansRequest struct {
|
|
StartTimeUnixNanos uint64
|
|
EndTimeUnixNanos uint64
|
|
Conditions []Condition
|
|
|
|
// Hints
|
|
|
|
// By default, the storage layer fetches spans meeting any of the criteria.
|
|
// This hint is for common cases like { x && y && z } where the storage layer
|
|
// can make extra optimizations by returning only spansets that meet
|
|
// all criteria.
|
|
AllConditions bool
|
|
|
|
// Sampling
|
|
// There are two methods of sampling: trace-level samples a subset of traces,
|
|
// either skipping them or returning them in full. Span-level samples a
|
|
// subset of spans within each trace, still returning a spanset for each
|
|
// matching trace. Only one will be applied at a time. These fields are
|
|
// optional and there is no negative effect on the results if they are not
|
|
// honored at the storage layer.
|
|
TraceSampler Sampler
|
|
SpanSampler Sampler
|
|
|
|
// SecondPassFn and Conditions allow a caller to retrieve one set of data
|
|
// in the first pass, filter using the SecondPassFn callback and then
|
|
// request a different set of data in the second pass. This is particularly
|
|
// useful for retrieving data required to resolve a TraceQL query in the first
|
|
// pass and only selecting metadata in the second pass.
|
|
// TODO: extend this to an arbitrary number of passes
|
|
SecondPass SecondPassFn
|
|
SecondPassConditions []Condition
|
|
SecondPassSelectAll bool // Ignore second pass conditions and select all attributes
|
|
}
|
|
|
|
func (f *FetchSpansRequest) appendCondition(c ...Condition) {
|
|
f.Conditions = append(f.Conditions, c...)
|
|
}
|
|
|
|
func (f *FetchSpansRequest) HasAttribute(a Attribute) bool {
|
|
for _, cc := range f.Conditions {
|
|
if cc.Attribute == a {
|
|
return true
|
|
}
|
|
}
|
|
for _, cc := range f.SecondPassConditions {
|
|
if cc.Attribute == a {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (f *FetchSpansRequest) SecondPassHasAttribute(a Attribute) bool {
|
|
for _, cc := range f.SecondPassConditions {
|
|
if cc.Attribute == a {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (f *FetchSpansRequest) HasAttributeWithOp(a Attribute, o Operator) bool {
|
|
for _, cc := range f.Conditions {
|
|
if cc.Attribute == a && cc.Op == o {
|
|
return true
|
|
}
|
|
}
|
|
|
|
for _, cc := range f.SecondPassConditions {
|
|
if cc.Attribute == a && cc.Op == o {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
type Span interface {
|
|
// AttributeFor returns the attribute for the given key. If the attribute is not found then
|
|
// the second return value will be false.
|
|
AttributeFor(Attribute) (Static, bool)
|
|
// AllAttributes returns a map of all attributes for this span. AllAttributes should be used sparingly
|
|
// and is expected to be significantly slower than AttributeFor.
|
|
AllAttributes() map[Attribute]Static
|
|
// AllAttributesFunc is a way to access all attributes for this span, letting the span determine the
|
|
// optimal method. Avoids allocating a map like AllAttributes.
|
|
AllAttributesFunc(func(Attribute, Static))
|
|
|
|
ID() []byte
|
|
StartTimeUnixNanos() uint64
|
|
DurationNanos() uint64
|
|
|
|
// SiblingOf returns all spans on the RHS side that have siblings in the LHS. If falseForAll is true
|
|
// then the returned spans will be those that do not have siblings in the LHS. buffer is an optional
|
|
// buffer to use to avoid allocations.
|
|
SiblingOf(lhs []Span, rhs []Span, falseForAll bool, union bool, buffer []Span) []Span
|
|
// DescendantOf returns all spans on the RHS side that have descendants in the LHS. If falseForAll is true
|
|
// then the returned spans will be those that do not have descendants in the LHS. invert is used to invert
|
|
// the relationship. If invert is true then this will behave like "AncestorOf". buffer is an optional
|
|
// buffer to use to avoid allocations.
|
|
DescendantOf(lhs []Span, rhs []Span, falseForAll bool, invert bool, union bool, buffer []Span) []Span
|
|
// ChildOf returns all spans on the RHS side that have children in the LHS. If falseForAll is true
|
|
// then the returned spans will be those that do not have children in the LHS. invert is used to invert
|
|
// the relationship. If invert is true then this will behave like "ParentOf". buffer is an optional
|
|
// buffer to use to avoid allocations.
|
|
ChildOf(lhs []Span, rhs []Span, falseForAll bool, invert bool, union bool, buffer []Span) []Span
|
|
}
|
|
|
|
// should we just make matched a field on the spanset instead of a special attribute?
|
|
const attributeMatched = "__matched"
|
|
|
|
type SpansetAttribute struct {
|
|
Name string
|
|
Val Static
|
|
}
|
|
|
|
type ServiceStats struct {
|
|
SpanCount uint32
|
|
ErrorCount uint32
|
|
}
|
|
|
|
type Spanset struct {
|
|
// these fields are actually used by the engine to evaluate queries
|
|
Scalar Static
|
|
Spans []Span
|
|
|
|
TraceID []byte
|
|
RootSpanName string
|
|
RootServiceName string
|
|
StartTimeUnixNanos uint64
|
|
DurationNanos uint64
|
|
ServiceStats map[string]ServiceStats
|
|
Attributes []*SpansetAttribute
|
|
|
|
// Set this function to provide upstream callers with a method to
|
|
// release this spanset and all its spans when finished. This method will be
|
|
// called with the spanset itself as the argument. This is done for a worthwhile
|
|
// memory savings as the same function pointer can then be reused across spansets.
|
|
ReleaseFn func(*Spanset)
|
|
}
|
|
|
|
func (s *Spanset) AddAttribute(key string, value Static) {
|
|
s.Attributes = append(s.Attributes, &SpansetAttribute{Name: key, Val: value})
|
|
}
|
|
|
|
// Release the spanset and all its span. This is just a wrapper of ReleaseFn that
|
|
// performs nil checks.
|
|
func (s *Spanset) Release() {
|
|
if s.ReleaseFn != nil {
|
|
s.ReleaseFn(s)
|
|
}
|
|
}
|
|
|
|
func (s *Spanset) clone() *Spanset {
|
|
ss := *s
|
|
return &ss
|
|
}
|
|
|
|
type SpansetIterator interface {
|
|
Next(context.Context) (*Spanset, error)
|
|
Close()
|
|
}
|
|
|
|
type FetchSpansResponse struct {
|
|
Results SpansetIterator
|
|
// callback to get the size of data read during Fetch
|
|
Bytes func() uint64
|
|
}
|
|
|
|
type SpansetFetcher interface {
|
|
Fetch(context.Context, FetchSpansRequest) (FetchSpansResponse, error)
|
|
}
|
|
|
|
// FetchTagValuesCallback is called to collect unique tag values.
|
|
// Returns true if it has exceeded the maximum number of results.
|
|
type FetchTagValuesCallback func(static Static) bool
|
|
|
|
type FetchTagValuesRequest struct {
|
|
Conditions []Condition
|
|
TagName Attribute
|
|
// TODO: Add start and end time?
|
|
}
|
|
|
|
type TagValuesFetcher interface {
|
|
Fetch(context.Context, FetchTagValuesRequest, FetchTagValuesCallback) error
|
|
}
|
|
|
|
type TagValuesFetcherWrapper struct {
|
|
f func(context.Context, FetchTagValuesRequest, FetchTagValuesCallback) error
|
|
}
|
|
|
|
var _ TagValuesFetcher = (*TagValuesFetcherWrapper)(nil)
|
|
|
|
func NewTagValuesFetcherWrapper(f func(context.Context, FetchTagValuesRequest, FetchTagValuesCallback) error) TagValuesFetcher {
|
|
return TagValuesFetcherWrapper{f}
|
|
}
|
|
|
|
func (s TagValuesFetcherWrapper) Fetch(ctx context.Context, request FetchTagValuesRequest, callback FetchTagValuesCallback) error {
|
|
return s.f(ctx, request, callback)
|
|
}
|
|
|
|
// MustExtractFetchSpansRequestWithMetadata parses the given traceql query and returns
|
|
// the storage layer conditions. Panics if the query fails to parse.
|
|
func MustExtractFetchSpansRequestWithMetadata(query string) FetchSpansRequest {
|
|
c, err := ExtractFetchSpansRequest(query)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
c.SecondPass = func(s *Spanset) ([]*Spanset, error) { return []*Spanset{s}, nil }
|
|
c.SecondPassConditions = SearchMetaConditions()
|
|
return c
|
|
}
|
|
|
|
// ExtractFetchSpansRequest parses the given traceql query and returns
|
|
// the storage layer conditions. Returns an error if the query fails to parse.
|
|
func ExtractFetchSpansRequest(query string) (FetchSpansRequest, error) {
|
|
ast, err := Parse(query)
|
|
if err != nil {
|
|
return FetchSpansRequest{}, err
|
|
}
|
|
|
|
req := FetchSpansRequest{
|
|
AllConditions: true,
|
|
}
|
|
|
|
ast.Pipeline.extractConditions(&req)
|
|
return req, nil
|
|
}
|
|
|
|
type SpansetFetcherWrapper struct {
|
|
f func(ctx context.Context, req FetchSpansRequest) (FetchSpansResponse, error)
|
|
}
|
|
|
|
var _ = (SpansetFetcher)(&SpansetFetcherWrapper{})
|
|
|
|
func NewSpansetFetcherWrapper(f func(ctx context.Context, req FetchSpansRequest) (FetchSpansResponse, error)) SpansetFetcher {
|
|
return SpansetFetcherWrapper{f}
|
|
}
|
|
|
|
func (s SpansetFetcherWrapper) Fetch(ctx context.Context, request FetchSpansRequest) (FetchSpansResponse, error) {
|
|
return s.f(ctx, request)
|
|
}
|
|
|
|
type FetchTagsCallback func(tag string, scope AttributeScope) bool
|
|
|
|
type FetchTagsRequest struct {
|
|
Conditions []Condition
|
|
Scope AttributeScope
|
|
// TODO: Add start and end time?
|
|
}
|
|
|
|
type TagNamesFetcher interface {
|
|
Fetch(context.Context, FetchTagsRequest, FetchTagsCallback) error
|
|
}
|
|
|
|
type TagNamesFetcherWrapper struct {
|
|
f func(context.Context, FetchTagsRequest, FetchTagsCallback) error
|
|
}
|
|
|
|
var _ TagNamesFetcher = (*TagNamesFetcherWrapper)(nil)
|
|
|
|
func NewTagNamesFetcherWrapper(f func(context.Context, FetchTagsRequest, FetchTagsCallback) error) TagNamesFetcher {
|
|
return TagNamesFetcherWrapper{f}
|
|
}
|
|
|
|
func (s TagNamesFetcherWrapper) Fetch(ctx context.Context, request FetchTagsRequest, callback FetchTagsCallback) error {
|
|
return s.f(ctx, request, callback)
|
|
}
|