diff --git a/pkg/proto/datadog/trace/stats.proto b/pkg/proto/datadog/trace/stats.proto index 45a2351c35c65..fae5d8d0b439f 100644 --- a/pkg/proto/datadog/trace/stats.proto +++ b/pkg/proto/datadog/trace/stats.proto @@ -62,6 +62,12 @@ message ClientStatsBucket { int64 agentTimeShift = 4; } +enum TraceRootFlag { + NOT_SET = 0; + TRUE = 1; + FALSE = 2; +} + // ClientGroupedStats aggregate stats on spans grouped by service, name, resource, status_code, type message ClientGroupedStats { string service = 1; @@ -82,4 +88,5 @@ message ClientGroupedStats { // peer_tags are supplementary tags that further describe a peer entity // E.g., `grpc.target` to describe the name of a gRPC peer, or `db.hostname` to describe the name of peer DB repeated string peer_tags = 16; + TraceRootFlag is_trace_root = 17; // this field's value is equal to span's ParentID == 0. } diff --git a/pkg/proto/pbgo/trace/stats.pb.go b/pkg/proto/pbgo/trace/stats.pb.go index b3ffa3ab127a3..f1d02751721b2 100644 --- a/pkg/proto/pbgo/trace/stats.pb.go +++ b/pkg/proto/pbgo/trace/stats.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.30.0 -// protoc v4.23.4 +// protoc v4.25.3 // source: datadog/trace/stats.proto package trace @@ -20,6 +20,55 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +type TraceRootFlag int32 + +const ( + TraceRootFlag_NOT_SET TraceRootFlag = 0 + TraceRootFlag_TRUE TraceRootFlag = 1 + TraceRootFlag_FALSE TraceRootFlag = 2 +) + +// Enum value maps for TraceRootFlag. +var ( + TraceRootFlag_name = map[int32]string{ + 0: "NOT_SET", + 1: "TRUE", + 2: "FALSE", + } + TraceRootFlag_value = map[string]int32{ + "NOT_SET": 0, + "TRUE": 1, + "FALSE": 2, + } +) + +func (x TraceRootFlag) Enum() *TraceRootFlag { + p := new(TraceRootFlag) + *p = x + return p +} + +func (x TraceRootFlag) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (TraceRootFlag) Descriptor() protoreflect.EnumDescriptor { + return file_datadog_trace_stats_proto_enumTypes[0].Descriptor() +} + +func (TraceRootFlag) Type() protoreflect.EnumType { + return &file_datadog_trace_stats_proto_enumTypes[0] +} + +func (x TraceRootFlag) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use TraceRootFlag.Descriptor instead. +func (TraceRootFlag) EnumDescriptor() ([]byte, []int) { + return file_datadog_trace_stats_proto_rawDescGZIP(), []int{0} +} + // StatsPayload is the payload used to send stats from the agent to the backend. type StatsPayload struct { state protoimpl.MessageState @@ -374,7 +423,8 @@ type ClientGroupedStats struct { SpanKind string `protobuf:"bytes,15,opt,name=span_kind,json=spanKind,proto3" json:"span_kind,omitempty"` // value of the span.kind tag on the span // peer_tags are supplementary tags that further describe a peer entity // E.g., `grpc.target` to describe the name of a gRPC peer, or `db.hostname` to describe the name of peer DB - PeerTags []string `protobuf:"bytes,16,rep,name=peer_tags,json=peerTags,proto3" json:"peer_tags,omitempty"` + PeerTags []string `protobuf:"bytes,16,rep,name=peer_tags,json=peerTags,proto3" json:"peer_tags,omitempty"` + IsTraceRoot TraceRootFlag `protobuf:"varint,17,opt,name=is_trace_root,json=isTraceRoot,proto3,enum=datadog.trace.TraceRootFlag" json:"is_trace_root,omitempty"` // this field's value is equal to span's ParentID == 0. } func (x *ClientGroupedStats) Reset() { @@ -514,6 +564,13 @@ func (x *ClientGroupedStats) GetPeerTags() []string { return nil } +func (x *ClientGroupedStats) GetIsTraceRoot() TraceRootFlag { + if x != nil { + return x.IsTraceRoot + } + return TraceRootFlag_NOT_SET +} + var File_datadog_trace_stats_proto protoreflect.FileDescriptor var file_datadog_trace_stats_proto_rawDesc = []byte{ @@ -574,7 +631,7 @@ var file_datadog_trace_stats_proto_rawDesc = []byte{ 0x6f, 0x75, 0x70, 0x65, 0x64, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x12, 0x26, 0x0a, 0x0e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x53, 0x68, 0x69, 0x66, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x61, 0x67, 0x65, 0x6e, 0x74, - 0x54, 0x69, 0x6d, 0x65, 0x53, 0x68, 0x69, 0x66, 0x74, 0x22, 0xc3, 0x03, 0x0a, 0x12, 0x43, 0x6c, + 0x54, 0x69, 0x6d, 0x65, 0x53, 0x68, 0x69, 0x66, 0x74, 0x22, 0x85, 0x04, 0x0a, 0x12, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x65, 0x64, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, @@ -602,9 +659,17 @@ var file_datadog_trace_stats_proto_rawDesc = []byte{ 0x1b, 0x0a, 0x09, 0x73, 0x70, 0x61, 0x6e, 0x5f, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x70, 0x61, 0x6e, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, 0x10, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x08, 0x70, 0x65, 0x65, 0x72, 0x54, 0x61, 0x67, 0x73, 0x4a, 0x04, 0x08, 0x0e, 0x10, 0x0f, 0x42, - 0x16, 0x5a, 0x14, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x62, 0x67, - 0x6f, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x08, 0x70, 0x65, 0x65, 0x72, 0x54, 0x61, 0x67, 0x73, 0x12, 0x40, 0x0a, 0x0d, 0x69, 0x73, 0x5f, + 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x1c, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x64, 0x6f, 0x67, 0x2e, 0x74, 0x72, 0x61, 0x63, 0x65, + 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x46, 0x6c, 0x61, 0x67, 0x52, 0x0b, + 0x69, 0x73, 0x54, 0x72, 0x61, 0x63, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x4a, 0x04, 0x08, 0x0e, 0x10, + 0x0f, 0x2a, 0x31, 0x0a, 0x0d, 0x54, 0x72, 0x61, 0x63, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x46, 0x6c, + 0x61, 0x67, 0x12, 0x0b, 0x0a, 0x07, 0x4e, 0x4f, 0x54, 0x5f, 0x53, 0x45, 0x54, 0x10, 0x00, 0x12, + 0x08, 0x0a, 0x04, 0x54, 0x52, 0x55, 0x45, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x46, 0x41, 0x4c, + 0x53, 0x45, 0x10, 0x02, 0x42, 0x16, 0x5a, 0x14, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2f, 0x70, 0x62, 0x67, 0x6f, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -619,22 +684,25 @@ func file_datadog_trace_stats_proto_rawDescGZIP() []byte { return file_datadog_trace_stats_proto_rawDescData } +var file_datadog_trace_stats_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_datadog_trace_stats_proto_msgTypes = make([]protoimpl.MessageInfo, 4) var file_datadog_trace_stats_proto_goTypes = []interface{}{ - (*StatsPayload)(nil), // 0: datadog.trace.StatsPayload - (*ClientStatsPayload)(nil), // 1: datadog.trace.ClientStatsPayload - (*ClientStatsBucket)(nil), // 2: datadog.trace.ClientStatsBucket - (*ClientGroupedStats)(nil), // 3: datadog.trace.ClientGroupedStats + (TraceRootFlag)(0), // 0: datadog.trace.TraceRootFlag + (*StatsPayload)(nil), // 1: datadog.trace.StatsPayload + (*ClientStatsPayload)(nil), // 2: datadog.trace.ClientStatsPayload + (*ClientStatsBucket)(nil), // 3: datadog.trace.ClientStatsBucket + (*ClientGroupedStats)(nil), // 4: datadog.trace.ClientGroupedStats } var file_datadog_trace_stats_proto_depIdxs = []int32{ - 1, // 0: datadog.trace.StatsPayload.stats:type_name -> datadog.trace.ClientStatsPayload - 2, // 1: datadog.trace.ClientStatsPayload.stats:type_name -> datadog.trace.ClientStatsBucket - 3, // 2: datadog.trace.ClientStatsBucket.stats:type_name -> datadog.trace.ClientGroupedStats - 3, // [3:3] is the sub-list for method output_type - 3, // [3:3] is the sub-list for method input_type - 3, // [3:3] is the sub-list for extension type_name - 3, // [3:3] is the sub-list for extension extendee - 0, // [0:3] is the sub-list for field type_name + 2, // 0: datadog.trace.StatsPayload.stats:type_name -> datadog.trace.ClientStatsPayload + 3, // 1: datadog.trace.ClientStatsPayload.stats:type_name -> datadog.trace.ClientStatsBucket + 4, // 2: datadog.trace.ClientStatsBucket.stats:type_name -> datadog.trace.ClientGroupedStats + 0, // 3: datadog.trace.ClientGroupedStats.is_trace_root:type_name -> datadog.trace.TraceRootFlag + 4, // [4:4] is the sub-list for method output_type + 4, // [4:4] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name } func init() { file_datadog_trace_stats_proto_init() } @@ -697,13 +765,14 @@ func file_datadog_trace_stats_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_datadog_trace_stats_proto_rawDesc, - NumEnums: 0, + NumEnums: 1, NumMessages: 4, NumExtensions: 0, NumServices: 0, }, GoTypes: file_datadog_trace_stats_proto_goTypes, DependencyIndexes: file_datadog_trace_stats_proto_depIdxs, + EnumInfos: file_datadog_trace_stats_proto_enumTypes, MessageInfos: file_datadog_trace_stats_proto_msgTypes, }.Build() File_datadog_trace_stats_proto = out.File diff --git a/pkg/proto/pbgo/trace/stats_gen.go b/pkg/proto/pbgo/trace/stats_gen.go index 2975ea578f271..2f594f9f81447 100644 --- a/pkg/proto/pbgo/trace/stats_gen.go +++ b/pkg/proto/pbgo/trace/stats_gen.go @@ -127,6 +127,16 @@ func (z *ClientGroupedStats) DecodeMsg(dc *msgp.Reader) (err error) { return } } + case "IsTraceRoot": + { + var zb0003 int32 + zb0003, err = dc.ReadInt32() + if err != nil { + err = msgp.WrapError(err, "IsTraceRoot") + return + } + z.IsTraceRoot = TraceRootFlag(zb0003) + } default: err = dc.Skip() if err != nil { @@ -140,9 +150,9 @@ func (z *ClientGroupedStats) DecodeMsg(dc *msgp.Reader) (err error) { // EncodeMsg implements msgp.Encodable func (z *ClientGroupedStats) EncodeMsg(en *msgp.Writer) (err error) { - // map header, size 15 + // map header, size 16 // write "Service" - err = en.Append(0x8f, 0xa7, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65) + err = en.Append(0xde, 0x0, 0x10, 0xa7, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65) if err != nil { return } @@ -298,15 +308,25 @@ func (z *ClientGroupedStats) EncodeMsg(en *msgp.Writer) (err error) { return } } + // write "IsTraceRoot" + err = en.Append(0xab, 0x49, 0x73, 0x54, 0x72, 0x61, 0x63, 0x65, 0x52, 0x6f, 0x6f, 0x74) + if err != nil { + return + } + err = en.WriteInt32(int32(z.IsTraceRoot)) + if err != nil { + err = msgp.WrapError(err, "IsTraceRoot") + return + } return } // MarshalMsg implements msgp.Marshaler func (z *ClientGroupedStats) MarshalMsg(b []byte) (o []byte, err error) { o = msgp.Require(b, z.Msgsize()) - // map header, size 15 + // map header, size 16 // string "Service" - o = append(o, 0x8f, 0xa7, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65) + o = append(o, 0xde, 0x0, 0x10, 0xa7, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65) o = msgp.AppendString(o, z.Service) // string "Name" o = append(o, 0xa4, 0x4e, 0x61, 0x6d, 0x65) @@ -353,6 +373,9 @@ func (z *ClientGroupedStats) MarshalMsg(b []byte) (o []byte, err error) { for za0001 := range z.PeerTags { o = msgp.AppendString(o, z.PeerTags[za0001]) } + // string "IsTraceRoot" + o = append(o, 0xab, 0x49, 0x73, 0x54, 0x72, 0x61, 0x63, 0x65, 0x52, 0x6f, 0x6f, 0x74) + o = msgp.AppendInt32(o, int32(z.IsTraceRoot)) return } @@ -477,6 +500,16 @@ func (z *ClientGroupedStats) UnmarshalMsg(bts []byte) (o []byte, err error) { return } } + case "IsTraceRoot": + { + var zb0003 int32 + zb0003, bts, err = msgp.ReadInt32Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "IsTraceRoot") + return + } + z.IsTraceRoot = TraceRootFlag(zb0003) + } default: bts, err = msgp.Skip(bts) if err != nil { @@ -491,10 +524,11 @@ func (z *ClientGroupedStats) UnmarshalMsg(bts []byte) (o []byte, err error) { // Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message func (z *ClientGroupedStats) Msgsize() (s int) { - s = 1 + 8 + msgp.StringPrefixSize + len(z.Service) + 5 + msgp.StringPrefixSize + len(z.Name) + 9 + msgp.StringPrefixSize + len(z.Resource) + 15 + msgp.Uint32Size + 5 + msgp.StringPrefixSize + len(z.Type) + 7 + msgp.StringPrefixSize + len(z.DBType) + 5 + msgp.Uint64Size + 7 + msgp.Uint64Size + 9 + msgp.Uint64Size + 10 + msgp.BytesPrefixSize + len(z.OkSummary) + 13 + msgp.BytesPrefixSize + len(z.ErrorSummary) + 11 + msgp.BoolSize + 13 + msgp.Uint64Size + 9 + msgp.StringPrefixSize + len(z.SpanKind) + 9 + msgp.ArrayHeaderSize + s = 3 + 8 + msgp.StringPrefixSize + len(z.Service) + 5 + msgp.StringPrefixSize + len(z.Name) + 9 + msgp.StringPrefixSize + len(z.Resource) + 15 + msgp.Uint32Size + 5 + msgp.StringPrefixSize + len(z.Type) + 7 + msgp.StringPrefixSize + len(z.DBType) + 5 + msgp.Uint64Size + 7 + msgp.Uint64Size + 9 + msgp.Uint64Size + 10 + msgp.BytesPrefixSize + len(z.OkSummary) + 13 + msgp.BytesPrefixSize + len(z.ErrorSummary) + 11 + msgp.BoolSize + 13 + msgp.Uint64Size + 9 + msgp.StringPrefixSize + len(z.SpanKind) + 9 + msgp.ArrayHeaderSize for za0001 := range z.PeerTags { s += msgp.StringPrefixSize + len(z.PeerTags[za0001]) } + s += 12 + msgp.Int32Size return } @@ -1704,3 +1738,55 @@ func (z *StatsPayload) Msgsize() (s int) { s += 13 + msgp.StringPrefixSize + len(z.AgentVersion) + 15 + msgp.BoolSize + 13 + msgp.BoolSize return } + +// DecodeMsg implements msgp.Decodable +func (z *TraceRootFlag) DecodeMsg(dc *msgp.Reader) (err error) { + { + var zb0001 int32 + zb0001, err = dc.ReadInt32() + if err != nil { + err = msgp.WrapError(err) + return + } + (*z) = TraceRootFlag(zb0001) + } + return +} + +// EncodeMsg implements msgp.Encodable +func (z TraceRootFlag) EncodeMsg(en *msgp.Writer) (err error) { + err = en.WriteInt32(int32(z)) + if err != nil { + err = msgp.WrapError(err) + return + } + return +} + +// MarshalMsg implements msgp.Marshaler +func (z TraceRootFlag) MarshalMsg(b []byte) (o []byte, err error) { + o = msgp.Require(b, z.Msgsize()) + o = msgp.AppendInt32(o, int32(z)) + return +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *TraceRootFlag) UnmarshalMsg(bts []byte) (o []byte, err error) { + { + var zb0001 int32 + zb0001, bts, err = msgp.ReadInt32Bytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + (*z) = TraceRootFlag(zb0001) + } + o = bts + return +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z TraceRootFlag) Msgsize() (s int) { + s = msgp.Int32Size + return +} diff --git a/pkg/proto/pbgo/trace/stats_vtproto.pb.go b/pkg/proto/pbgo/trace/stats_vtproto.pb.go index c29eadf308e72..16eefd4d30896 100644 --- a/pkg/proto/pbgo/trace/stats_vtproto.pb.go +++ b/pkg/proto/pbgo/trace/stats_vtproto.pb.go @@ -329,6 +329,13 @@ func (m *ClientGroupedStats) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if m.IsTraceRoot != 0 { + i = encodeVarint(dAtA, i, uint64(m.IsTraceRoot)) + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0x88 + } if len(m.PeerTags) > 0 { for iNdEx := len(m.PeerTags) - 1; iNdEx >= 0; iNdEx-- { i -= len(m.PeerTags[iNdEx]) @@ -624,6 +631,9 @@ func (m *ClientGroupedStats) SizeVT() (n int) { n += 2 + l + sov(uint64(l)) } } + if m.IsTraceRoot != 0 { + n += 2 + sov(uint64(m.IsTraceRoot)) + } n += len(m.unknownFields) return n } @@ -1915,6 +1925,25 @@ func (m *ClientGroupedStats) UnmarshalVT(dAtA []byte) error { } m.PeerTags = append(m.PeerTags, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex + case 17: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field IsTraceRoot", wireType) + } + m.IsTraceRoot = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.IsTraceRoot |= TraceRootFlag(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skip(dAtA[iNdEx:]) diff --git a/pkg/trace/stats/aggregation.go b/pkg/trace/stats/aggregation.go index c91ea409a73e0..08cab775ef0c1 100644 --- a/pkg/trace/stats/aggregation.go +++ b/pkg/trace/stats/aggregation.go @@ -39,6 +39,7 @@ type BucketsAggregationKey struct { StatusCode uint32 Synthetics bool PeerTagsHash uint64 + IsTraceRoot pb.TraceRootFlag } // PayloadAggregationKey specifies the key by which a payload is aggregated. @@ -77,16 +78,23 @@ func clientOrProducer(spanKind string) bool { // NewAggregationFromSpan creates a new aggregation from the provided span and env func NewAggregationFromSpan(s *pb.Span, origin string, aggKey PayloadAggregationKey, enablePeerTagsAgg bool, peerTagKeys []string) (Aggregation, []string) { synthetics := strings.HasPrefix(origin, tagSynthetics) + var isTraceRoot pb.TraceRootFlag + if s.ParentID == 0 { + isTraceRoot = pb.TraceRootFlag_TRUE + } else { + isTraceRoot = pb.TraceRootFlag_FALSE + } agg := Aggregation{ PayloadAggregationKey: aggKey, BucketsAggregationKey: BucketsAggregationKey{ - Resource: s.Resource, - Service: s.Service, - Name: s.Name, - SpanKind: s.Meta[tagSpanKind], - Type: s.Type, - StatusCode: getStatusCode(s), - Synthetics: synthetics, + Resource: s.Resource, + Service: s.Service, + Name: s.Name, + SpanKind: s.Meta[tagSpanKind], + Type: s.Type, + StatusCode: getStatusCode(s), + Synthetics: synthetics, + IsTraceRoot: isTraceRoot, }, } var peerTags []string diff --git a/pkg/trace/stats/aggregation_test.go b/pkg/trace/stats/aggregation_test.go index 2dda81a7e2e74..6e2b1efc24a87 100644 --- a/pkg/trace/stats/aggregation_test.go +++ b/pkg/trace/stats/aggregation_test.go @@ -150,7 +150,9 @@ func TestNewAggregation(t *testing.T) { }, } { agg, et := NewAggregationFromSpan(tt.in, "", PayloadAggregationKey{}, tt.enablePeerTagsAgg, peerTags) - assert.Equal(t, tt.resAgg, agg, tt.name) + assert.Equal(t, tt.resAgg.Service, agg.Service, tt.name) + assert.Equal(t, tt.resAgg.SpanKind, agg.SpanKind, tt.name) + assert.Equal(t, tt.resAgg.PeerTagsHash, agg.PeerTagsHash, tt.name) assert.Equal(t, tt.resPeerTags, et, tt.name) } } @@ -175,3 +177,30 @@ func TestSpanKindIsConsumerOrProducer(t *testing.T) { assert.Equal(t, tc.res, clientOrProducer(tc.input)) } } + +func TestIsRootSpan(t *testing.T) { + for _, tt := range []struct { + in *pb.Span + isTraceRoot pb.TraceRootFlag + }{ + { + &pb.Span{}, + pb.TraceRootFlag_TRUE, + }, + { + &pb.Span{ + ParentID: 0, + }, + pb.TraceRootFlag_TRUE, + }, + { + &pb.Span{ + ParentID: 123, + }, + pb.TraceRootFlag_FALSE, + }, + } { + agg, _ := NewAggregationFromSpan(tt.in, "", PayloadAggregationKey{}, true, []string{}) + assert.Equal(t, tt.isTraceRoot, agg.IsTraceRoot) + } +} diff --git a/pkg/trace/stats/client_stats_aggregator.go b/pkg/trace/stats/client_stats_aggregator.go index ef84920187c64..f1e6f0672c43e 100644 --- a/pkg/trace/stats/client_stats_aggregator.go +++ b/pkg/trace/stats/client_stats_aggregator.go @@ -6,9 +6,10 @@ package stats import ( - "github.com/DataDog/datadog-agent/pkg/trace/version" "time" + "github.com/DataDog/datadog-agent/pkg/trace/version" + pb "github.com/DataDog/datadog-agent/pkg/proto/pbgo/trace" "github.com/DataDog/datadog-agent/pkg/trace/config" "github.com/DataDog/datadog-agent/pkg/trace/log" @@ -294,6 +295,7 @@ func (b *bucket) aggregationToPayloads() []*pb.ClientStatsPayload { HTTPStatusCode: aggrKey.StatusCode, Type: aggrKey.Type, Synthetics: aggrKey.Synthetics, + IsTraceRoot: aggrKey.IsTraceRoot, PeerTags: counts.peerTags, Hits: counts.hits, Errors: counts.errors, @@ -332,13 +334,14 @@ func newPayloadAggregationKey(env, hostname, version, cid string, gitCommitSha s func newBucketAggregationKey(b *pb.ClientGroupedStats, enablePeerTagsAgg bool) BucketsAggregationKey { k := BucketsAggregationKey{ - Service: b.Service, - Name: b.Name, - SpanKind: b.SpanKind, - Resource: b.Resource, - Type: b.Type, - Synthetics: b.Synthetics, - StatusCode: b.HTTPStatusCode, + Service: b.Service, + Name: b.Name, + SpanKind: b.SpanKind, + Resource: b.Resource, + Type: b.Type, + Synthetics: b.Synthetics, + StatusCode: b.HTTPStatusCode, + IsTraceRoot: b.IsTraceRoot, } if enablePeerTagsAgg { k.PeerTagsHash = peerTagsHash(b.GetPeerTags()) diff --git a/pkg/trace/stats/client_stats_aggregator_test.go b/pkg/trace/stats/client_stats_aggregator_test.go index 8eb370342bfde..8e13a2412b5cc 100644 --- a/pkg/trace/stats/client_stats_aggregator_test.go +++ b/pkg/trace/stats/client_stats_aggregator_test.go @@ -744,6 +744,7 @@ func deepCopyGroupedStats(s []*proto.ClientGroupedStats) []*proto.ClientGroupedS TopLevelHits: b.GetTopLevelHits(), SpanKind: b.GetSpanKind(), PeerTags: b.GetPeerTags(), + IsTraceRoot: b.GetIsTraceRoot(), } if b.OkSummary != nil { stats[i].OkSummary = make([]byte, len(b.OkSummary)) diff --git a/pkg/trace/stats/concentrator_test.go b/pkg/trace/stats/concentrator_test.go index 28b75860d7a1e..5380558675271 100644 --- a/pkg/trace/stats/concentrator_test.go +++ b/pkg/trace/stats/concentrator_test.go @@ -267,6 +267,7 @@ func TestConcentratorOldestTs(t *testing.T) { Hits: 6, TopLevelHits: 6, Errors: 0, + IsTraceRoot: pb.TraceRootFlag_TRUE, }, } assertCountsEqual(t, expected, stats.Stats[0].Stats[0].Stats) @@ -304,6 +305,7 @@ func TestConcentratorOldestTs(t *testing.T) { Hits: 5, TopLevelHits: 5, Errors: 0, + IsTraceRoot: pb.TraceRootFlag_TRUE, }, } assertCountsEqual(t, expected, stats.Stats[0].Stats[0].Stats) @@ -324,6 +326,7 @@ func TestConcentratorOldestTs(t *testing.T) { Hits: 1, TopLevelHits: 1, Errors: 0, + IsTraceRoot: pb.TraceRootFlag_TRUE, }, } assertCountsEqual(t, expected, stats.Stats[0].Stats[0].Stats) @@ -433,6 +436,7 @@ func TestConcentratorStatsCounts(t *testing.T) { Hits: 4, TopLevelHits: 4, Errors: 1, + IsTraceRoot: pb.TraceRootFlag_TRUE, }, { Service: "A2", @@ -443,6 +447,7 @@ func TestConcentratorStatsCounts(t *testing.T) { Hits: 2, TopLevelHits: 2, Errors: 2, + IsTraceRoot: pb.TraceRootFlag_TRUE, }, { Service: "A2", @@ -453,6 +458,7 @@ func TestConcentratorStatsCounts(t *testing.T) { Hits: 1, TopLevelHits: 1, Errors: 0, + IsTraceRoot: pb.TraceRootFlag_TRUE, }, { Service: "A1", @@ -464,6 +470,7 @@ func TestConcentratorStatsCounts(t *testing.T) { Hits: 1, TopLevelHits: 1, Errors: 0, + IsTraceRoot: pb.TraceRootFlag_TRUE, }, { Service: "A1", @@ -475,6 +482,7 @@ func TestConcentratorStatsCounts(t *testing.T) { Hits: 1, TopLevelHits: 1, Errors: 0, + IsTraceRoot: pb.TraceRootFlag_TRUE, }, } // 1-bucket old flush @@ -488,6 +496,7 @@ func TestConcentratorStatsCounts(t *testing.T) { Hits: 1, TopLevelHits: 1, Errors: 1, + IsTraceRoot: pb.TraceRootFlag_TRUE, }, { Service: "A1", @@ -498,6 +507,7 @@ func TestConcentratorStatsCounts(t *testing.T) { Hits: 1, TopLevelHits: 1, Errors: 0, + IsTraceRoot: pb.TraceRootFlag_TRUE, }, { Service: "A2", @@ -508,6 +518,7 @@ func TestConcentratorStatsCounts(t *testing.T) { Hits: 1, TopLevelHits: 1, Errors: 1, + IsTraceRoot: pb.TraceRootFlag_TRUE, }, { Service: "A2", @@ -518,6 +529,7 @@ func TestConcentratorStatsCounts(t *testing.T) { Hits: 1, TopLevelHits: 1, Errors: 1, + IsTraceRoot: pb.TraceRootFlag_TRUE, }, { Service: "A2", @@ -528,6 +540,7 @@ func TestConcentratorStatsCounts(t *testing.T) { Hits: 1, TopLevelHits: 1, Errors: 0, + IsTraceRoot: pb.TraceRootFlag_TRUE, }, } // last bucket to be flushed @@ -541,6 +554,7 @@ func TestConcentratorStatsCounts(t *testing.T) { Hits: 1, TopLevelHits: 1, Errors: 0, + IsTraceRoot: pb.TraceRootFlag_TRUE, }, } expectedCountValByKeyByTime[alignedNow+testBucketInterval] = []*pb.ClientGroupedStats{} @@ -584,6 +598,49 @@ func TestConcentratorStatsCounts(t *testing.T) { } } +// TestRootTag tests that an aggregation will be slit up by the IsTraceRoot aggKey +func TestRootTag(t *testing.T) { + now := time.Now() + spans := []*pb.Span{ + testSpan(now, 1, 0, 40, 10, "A1", "resource1", 0, nil), + testSpan(now, 2, 1, 30, 10, "A1", "resource1", 0, nil), + testSpan(now, 3, 2, 20, 10, "A1", "resource1", 0, map[string]string{"span.kind": "client"}), + testSpan(now, 4, 1000, 10, 10, "A1", "resource1", 0, nil), + } + traceutil.ComputeTopLevel(spans) + testTrace := toProcessedTrace(spans, "none", "", "", "", "") + c := NewTestConcentrator(now) + c.addNow(testTrace, "") + + expected := []*pb.ClientGroupedStats{ + { + Service: "A1", + Resource: "resource1", + Type: "db", + Name: "query", + Duration: 60, + Hits: 2, + TopLevelHits: 2, + Errors: 0, + IsTraceRoot: pb.TraceRootFlag_TRUE, + }, + { + Service: "A1", + Resource: "resource1", + Type: "db", + Name: "query", + Duration: 10, + Hits: 1, + TopLevelHits: 1, + Errors: 0, + IsTraceRoot: pb.TraceRootFlag_FALSE, + }, + } + + stats := c.flushNow(now.UnixNano()+int64(c.bufferLen)*testBucketInterval, false) + assertCountsEqual(t, expected, stats.Stats[0].Stats[0].Stats) +} + func generateDistribution(t *testing.T, now time.Time, generator func(i int) int64) *ddsketch.DDSketch { assert := assert.New(t) c := NewTestConcentrator(now) diff --git a/pkg/trace/stats/statsraw.go b/pkg/trace/stats/statsraw.go index 8e8d7e8958481..d1407dfb8d3ed 100644 --- a/pkg/trace/stats/statsraw.go +++ b/pkg/trace/stats/statsraw.go @@ -76,6 +76,7 @@ func (s *groupedStats) export(a Aggregation) (*pb.ClientGroupedStats, error) { Synthetics: a.Synthetics, SpanKind: a.SpanKind, PeerTags: s.peerTags, + IsTraceRoot: a.IsTraceRoot, }, nil } diff --git a/pkg/trace/stats/statsraw_test.go b/pkg/trace/stats/statsraw_test.go index d802d08d89de6..fc6378c0a646c 100644 --- a/pkg/trace/stats/statsraw_test.go +++ b/pkg/trace/stats/statsraw_test.go @@ -30,9 +30,10 @@ func TestGrain(t *testing.T) { ContainerID: "cid", }, BucketsAggregationKey: BucketsAggregationKey{ - Service: "thing", - Name: "other", - Resource: "yo", + Service: "thing", + Name: "other", + Resource: "yo", + IsTraceRoot: pb.TraceRootFlag_TRUE, }, }, aggr) } @@ -59,10 +60,11 @@ func TestGrainWithPeerTags(t *testing.T) { ContainerID: "cid", }, BucketsAggregationKey: BucketsAggregationKey{ - Service: "thing", - SpanKind: "client", - Name: "other", - Resource: "yo", + Service: "thing", + SpanKind: "client", + Name: "other", + Resource: "yo", + IsTraceRoot: pb.TraceRootFlag_TRUE, }, }, aggr) assert.Nil(et) @@ -93,6 +95,7 @@ func TestGrainWithPeerTags(t *testing.T) { Name: "other", Resource: "yo", PeerTagsHash: 13698082192712149795, + IsTraceRoot: pb.TraceRootFlag_TRUE, }, }, aggr) assert.Equal([]string{"aws.s3.bucket:bucket-a", "peer.service:aws-s3"}, et) @@ -123,6 +126,7 @@ func TestGrainWithPeerTags(t *testing.T) { Name: "other", Resource: "yo", PeerTagsHash: 5537613849774405073, + IsTraceRoot: pb.TraceRootFlag_TRUE, }, }, aggr) assert.Equal([]string{"db.instance:dynamo.test.us1", "db.system:dynamodb", "peer.service:aws-dynamodb"}, et) @@ -146,11 +150,12 @@ func TestGrainWithSynthetics(t *testing.T) { ContainerID: "cid", }, BucketsAggregationKey: BucketsAggregationKey{ - Service: "thing", - Resource: "yo", - Name: "other", - StatusCode: 418, - Synthetics: true, + Service: "thing", + Resource: "yo", + Name: "other", + StatusCode: 418, + Synthetics: true, + IsTraceRoot: pb.TraceRootFlag_TRUE, }, }, aggr) } diff --git a/releasenotes/notes/apm-adding-is_trace_root-tag-for-APM-Stats-f3f4384105897d11.yaml b/releasenotes/notes/apm-adding-is_trace_root-tag-for-APM-Stats-f3f4384105897d11.yaml new file mode 100644 index 0000000000000..8a1317705a06c --- /dev/null +++ b/releasenotes/notes/apm-adding-is_trace_root-tag-for-APM-Stats-f3f4384105897d11.yaml @@ -0,0 +1,11 @@ +# Each section from every release note are combined when the +# CHANGELOG.rst is rendered. So the text needs to be worded so that +# it does not depend on any information only available in another +# section. This may mean repeating some details, but each section +# must be readable independently of the other. +# +# Each section note must be formatted as reStructuredText. +--- +features: + - | + APM stats now include an is_trace_root field to indicate if the stats are from the root span of a trace.