From 3c412d8ac70c018f373e998350e4310abfca750c Mon Sep 17 00:00:00 2001 From: Marc Sanmiquel Date: Mon, 10 Nov 2025 16:23:23 +0100 Subject: [PATCH 1/6] feat: add proto defintions for individual profile retrieval API --- .../gen/ingester/v1/ingester.openapi.yaml | 48 ++ .../gen/querier/v1/querier.openapi.yaml | 120 +++++ .../gen/query/v1/query.openapi.yaml | 48 ++ .../storegateway/v1/storegateway.openapi.yaml | 48 ++ api/gen/proto/go/querier/v1/querier.pb.go | 216 ++++++-- .../proto/go/querier/v1/querier_vtproto.pb.go | 462 ++++++++++++++++++ .../v1/querierv1connect/querier.connect.go | 35 ++ .../querierv1connect/querier.connect.mux.go | 5 + api/gen/proto/go/types/v1/types.pb.go | 128 ++++- api/gen/proto/go/types/v1/types_vtproto.pb.go | 387 +++++++++++++++ api/openapiv2/gen/phlare.swagger.json | 45 ++ api/querier/v1/querier.proto | 22 + api/types/v1/types.proto | 21 + docs/sources/reference-server-api/index.md | 37 ++ pkg/frontend/frontend_get_profile.go | 18 + .../readpath/query_service_handler.go | 8 + .../query_frontend_get_profile.go | 21 + pkg/querier/querier_get_profile.go | 18 + .../mock_querier_service_client.go | 59 +++ pkg/util/spanlogger/query_log.go | 13 + 20 files changed, 1692 insertions(+), 67 deletions(-) create mode 100644 pkg/frontend/frontend_get_profile.go create mode 100644 pkg/frontend/readpath/queryfrontend/query_frontend_get_profile.go create mode 100644 pkg/querier/querier_get_profile.go diff --git a/api/connect-openapi/gen/ingester/v1/ingester.openapi.yaml b/api/connect-openapi/gen/ingester/v1/ingester.openapi.yaml index a0c99129ea..da109da316 100644 --- a/api/connect-openapi/gen/ingester/v1/ingester.openapi.yaml +++ b/api/connect-openapi/gen/ingester/v1/ingester.openapi.yaml @@ -1048,6 +1048,48 @@ components: title: labels title: BlockInfo additionalProperties: false + types.v1.Exemplar: + type: object + properties: + timestamp: + type: + - integer + - string + examples: + - "1730000023000" + title: timestamp + format: int64 + description: Milliseconds since epoch when the profile was captured. + id: + type: string + examples: + - 7c9e6679-7425-40de-944b-e07fc1f90ae7 + title: id + description: Unique identifier for the profile (UUID). + value: + type: + - integer + - string + examples: + - "2450000000" + title: value + format: int64 + description: Total sample value for this profile (e.g., CPU nanoseconds, bytes allocated). + labels: + type: array + items: + $ref: '#/components/schemas/types.v1.LabelPair' + title: labels + description: |- + Optional dynamic labels that were used to split the profile during ingestion + (e.g., trace_id, span_id). Static series labels are not included as they're + already present in the parent Series. + title: Exemplar + additionalProperties: false + description: |- + Exemplar represents metadata for an individual profile sample. + Exemplars allow users to drill down from aggregated timeline views + to specific profile instances. types.v1.GetProfileStatsRequest: type: object title: GetProfileStatsRequest @@ -1229,6 +1271,12 @@ components: items: $ref: '#/components/schemas/types.v1.ProfileAnnotation' title: annotations + exemplars: + type: array + items: + $ref: '#/components/schemas/types.v1.Exemplar' + title: exemplars + description: Exemplars are samples of individual profiles that contributed to this aggregated point title: Point additionalProperties: false types.v1.ProfileAnnotation: diff --git a/api/connect-openapi/gen/querier/v1/querier.openapi.yaml b/api/connect-openapi/gen/querier/v1/querier.openapi.yaml index c50ae05f0e..17f057964c 100644 --- a/api/connect-openapi/gen/querier/v1/querier.openapi.yaml +++ b/api/connect-openapi/gen/querier/v1/querier.openapi.yaml @@ -82,6 +82,46 @@ paths: application/json: schema: $ref: '#/components/schemas/querier.v1.DiffResponse' + /querier.v1.QuerierService/GetProfile: + post: + tags: + - scope/public + - querier.v1.QuerierService + summary: GetProfile retrieves an individual profile by its UUID. If the profile was split during ingestion (e.g., by span_id), all parts will be merged before returning. + description: |- + GetProfile retrieves an individual profile by its UUID. + If the profile was split during ingestion (e.g., by span_id), all parts + will be merged before returning. + operationId: querier.v1.QuerierService.GetProfile + parameters: + - name: Connect-Protocol-Version + in: header + required: true + schema: + $ref: '#/components/schemas/connect-protocol-version' + - name: Connect-Timeout-Ms + in: header + schema: + $ref: '#/components/schemas/connect-timeout-header' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/querier.v1.GetProfileRequest' + required: true + responses: + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/querier.v1.GetProfileResponse' /querier.v1.QuerierService/GetProfileStats: post: tags: @@ -1024,6 +1064,38 @@ components: format: int64 title: FlameGraphDiff additionalProperties: false + querier.v1.GetProfileRequest: + type: object + properties: + id: + type: string + examples: + - 7c9e6679-7425-40de-944b-e07fc1f90ae7 + title: id + description: Profile UUID from Exemplar.id field (required) + timestamp: + type: + - integer + - string + examples: + - "1730000023000" + title: timestamp + format: int64 + description: |- + Profile timestamp in milliseconds since epoch. + Used to narrow down which blocks to query. If not provided, all blocks will be scanned which is expensive. + This timestamp is available from Exemplar.timestamp in the SelectSeries response. + title: GetProfileRequest + additionalProperties: false + querier.v1.GetProfileResponse: + type: object + properties: + profile: + title: profile + description: Complete pprof profile (merged if it was split during ingestion) + $ref: '#/components/schemas/google.v1.Profile' + title: GetProfileResponse + additionalProperties: false querier.v1.Level: type: object properties: @@ -1491,6 +1563,48 @@ components: title: labels_set title: SeriesResponse additionalProperties: false + types.v1.Exemplar: + type: object + properties: + timestamp: + type: + - integer + - string + examples: + - "1730000023000" + title: timestamp + format: int64 + description: Milliseconds since epoch when the profile was captured. + id: + type: string + examples: + - 7c9e6679-7425-40de-944b-e07fc1f90ae7 + title: id + description: Unique identifier for the profile (UUID). + value: + type: + - integer + - string + examples: + - "2450000000" + title: value + format: int64 + description: Total sample value for this profile (e.g., CPU nanoseconds, bytes allocated). + labels: + type: array + items: + $ref: '#/components/schemas/types.v1.LabelPair' + title: labels + description: |- + Optional dynamic labels that were used to split the profile during ingestion + (e.g., trace_id, span_id). Static series labels are not included as they're + already present in the parent Series. + title: Exemplar + additionalProperties: false + description: |- + Exemplar represents metadata for an individual profile sample. + Exemplars allow users to drill down from aggregated timeline views + to specific profile instances. types.v1.GetProfileStatsRequest: type: object title: GetProfileStatsRequest @@ -1672,6 +1786,12 @@ components: items: $ref: '#/components/schemas/types.v1.ProfileAnnotation' title: annotations + exemplars: + type: array + items: + $ref: '#/components/schemas/types.v1.Exemplar' + title: exemplars + description: Exemplars are samples of individual profiles that contributed to this aggregated point title: Point additionalProperties: false types.v1.ProfileAnnotation: diff --git a/api/connect-openapi/gen/query/v1/query.openapi.yaml b/api/connect-openapi/gen/query/v1/query.openapi.yaml index 53be2622f6..617fb94bf8 100644 --- a/api/connect-openapi/gen/query/v1/query.openapi.yaml +++ b/api/connect-openapi/gen/query/v1/query.openapi.yaml @@ -725,6 +725,48 @@ components: format: byte title: TreeReport additionalProperties: false + types.v1.Exemplar: + type: object + properties: + timestamp: + type: + - integer + - string + examples: + - "1730000023000" + title: timestamp + format: int64 + description: Milliseconds since epoch when the profile was captured. + id: + type: string + examples: + - 7c9e6679-7425-40de-944b-e07fc1f90ae7 + title: id + description: Unique identifier for the profile (UUID). + value: + type: + - integer + - string + examples: + - "2450000000" + title: value + format: int64 + description: Total sample value for this profile (e.g., CPU nanoseconds, bytes allocated). + labels: + type: array + items: + $ref: '#/components/schemas/types.v1.LabelPair' + title: labels + description: |- + Optional dynamic labels that were used to split the profile during ingestion + (e.g., trace_id, span_id). Static series labels are not included as they're + already present in the parent Series. + title: Exemplar + additionalProperties: false + description: |- + Exemplar represents metadata for an individual profile sample. + Exemplars allow users to drill down from aggregated timeline views + to specific profile instances. types.v1.GoPGO: type: object properties: @@ -795,6 +837,12 @@ components: items: $ref: '#/components/schemas/types.v1.ProfileAnnotation' title: annotations + exemplars: + type: array + items: + $ref: '#/components/schemas/types.v1.Exemplar' + title: exemplars + description: Exemplars are samples of individual profiles that contributed to this aggregated point title: Point additionalProperties: false types.v1.ProfileAnnotation: diff --git a/api/connect-openapi/gen/storegateway/v1/storegateway.openapi.yaml b/api/connect-openapi/gen/storegateway/v1/storegateway.openapi.yaml index 4dc89947f0..2cb3520c6a 100644 --- a/api/connect-openapi/gen/storegateway/v1/storegateway.openapi.yaml +++ b/api/connect-openapi/gen/storegateway/v1/storegateway.openapi.yaml @@ -867,6 +867,48 @@ components: title: labels title: BlockInfo additionalProperties: false + types.v1.Exemplar: + type: object + properties: + timestamp: + type: + - integer + - string + examples: + - "1730000023000" + title: timestamp + format: int64 + description: Milliseconds since epoch when the profile was captured. + id: + type: string + examples: + - 7c9e6679-7425-40de-944b-e07fc1f90ae7 + title: id + description: Unique identifier for the profile (UUID). + value: + type: + - integer + - string + examples: + - "2450000000" + title: value + format: int64 + description: Total sample value for this profile (e.g., CPU nanoseconds, bytes allocated). + labels: + type: array + items: + $ref: '#/components/schemas/types.v1.LabelPair' + title: labels + description: |- + Optional dynamic labels that were used to split the profile during ingestion + (e.g., trace_id, span_id). Static series labels are not included as they're + already present in the parent Series. + title: Exemplar + additionalProperties: false + description: |- + Exemplar represents metadata for an individual profile sample. + Exemplars allow users to drill down from aggregated timeline views + to specific profile instances. types.v1.GoPGO: type: object properties: @@ -1021,6 +1063,12 @@ components: items: $ref: '#/components/schemas/types.v1.ProfileAnnotation' title: annotations + exemplars: + type: array + items: + $ref: '#/components/schemas/types.v1.Exemplar' + title: exemplars + description: Exemplars are samples of individual profiles that contributed to this aggregated point title: Point additionalProperties: false types.v1.ProfileAnnotation: diff --git a/api/gen/proto/go/querier/v1/querier.pb.go b/api/gen/proto/go/querier/v1/querier.pb.go index 3b4cf211a2..516f102f5c 100644 --- a/api/gen/proto/go/querier/v1/querier.pb.go +++ b/api/gen/proto/go/querier/v1/querier.pb.go @@ -1429,6 +1429,107 @@ func (x *QueryImpact) GetDeduplicationNeeded() bool { return false } +type GetProfileRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Profile UUID from Exemplar.id field (required) + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // Profile timestamp in milliseconds since epoch. + // Used to narrow down which blocks to query. If not provided, all blocks will be scanned which is expensive. + // This timestamp is available from Exemplar.timestamp in the SelectSeries response. + Timestamp int64 `protobuf:"varint,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetProfileRequest) Reset() { + *x = GetProfileRequest{} + mi := &file_querier_v1_querier_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetProfileRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetProfileRequest) ProtoMessage() {} + +func (x *GetProfileRequest) ProtoReflect() protoreflect.Message { + mi := &file_querier_v1_querier_proto_msgTypes[20] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetProfileRequest.ProtoReflect.Descriptor instead. +func (*GetProfileRequest) Descriptor() ([]byte, []int) { + return file_querier_v1_querier_proto_rawDescGZIP(), []int{20} +} + +func (x *GetProfileRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *GetProfileRequest) GetTimestamp() int64 { + if x != nil { + return x.Timestamp + } + return 0 +} + +type GetProfileResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Complete pprof profile (merged if it was split during ingestion) + Profile *v11.Profile `protobuf:"bytes,1,opt,name=profile,proto3" json:"profile,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetProfileResponse) Reset() { + *x = GetProfileResponse{} + mi := &file_querier_v1_querier_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetProfileResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetProfileResponse) ProtoMessage() {} + +func (x *GetProfileResponse) ProtoReflect() protoreflect.Message { + mi := &file_querier_v1_querier_proto_msgTypes[21] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetProfileResponse.ProtoReflect.Descriptor instead. +func (*GetProfileResponse) Descriptor() ([]byte, []int) { + return file_querier_v1_querier_proto_rawDescGZIP(), []int{21} +} + +func (x *GetProfileResponse) GetProfile() *v11.Profile { + if x != nil { + return x.Profile + } + return nil +} + var File_querier_v1_querier_proto protoreflect.FileDescriptor const file_querier_v1_querier_proto_rawDesc = "" + @@ -1552,11 +1653,16 @@ const file_querier_v1_querier_proto_rawDesc = "" + "\vQueryImpact\x128\n" + "\x19total_bytes_in_time_range\x18\x02 \x01(\x04R\x15totalBytesInTimeRange\x120\n" + "\x14total_queried_series\x18\x03 \x01(\x04R\x12totalQueriedSeries\x121\n" + - "\x14deduplication_needed\x18\x04 \x01(\bR\x13deduplicationNeeded*g\n" + + "\x14deduplication_needed\x18\x04 \x01(\bR\x13deduplicationNeeded\"\x84\x01\n" + + "\x11GetProfileRequest\x12;\n" + + "\x02id\x18\x01 \x01(\tB+\xbaG(:&\x12$7c9e6679-7425-40de-944b-e07fc1f90ae7R\x02id\x122\n" + + "\ttimestamp\x18\x02 \x01(\x03B\x14\xbaG\x11:\x0f\x12\r1730000023000R\ttimestamp\"B\n" + + "\x12GetProfileResponse\x12,\n" + + "\aprofile\x18\x01 \x01(\v2\x12.google.v1.ProfileR\aprofile*g\n" + "\rProfileFormat\x12\x1e\n" + "\x1aPROFILE_FORMAT_UNSPECIFIED\x10\x00\x12\x1d\n" + "\x19PROFILE_FORMAT_FLAMEGRAPH\x10\x01\x12\x17\n" + - "\x13PROFILE_FORMAT_TREE\x10\x022\xfc\b\n" + + "\x13PROFILE_FORMAT_TREE\x10\x022\xdc\t\n" + "\x0eQuerierService\x12d\n" + "\fProfileTypes\x12\x1f.querier.v1.ProfileTypesRequest\x1a .querier.v1.ProfileTypesResponse\"\x11\xbaG\x0e\n" + "\fscope/public\x12]\n" + @@ -1580,7 +1686,10 @@ const file_querier_v1_querier_proto_rawDesc = "" + "\x0fGetProfileStats\x12 .types.v1.GetProfileStatsRequest\x1a!.types.v1.GetProfileStatsResponse\"\x13\xbaG\x10\n" + "\x0escope/internal\x12f\n" + "\fAnalyzeQuery\x12\x1f.querier.v1.AnalyzeQueryRequest\x1a .querier.v1.AnalyzeQueryResponse\"\x13\xbaG\x10\n" + - "\x0escope/internalB\xab\x01\n" + + "\x0escope/internal\x12^\n" + + "\n" + + "GetProfile\x12\x1d.querier.v1.GetProfileRequest\x1a\x1e.querier.v1.GetProfileResponse\"\x11\xbaG\x0e\n" + + "\fscope/publicB\xab\x01\n" + "\x0ecom.querier.v1B\fQuerierProtoP\x01ZBgithub.com/grafana/pyroscope/api/gen/proto/go/querier/v1;querierv1\xa2\x02\x03QXX\xaa\x02\n" + "Querier.V1\xca\x02\n" + "Querier\\V1\xe2\x02\x16Querier\\V1\\GPBMetadata\xea\x02\vQuerier::V1b\x06proto3" @@ -1598,7 +1707,7 @@ func file_querier_v1_querier_proto_rawDescGZIP() []byte { } var file_querier_v1_querier_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_querier_v1_querier_proto_msgTypes = make([]protoimpl.MessageInfo, 20) +var file_querier_v1_querier_proto_msgTypes = make([]protoimpl.MessageInfo, 22) var file_querier_v1_querier_proto_goTypes = []any{ (ProfileFormat)(0), // 0: querier.v1.ProfileFormat (*ProfileTypesRequest)(nil), // 1: querier.v1.ProfileTypesRequest @@ -1621,24 +1730,26 @@ var file_querier_v1_querier_proto_goTypes = []any{ (*AnalyzeQueryResponse)(nil), // 18: querier.v1.AnalyzeQueryResponse (*QueryScope)(nil), // 19: querier.v1.QueryScope (*QueryImpact)(nil), // 20: querier.v1.QueryImpact - (*v1.ProfileType)(nil), // 21: types.v1.ProfileType - (*v1.Labels)(nil), // 22: types.v1.Labels - (*v1.StackTraceSelector)(nil), // 23: types.v1.StackTraceSelector - (v1.TimeSeriesAggregationType)(0), // 24: types.v1.TimeSeriesAggregationType - (*v1.Series)(nil), // 25: types.v1.Series - (*v1.LabelValuesRequest)(nil), // 26: types.v1.LabelValuesRequest - (*v1.LabelNamesRequest)(nil), // 27: types.v1.LabelNamesRequest - (*v1.GetProfileStatsRequest)(nil), // 28: types.v1.GetProfileStatsRequest - (*v1.LabelValuesResponse)(nil), // 29: types.v1.LabelValuesResponse - (*v1.LabelNamesResponse)(nil), // 30: types.v1.LabelNamesResponse - (*v11.Profile)(nil), // 31: google.v1.Profile - (*v1.GetProfileStatsResponse)(nil), // 32: types.v1.GetProfileStatsResponse + (*GetProfileRequest)(nil), // 21: querier.v1.GetProfileRequest + (*GetProfileResponse)(nil), // 22: querier.v1.GetProfileResponse + (*v1.ProfileType)(nil), // 23: types.v1.ProfileType + (*v1.Labels)(nil), // 24: types.v1.Labels + (*v1.StackTraceSelector)(nil), // 25: types.v1.StackTraceSelector + (v1.TimeSeriesAggregationType)(0), // 26: types.v1.TimeSeriesAggregationType + (*v1.Series)(nil), // 27: types.v1.Series + (*v11.Profile)(nil), // 28: google.v1.Profile + (*v1.LabelValuesRequest)(nil), // 29: types.v1.LabelValuesRequest + (*v1.LabelNamesRequest)(nil), // 30: types.v1.LabelNamesRequest + (*v1.GetProfileStatsRequest)(nil), // 31: types.v1.GetProfileStatsRequest + (*v1.LabelValuesResponse)(nil), // 32: types.v1.LabelValuesResponse + (*v1.LabelNamesResponse)(nil), // 33: types.v1.LabelNamesResponse + (*v1.GetProfileStatsResponse)(nil), // 34: types.v1.GetProfileStatsResponse } var file_querier_v1_querier_proto_depIdxs = []int32{ - 21, // 0: querier.v1.ProfileTypesResponse.profile_types:type_name -> types.v1.ProfileType - 22, // 1: querier.v1.SeriesResponse.labels_set:type_name -> types.v1.Labels + 23, // 0: querier.v1.ProfileTypesResponse.profile_types:type_name -> types.v1.ProfileType + 24, // 1: querier.v1.SeriesResponse.labels_set:type_name -> types.v1.Labels 0, // 2: querier.v1.SelectMergeStacktracesRequest.format:type_name -> querier.v1.ProfileFormat - 23, // 3: querier.v1.SelectMergeStacktracesRequest.stack_trace_selector:type_name -> types.v1.StackTraceSelector + 25, // 3: querier.v1.SelectMergeStacktracesRequest.stack_trace_selector:type_name -> types.v1.StackTraceSelector 11, // 4: querier.v1.SelectMergeStacktracesResponse.flamegraph:type_name -> querier.v1.FlameGraph 0, // 5: querier.v1.SelectMergeSpanProfileRequest.format:type_name -> querier.v1.ProfileFormat 11, // 6: querier.v1.SelectMergeSpanProfileResponse.flamegraph:type_name -> querier.v1.FlameGraph @@ -1647,39 +1758,42 @@ var file_querier_v1_querier_proto_depIdxs = []int32{ 12, // 9: querier.v1.DiffResponse.flamegraph:type_name -> querier.v1.FlameGraphDiff 13, // 10: querier.v1.FlameGraph.levels:type_name -> querier.v1.Level 13, // 11: querier.v1.FlameGraphDiff.levels:type_name -> querier.v1.Level - 23, // 12: querier.v1.SelectMergeProfileRequest.stack_trace_selector:type_name -> types.v1.StackTraceSelector - 24, // 13: querier.v1.SelectSeriesRequest.aggregation:type_name -> types.v1.TimeSeriesAggregationType - 23, // 14: querier.v1.SelectSeriesRequest.stack_trace_selector:type_name -> types.v1.StackTraceSelector - 25, // 15: querier.v1.SelectSeriesResponse.series:type_name -> types.v1.Series + 25, // 12: querier.v1.SelectMergeProfileRequest.stack_trace_selector:type_name -> types.v1.StackTraceSelector + 26, // 13: querier.v1.SelectSeriesRequest.aggregation:type_name -> types.v1.TimeSeriesAggregationType + 25, // 14: querier.v1.SelectSeriesRequest.stack_trace_selector:type_name -> types.v1.StackTraceSelector + 27, // 15: querier.v1.SelectSeriesResponse.series:type_name -> types.v1.Series 19, // 16: querier.v1.AnalyzeQueryResponse.query_scopes:type_name -> querier.v1.QueryScope 20, // 17: querier.v1.AnalyzeQueryResponse.query_impact:type_name -> querier.v1.QueryImpact - 1, // 18: querier.v1.QuerierService.ProfileTypes:input_type -> querier.v1.ProfileTypesRequest - 26, // 19: querier.v1.QuerierService.LabelValues:input_type -> types.v1.LabelValuesRequest - 27, // 20: querier.v1.QuerierService.LabelNames:input_type -> types.v1.LabelNamesRequest - 3, // 21: querier.v1.QuerierService.Series:input_type -> querier.v1.SeriesRequest - 5, // 22: querier.v1.QuerierService.SelectMergeStacktraces:input_type -> querier.v1.SelectMergeStacktracesRequest - 7, // 23: querier.v1.QuerierService.SelectMergeSpanProfile:input_type -> querier.v1.SelectMergeSpanProfileRequest - 14, // 24: querier.v1.QuerierService.SelectMergeProfile:input_type -> querier.v1.SelectMergeProfileRequest - 15, // 25: querier.v1.QuerierService.SelectSeries:input_type -> querier.v1.SelectSeriesRequest - 9, // 26: querier.v1.QuerierService.Diff:input_type -> querier.v1.DiffRequest - 28, // 27: querier.v1.QuerierService.GetProfileStats:input_type -> types.v1.GetProfileStatsRequest - 17, // 28: querier.v1.QuerierService.AnalyzeQuery:input_type -> querier.v1.AnalyzeQueryRequest - 2, // 29: querier.v1.QuerierService.ProfileTypes:output_type -> querier.v1.ProfileTypesResponse - 29, // 30: querier.v1.QuerierService.LabelValues:output_type -> types.v1.LabelValuesResponse - 30, // 31: querier.v1.QuerierService.LabelNames:output_type -> types.v1.LabelNamesResponse - 4, // 32: querier.v1.QuerierService.Series:output_type -> querier.v1.SeriesResponse - 6, // 33: querier.v1.QuerierService.SelectMergeStacktraces:output_type -> querier.v1.SelectMergeStacktracesResponse - 8, // 34: querier.v1.QuerierService.SelectMergeSpanProfile:output_type -> querier.v1.SelectMergeSpanProfileResponse - 31, // 35: querier.v1.QuerierService.SelectMergeProfile:output_type -> google.v1.Profile - 16, // 36: querier.v1.QuerierService.SelectSeries:output_type -> querier.v1.SelectSeriesResponse - 10, // 37: querier.v1.QuerierService.Diff:output_type -> querier.v1.DiffResponse - 32, // 38: querier.v1.QuerierService.GetProfileStats:output_type -> types.v1.GetProfileStatsResponse - 18, // 39: querier.v1.QuerierService.AnalyzeQuery:output_type -> querier.v1.AnalyzeQueryResponse - 29, // [29:40] is the sub-list for method output_type - 18, // [18:29] is the sub-list for method input_type - 18, // [18:18] is the sub-list for extension type_name - 18, // [18:18] is the sub-list for extension extendee - 0, // [0:18] is the sub-list for field type_name + 28, // 18: querier.v1.GetProfileResponse.profile:type_name -> google.v1.Profile + 1, // 19: querier.v1.QuerierService.ProfileTypes:input_type -> querier.v1.ProfileTypesRequest + 29, // 20: querier.v1.QuerierService.LabelValues:input_type -> types.v1.LabelValuesRequest + 30, // 21: querier.v1.QuerierService.LabelNames:input_type -> types.v1.LabelNamesRequest + 3, // 22: querier.v1.QuerierService.Series:input_type -> querier.v1.SeriesRequest + 5, // 23: querier.v1.QuerierService.SelectMergeStacktraces:input_type -> querier.v1.SelectMergeStacktracesRequest + 7, // 24: querier.v1.QuerierService.SelectMergeSpanProfile:input_type -> querier.v1.SelectMergeSpanProfileRequest + 14, // 25: querier.v1.QuerierService.SelectMergeProfile:input_type -> querier.v1.SelectMergeProfileRequest + 15, // 26: querier.v1.QuerierService.SelectSeries:input_type -> querier.v1.SelectSeriesRequest + 9, // 27: querier.v1.QuerierService.Diff:input_type -> querier.v1.DiffRequest + 31, // 28: querier.v1.QuerierService.GetProfileStats:input_type -> types.v1.GetProfileStatsRequest + 17, // 29: querier.v1.QuerierService.AnalyzeQuery:input_type -> querier.v1.AnalyzeQueryRequest + 21, // 30: querier.v1.QuerierService.GetProfile:input_type -> querier.v1.GetProfileRequest + 2, // 31: querier.v1.QuerierService.ProfileTypes:output_type -> querier.v1.ProfileTypesResponse + 32, // 32: querier.v1.QuerierService.LabelValues:output_type -> types.v1.LabelValuesResponse + 33, // 33: querier.v1.QuerierService.LabelNames:output_type -> types.v1.LabelNamesResponse + 4, // 34: querier.v1.QuerierService.Series:output_type -> querier.v1.SeriesResponse + 6, // 35: querier.v1.QuerierService.SelectMergeStacktraces:output_type -> querier.v1.SelectMergeStacktracesResponse + 8, // 36: querier.v1.QuerierService.SelectMergeSpanProfile:output_type -> querier.v1.SelectMergeSpanProfileResponse + 28, // 37: querier.v1.QuerierService.SelectMergeProfile:output_type -> google.v1.Profile + 16, // 38: querier.v1.QuerierService.SelectSeries:output_type -> querier.v1.SelectSeriesResponse + 10, // 39: querier.v1.QuerierService.Diff:output_type -> querier.v1.DiffResponse + 34, // 40: querier.v1.QuerierService.GetProfileStats:output_type -> types.v1.GetProfileStatsResponse + 18, // 41: querier.v1.QuerierService.AnalyzeQuery:output_type -> querier.v1.AnalyzeQueryResponse + 22, // 42: querier.v1.QuerierService.GetProfile:output_type -> querier.v1.GetProfileResponse + 31, // [31:43] is the sub-list for method output_type + 19, // [19:31] is the sub-list for method input_type + 19, // [19:19] is the sub-list for extension type_name + 19, // [19:19] is the sub-list for extension extendee + 0, // [0:19] is the sub-list for field type_name } func init() { file_querier_v1_querier_proto_init() } @@ -1697,7 +1811,7 @@ func file_querier_v1_querier_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_querier_v1_querier_proto_rawDesc), len(file_querier_v1_querier_proto_rawDesc)), NumEnums: 1, - NumMessages: 20, + NumMessages: 22, NumExtensions: 0, NumServices: 1, }, diff --git a/api/gen/proto/go/querier/v1/querier_vtproto.pb.go b/api/gen/proto/go/querier/v1/querier_vtproto.pb.go index 517ac6c999..a195675a14 100644 --- a/api/gen/proto/go/querier/v1/querier_vtproto.pb.go +++ b/api/gen/proto/go/querier/v1/querier_vtproto.pb.go @@ -537,6 +537,47 @@ func (m *QueryImpact) CloneMessageVT() proto.Message { return m.CloneVT() } +func (m *GetProfileRequest) CloneVT() *GetProfileRequest { + if m == nil { + return (*GetProfileRequest)(nil) + } + r := new(GetProfileRequest) + r.Id = m.Id + r.Timestamp = m.Timestamp + if len(m.unknownFields) > 0 { + r.unknownFields = make([]byte, len(m.unknownFields)) + copy(r.unknownFields, m.unknownFields) + } + return r +} + +func (m *GetProfileRequest) CloneMessageVT() proto.Message { + return m.CloneVT() +} + +func (m *GetProfileResponse) CloneVT() *GetProfileResponse { + if m == nil { + return (*GetProfileResponse)(nil) + } + r := new(GetProfileResponse) + if rhs := m.Profile; rhs != nil { + if vtpb, ok := interface{}(rhs).(interface{ CloneVT() *v11.Profile }); ok { + r.Profile = vtpb.CloneVT() + } else { + r.Profile = proto.Clone(rhs).(*v11.Profile) + } + } + if len(m.unknownFields) > 0 { + r.unknownFields = make([]byte, len(m.unknownFields)) + copy(r.unknownFields, m.unknownFields) + } + return r +} + +func (m *GetProfileResponse) CloneMessageVT() proto.Message { + return m.CloneVT() +} + func (this *ProfileTypesRequest) EqualVT(that *ProfileTypesRequest) bool { if this == that { return true @@ -1232,6 +1273,51 @@ func (this *QueryImpact) EqualMessageVT(thatMsg proto.Message) bool { } return this.EqualVT(that) } +func (this *GetProfileRequest) EqualVT(that *GetProfileRequest) bool { + if this == that { + return true + } else if this == nil || that == nil { + return false + } + if this.Id != that.Id { + return false + } + if this.Timestamp != that.Timestamp { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *GetProfileRequest) EqualMessageVT(thatMsg proto.Message) bool { + that, ok := thatMsg.(*GetProfileRequest) + if !ok { + return false + } + return this.EqualVT(that) +} +func (this *GetProfileResponse) EqualVT(that *GetProfileResponse) bool { + if this == that { + return true + } else if this == nil || that == nil { + return false + } + if equal, ok := interface{}(this.Profile).(interface{ EqualVT(*v11.Profile) bool }); ok { + if !equal.EqualVT(that.Profile) { + return false + } + } else if !proto.Equal(this.Profile, that.Profile) { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *GetProfileResponse) EqualMessageVT(thatMsg proto.Message) bool { + that, ok := thatMsg.(*GetProfileResponse) + if !ok { + return false + } + return this.EqualVT(that) +} // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. @@ -1271,6 +1357,10 @@ type QuerierServiceClient interface { // GetProfileStats returns profile stats for the current tenant. GetProfileStats(ctx context.Context, in *v1.GetProfileStatsRequest, opts ...grpc.CallOption) (*v1.GetProfileStatsResponse, error) AnalyzeQuery(ctx context.Context, in *AnalyzeQueryRequest, opts ...grpc.CallOption) (*AnalyzeQueryResponse, error) + // GetProfile retrieves an individual profile by its UUID. + // If the profile was split during ingestion (e.g., by span_id), all parts + // will be merged before returning. + GetProfile(ctx context.Context, in *GetProfileRequest, opts ...grpc.CallOption) (*GetProfileResponse, error) } type querierServiceClient struct { @@ -1380,6 +1470,15 @@ func (c *querierServiceClient) AnalyzeQuery(ctx context.Context, in *AnalyzeQuer return out, nil } +func (c *querierServiceClient) GetProfile(ctx context.Context, in *GetProfileRequest, opts ...grpc.CallOption) (*GetProfileResponse, error) { + out := new(GetProfileResponse) + err := c.cc.Invoke(ctx, "/querier.v1.QuerierService/GetProfile", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // QuerierServiceServer is the server API for QuerierService service. // All implementations must embed UnimplementedQuerierServiceServer // for forward compatibility @@ -1413,6 +1512,10 @@ type QuerierServiceServer interface { // GetProfileStats returns profile stats for the current tenant. GetProfileStats(context.Context, *v1.GetProfileStatsRequest) (*v1.GetProfileStatsResponse, error) AnalyzeQuery(context.Context, *AnalyzeQueryRequest) (*AnalyzeQueryResponse, error) + // GetProfile retrieves an individual profile by its UUID. + // If the profile was split during ingestion (e.g., by span_id), all parts + // will be merged before returning. + GetProfile(context.Context, *GetProfileRequest) (*GetProfileResponse, error) mustEmbedUnimplementedQuerierServiceServer() } @@ -1453,6 +1556,9 @@ func (UnimplementedQuerierServiceServer) GetProfileStats(context.Context, *v1.Ge func (UnimplementedQuerierServiceServer) AnalyzeQuery(context.Context, *AnalyzeQueryRequest) (*AnalyzeQueryResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method AnalyzeQuery not implemented") } +func (UnimplementedQuerierServiceServer) GetProfile(context.Context, *GetProfileRequest) (*GetProfileResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetProfile not implemented") +} func (UnimplementedQuerierServiceServer) mustEmbedUnimplementedQuerierServiceServer() {} // UnsafeQuerierServiceServer may be embedded to opt out of forward compatibility for this service. @@ -1664,6 +1770,24 @@ func _QuerierService_AnalyzeQuery_Handler(srv interface{}, ctx context.Context, return interceptor(ctx, in, info, handler) } +func _QuerierService_GetProfile_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetProfileRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QuerierServiceServer).GetProfile(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/querier.v1.QuerierService/GetProfile", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QuerierServiceServer).GetProfile(ctx, req.(*GetProfileRequest)) + } + return interceptor(ctx, in, info, handler) +} + // QuerierService_ServiceDesc is the grpc.ServiceDesc for QuerierService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -1715,6 +1839,10 @@ var QuerierService_ServiceDesc = grpc.ServiceDesc{ MethodName: "AnalyzeQuery", Handler: _QuerierService_AnalyzeQuery_Handler, }, + { + MethodName: "GetProfile", + Handler: _QuerierService_GetProfile_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "querier/v1/querier.proto", @@ -2974,6 +3102,106 @@ func (m *QueryImpact) MarshalToSizedBufferVT(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *GetProfileRequest) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GetProfileRequest) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *GetProfileRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.Timestamp != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Timestamp)) + i-- + dAtA[i] = 0x10 + } + if len(m.Id) > 0 { + i -= len(m.Id) + copy(dAtA[i:], m.Id) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Id))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *GetProfileResponse) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GetProfileResponse) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *GetProfileResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.Profile != nil { + if vtmsg, ok := interface{}(m.Profile).(interface { + MarshalToSizedBufferVT([]byte) (int, error) + }); ok { + size, err := vtmsg.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + } else { + encoded, err := proto.Marshal(m.Profile) + if err != nil { + return 0, err + } + i -= len(encoded) + copy(dAtA[i:], encoded) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(encoded))) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func (m *ProfileTypesRequest) SizeVT() (n int) { if m == nil { return 0 @@ -3490,6 +3718,43 @@ func (m *QueryImpact) SizeVT() (n int) { return n } +func (m *GetProfileRequest) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Id) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.Timestamp != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.Timestamp)) + } + n += len(m.unknownFields) + return n +} + +func (m *GetProfileResponse) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Profile != nil { + if size, ok := interface{}(m.Profile).(interface { + SizeVT() int + }); ok { + l = size.SizeVT() + } else { + l = proto.Size(m.Profile) + } + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + func (m *ProfileTypesRequest) UnmarshalVT(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -6481,3 +6746,200 @@ func (m *QueryImpact) UnmarshalVT(dAtA []byte) error { } return nil } +func (m *GetProfileRequest) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GetProfileRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GetProfileRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Id = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType) + } + m.Timestamp = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Timestamp |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *GetProfileResponse) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GetProfileResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GetProfileResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Profile", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Profile == nil { + m.Profile = &v11.Profile{} + } + if unmarshal, ok := interface{}(m.Profile).(interface { + UnmarshalVT([]byte) error + }); ok { + if err := unmarshal.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + } else { + if err := proto.Unmarshal(dAtA[iNdEx:postIndex], m.Profile); err != nil { + return err + } + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} diff --git a/api/gen/proto/go/querier/v1/querierv1connect/querier.connect.go b/api/gen/proto/go/querier/v1/querierv1connect/querier.connect.go index 542c09fce1..c350e78508 100644 --- a/api/gen/proto/go/querier/v1/querierv1connect/querier.connect.go +++ b/api/gen/proto/go/querier/v1/querierv1connect/querier.connect.go @@ -68,6 +68,9 @@ const ( // QuerierServiceAnalyzeQueryProcedure is the fully-qualified name of the QuerierService's // AnalyzeQuery RPC. QuerierServiceAnalyzeQueryProcedure = "/querier.v1.QuerierService/AnalyzeQuery" + // QuerierServiceGetProfileProcedure is the fully-qualified name of the QuerierService's GetProfile + // RPC. + QuerierServiceGetProfileProcedure = "/querier.v1.QuerierService/GetProfile" ) // QuerierServiceClient is a client for the querier.v1.QuerierService service. @@ -101,6 +104,10 @@ type QuerierServiceClient interface { // GetProfileStats returns profile stats for the current tenant. GetProfileStats(context.Context, *connect.Request[v11.GetProfileStatsRequest]) (*connect.Response[v11.GetProfileStatsResponse], error) AnalyzeQuery(context.Context, *connect.Request[v1.AnalyzeQueryRequest]) (*connect.Response[v1.AnalyzeQueryResponse], error) + // GetProfile retrieves an individual profile by its UUID. + // If the profile was split during ingestion (e.g., by span_id), all parts + // will be merged before returning. + GetProfile(context.Context, *connect.Request[v1.GetProfileRequest]) (*connect.Response[v1.GetProfileResponse], error) } // NewQuerierServiceClient constructs a client for the querier.v1.QuerierService service. By @@ -180,6 +187,12 @@ func NewQuerierServiceClient(httpClient connect.HTTPClient, baseURL string, opts connect.WithSchema(querierServiceMethods.ByName("AnalyzeQuery")), connect.WithClientOptions(opts...), ), + getProfile: connect.NewClient[v1.GetProfileRequest, v1.GetProfileResponse]( + httpClient, + baseURL+QuerierServiceGetProfileProcedure, + connect.WithSchema(querierServiceMethods.ByName("GetProfile")), + connect.WithClientOptions(opts...), + ), } } @@ -196,6 +209,7 @@ type querierServiceClient struct { diff *connect.Client[v1.DiffRequest, v1.DiffResponse] getProfileStats *connect.Client[v11.GetProfileStatsRequest, v11.GetProfileStatsResponse] analyzeQuery *connect.Client[v1.AnalyzeQueryRequest, v1.AnalyzeQueryResponse] + getProfile *connect.Client[v1.GetProfileRequest, v1.GetProfileResponse] } // ProfileTypes calls querier.v1.QuerierService.ProfileTypes. @@ -253,6 +267,11 @@ func (c *querierServiceClient) AnalyzeQuery(ctx context.Context, req *connect.Re return c.analyzeQuery.CallUnary(ctx, req) } +// GetProfile calls querier.v1.QuerierService.GetProfile. +func (c *querierServiceClient) GetProfile(ctx context.Context, req *connect.Request[v1.GetProfileRequest]) (*connect.Response[v1.GetProfileResponse], error) { + return c.getProfile.CallUnary(ctx, req) +} + // QuerierServiceHandler is an implementation of the querier.v1.QuerierService service. type QuerierServiceHandler interface { // ProfileType returns a list of the existing profile types. @@ -284,6 +303,10 @@ type QuerierServiceHandler interface { // GetProfileStats returns profile stats for the current tenant. GetProfileStats(context.Context, *connect.Request[v11.GetProfileStatsRequest]) (*connect.Response[v11.GetProfileStatsResponse], error) AnalyzeQuery(context.Context, *connect.Request[v1.AnalyzeQueryRequest]) (*connect.Response[v1.AnalyzeQueryResponse], error) + // GetProfile retrieves an individual profile by its UUID. + // If the profile was split during ingestion (e.g., by span_id), all parts + // will be merged before returning. + GetProfile(context.Context, *connect.Request[v1.GetProfileRequest]) (*connect.Response[v1.GetProfileResponse], error) } // NewQuerierServiceHandler builds an HTTP handler from the service implementation. It returns the @@ -359,6 +382,12 @@ func NewQuerierServiceHandler(svc QuerierServiceHandler, opts ...connect.Handler connect.WithSchema(querierServiceMethods.ByName("AnalyzeQuery")), connect.WithHandlerOptions(opts...), ) + querierServiceGetProfileHandler := connect.NewUnaryHandler( + QuerierServiceGetProfileProcedure, + svc.GetProfile, + connect.WithSchema(querierServiceMethods.ByName("GetProfile")), + connect.WithHandlerOptions(opts...), + ) return "/querier.v1.QuerierService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case QuerierServiceProfileTypesProcedure: @@ -383,6 +412,8 @@ func NewQuerierServiceHandler(svc QuerierServiceHandler, opts ...connect.Handler querierServiceGetProfileStatsHandler.ServeHTTP(w, r) case QuerierServiceAnalyzeQueryProcedure: querierServiceAnalyzeQueryHandler.ServeHTTP(w, r) + case QuerierServiceGetProfileProcedure: + querierServiceGetProfileHandler.ServeHTTP(w, r) default: http.NotFound(w, r) } @@ -435,3 +466,7 @@ func (UnimplementedQuerierServiceHandler) GetProfileStats(context.Context, *conn func (UnimplementedQuerierServiceHandler) AnalyzeQuery(context.Context, *connect.Request[v1.AnalyzeQueryRequest]) (*connect.Response[v1.AnalyzeQueryResponse], error) { return nil, connect.NewError(connect.CodeUnimplemented, errors.New("querier.v1.QuerierService.AnalyzeQuery is not implemented")) } + +func (UnimplementedQuerierServiceHandler) GetProfile(context.Context, *connect.Request[v1.GetProfileRequest]) (*connect.Response[v1.GetProfileResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("querier.v1.QuerierService.GetProfile is not implemented")) +} diff --git a/api/gen/proto/go/querier/v1/querierv1connect/querier.connect.mux.go b/api/gen/proto/go/querier/v1/querierv1connect/querier.connect.mux.go index 9a82702031..012667c304 100644 --- a/api/gen/proto/go/querier/v1/querierv1connect/querier.connect.mux.go +++ b/api/gen/proto/go/querier/v1/querierv1connect/querier.connect.mux.go @@ -74,4 +74,9 @@ func RegisterQuerierServiceHandler(mux *mux.Router, svc QuerierServiceHandler, o svc.AnalyzeQuery, opts..., )) + mux.Handle("/querier.v1.QuerierService/GetProfile", connect.NewUnaryHandler( + "/querier.v1.QuerierService/GetProfile", + svc.GetProfile, + opts..., + )) } diff --git a/api/gen/proto/go/types/v1/types.pb.go b/api/gen/proto/go/types/v1/types.pb.go index 14d95762be..6ad7322f66 100644 --- a/api/gen/proto/go/types/v1/types.pb.go +++ b/api/gen/proto/go/types/v1/types.pb.go @@ -307,8 +307,10 @@ type Point struct { state protoimpl.MessageState `protogen:"open.v1"` Value float64 `protobuf:"fixed64,1,opt,name=value,proto3" json:"value,omitempty"` // Milliseconds unix timestamp - Timestamp int64 `protobuf:"varint,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"` - Annotations []*ProfileAnnotation `protobuf:"bytes,3,rep,name=annotations,proto3" json:"annotations,omitempty"` + Timestamp int64 `protobuf:"varint,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + Annotations []*ProfileAnnotation `protobuf:"bytes,3,rep,name=annotations,proto3" json:"annotations,omitempty"` + // Exemplars are samples of individual profiles that contributed to this aggregated point + Exemplars []*Exemplar `protobuf:"bytes,4,rep,name=exemplars,proto3" json:"exemplars,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -364,6 +366,13 @@ func (x *Point) GetAnnotations() []*ProfileAnnotation { return nil } +func (x *Point) GetExemplars() []*Exemplar { + if x != nil { + return x.Exemplars + } + return nil +} + // Annotations provide additional metadata for a profile. // // The main differences between labels and annotations are: @@ -1041,6 +1050,83 @@ func (x *GetProfileStatsResponse) GetNewestProfileTime() int64 { return 0 } +// Exemplar represents metadata for an individual profile sample. +// Exemplars allow users to drill down from aggregated timeline views +// to specific profile instances. +type Exemplar struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Milliseconds since epoch when the profile was captured. + Timestamp int64 `protobuf:"varint,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + // Unique identifier for the profile (UUID). + Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` + // Total sample value for this profile (e.g., CPU nanoseconds, bytes allocated). + Value uint64 `protobuf:"varint,3,opt,name=value,proto3" json:"value,omitempty"` + // Optional dynamic labels that were used to split the profile during ingestion + // (e.g., trace_id, span_id). Static series labels are not included as they're + // already present in the parent Series. + Labels []*LabelPair `protobuf:"bytes,4,rep,name=labels,proto3" json:"labels,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Exemplar) Reset() { + *x = Exemplar{} + mi := &file_types_v1_types_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Exemplar) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Exemplar) ProtoMessage() {} + +func (x *Exemplar) ProtoReflect() protoreflect.Message { + mi := &file_types_v1_types_proto_msgTypes[17] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Exemplar.ProtoReflect.Descriptor instead. +func (*Exemplar) Descriptor() ([]byte, []int) { + return file_types_v1_types_proto_rawDescGZIP(), []int{17} +} + +func (x *Exemplar) GetTimestamp() int64 { + if x != nil { + return x.Timestamp + } + return 0 +} + +func (x *Exemplar) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Exemplar) GetValue() uint64 { + if x != nil { + return x.Value + } + return 0 +} + +func (x *Exemplar) GetLabels() []*LabelPair { + if x != nil { + return x.Labels + } + return nil +} + var File_types_v1_types_proto protoreflect.FileDescriptor const file_types_v1_types_proto_rawDesc = "" + @@ -1065,11 +1151,12 @@ const file_types_v1_types_proto_rawDesc = "" + "\x06labels\x18\x01 \x03(\v2\x13.types.v1.LabelPairR\x06labels\"^\n" + "\x06Series\x12+\n" + "\x06labels\x18\x01 \x03(\v2\x13.types.v1.LabelPairR\x06labels\x12'\n" + - "\x06points\x18\x02 \x03(\v2\x0f.types.v1.PointR\x06points\"z\n" + + "\x06points\x18\x02 \x03(\v2\x0f.types.v1.PointR\x06points\"\xac\x01\n" + "\x05Point\x12\x14\n" + "\x05value\x18\x01 \x01(\x01R\x05value\x12\x1c\n" + "\ttimestamp\x18\x02 \x01(\x03R\ttimestamp\x12=\n" + - "\vannotations\x18\x03 \x03(\v2\x1b.types.v1.ProfileAnnotationR\vannotations\";\n" + + "\vannotations\x18\x03 \x03(\v2\x1b.types.v1.ProfileAnnotationR\vannotations\x120\n" + + "\texemplars\x18\x04 \x03(\v2\x12.types.v1.ExemplarR\texemplars\";\n" + "\x11ProfileAnnotation\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value\"\xad\x01\n" + @@ -1110,7 +1197,13 @@ const file_types_v1_types_proto_rawDesc = "" + "\x17GetProfileStatsResponse\x12#\n" + "\rdata_ingested\x18\x01 \x01(\bR\fdataIngested\x12.\n" + "\x13oldest_profile_time\x18\x02 \x01(\x03R\x11oldestProfileTime\x12.\n" + - "\x13newest_profile_time\x18\x03 \x01(\x03R\x11newestProfileTime*k\n" + + "\x13newest_profile_time\x18\x03 \x01(\x03R\x11newestProfileTime\"\xd1\x01\n" + + "\bExemplar\x122\n" + + "\ttimestamp\x18\x01 \x01(\x03B\x14\xbaG\x11:\x0f\x12\r1730000023000R\ttimestamp\x12;\n" + + "\x02id\x18\x02 \x01(\tB+\xbaG(:&\x12$7c9e6679-7425-40de-944b-e07fc1f90ae7R\x02id\x12'\n" + + "\x05value\x18\x03 \x01(\x04B\x11\xbaG\x0e:\f\x12\n" + + "2450000000R\x05value\x12+\n" + + "\x06labels\x18\x04 \x03(\v2\x13.types.v1.LabelPairR\x06labels*k\n" + "\x19TimeSeriesAggregationType\x12$\n" + " TIME_SERIES_AGGREGATION_TYPE_SUM\x10\x00\x12(\n" + "$TIME_SERIES_AGGREGATION_TYPE_AVERAGE\x10\x01B\x9b\x01\n" + @@ -1130,7 +1223,7 @@ func file_types_v1_types_proto_rawDescGZIP() []byte { } var file_types_v1_types_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_types_v1_types_proto_msgTypes = make([]protoimpl.MessageInfo, 17) +var file_types_v1_types_proto_msgTypes = make([]protoimpl.MessageInfo, 18) var file_types_v1_types_proto_goTypes = []any{ (TimeSeriesAggregationType)(0), // 0: types.v1.TimeSeriesAggregationType (*LabelPair)(nil), // 1: types.v1.LabelPair @@ -1150,21 +1243,24 @@ var file_types_v1_types_proto_goTypes = []any{ (*GoPGO)(nil), // 15: types.v1.GoPGO (*GetProfileStatsRequest)(nil), // 16: types.v1.GetProfileStatsRequest (*GetProfileStatsResponse)(nil), // 17: types.v1.GetProfileStatsResponse + (*Exemplar)(nil), // 18: types.v1.Exemplar } var file_types_v1_types_proto_depIdxs = []int32{ 1, // 0: types.v1.Labels.labels:type_name -> types.v1.LabelPair 1, // 1: types.v1.Series.labels:type_name -> types.v1.LabelPair 5, // 2: types.v1.Series.points:type_name -> types.v1.Point 6, // 3: types.v1.Point.annotations:type_name -> types.v1.ProfileAnnotation - 12, // 4: types.v1.BlockInfo.compaction:type_name -> types.v1.BlockCompaction - 1, // 5: types.v1.BlockInfo.labels:type_name -> types.v1.LabelPair - 14, // 6: types.v1.StackTraceSelector.call_site:type_name -> types.v1.Location - 15, // 7: types.v1.StackTraceSelector.go_pgo:type_name -> types.v1.GoPGO - 8, // [8:8] is the sub-list for method output_type - 8, // [8:8] is the sub-list for method input_type - 8, // [8:8] is the sub-list for extension type_name - 8, // [8:8] is the sub-list for extension extendee - 0, // [0:8] is the sub-list for field type_name + 18, // 4: types.v1.Point.exemplars:type_name -> types.v1.Exemplar + 12, // 5: types.v1.BlockInfo.compaction:type_name -> types.v1.BlockCompaction + 1, // 6: types.v1.BlockInfo.labels:type_name -> types.v1.LabelPair + 14, // 7: types.v1.StackTraceSelector.call_site:type_name -> types.v1.Location + 15, // 8: types.v1.StackTraceSelector.go_pgo:type_name -> types.v1.GoPGO + 1, // 9: types.v1.Exemplar.labels:type_name -> types.v1.LabelPair + 10, // [10:10] is the sub-list for method output_type + 10, // [10:10] is the sub-list for method input_type + 10, // [10:10] is the sub-list for extension type_name + 10, // [10:10] is the sub-list for extension extendee + 0, // [0:10] is the sub-list for field type_name } func init() { file_types_v1_types_proto_init() } @@ -1178,7 +1274,7 @@ func file_types_v1_types_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_types_v1_types_proto_rawDesc), len(file_types_v1_types_proto_rawDesc)), NumEnums: 1, - NumMessages: 17, + NumMessages: 18, NumExtensions: 0, NumServices: 0, }, diff --git a/api/gen/proto/go/types/v1/types_vtproto.pb.go b/api/gen/proto/go/types/v1/types_vtproto.pb.go index c9bd53bbd3..b18e6d598a 100644 --- a/api/gen/proto/go/types/v1/types_vtproto.pb.go +++ b/api/gen/proto/go/types/v1/types_vtproto.pb.go @@ -128,6 +128,13 @@ func (m *Point) CloneVT() *Point { } r.Annotations = tmpContainer } + if rhs := m.Exemplars; rhs != nil { + tmpContainer := make([]*Exemplar, len(rhs)) + for k, v := range rhs { + tmpContainer[k] = v.CloneVT() + } + r.Exemplars = tmpContainer + } if len(m.unknownFields) > 0 { r.unknownFields = make([]byte, len(m.unknownFields)) copy(r.unknownFields, m.unknownFields) @@ -394,6 +401,32 @@ func (m *GetProfileStatsResponse) CloneMessageVT() proto.Message { return m.CloneVT() } +func (m *Exemplar) CloneVT() *Exemplar { + if m == nil { + return (*Exemplar)(nil) + } + r := new(Exemplar) + r.Timestamp = m.Timestamp + r.Id = m.Id + r.Value = m.Value + if rhs := m.Labels; rhs != nil { + tmpContainer := make([]*LabelPair, len(rhs)) + for k, v := range rhs { + tmpContainer[k] = v.CloneVT() + } + r.Labels = tmpContainer + } + if len(m.unknownFields) > 0 { + r.unknownFields = make([]byte, len(m.unknownFields)) + copy(r.unknownFields, m.unknownFields) + } + return r +} + +func (m *Exemplar) CloneMessageVT() proto.Message { + return m.CloneVT() +} + func (this *LabelPair) EqualVT(that *LabelPair) bool { if this == that { return true @@ -562,6 +595,23 @@ func (this *Point) EqualVT(that *Point) bool { } } } + if len(this.Exemplars) != len(that.Exemplars) { + return false + } + for i, vx := range this.Exemplars { + vy := that.Exemplars[i] + if p, q := vx, vy; p != q { + if p == nil { + p = &Exemplar{} + } + if q == nil { + q = &Exemplar{} + } + if !p.EqualVT(q) { + return false + } + } + } return string(this.unknownFields) == string(that.unknownFields) } @@ -909,6 +959,48 @@ func (this *GetProfileStatsResponse) EqualMessageVT(thatMsg proto.Message) bool } return this.EqualVT(that) } +func (this *Exemplar) EqualVT(that *Exemplar) bool { + if this == that { + return true + } else if this == nil || that == nil { + return false + } + if this.Timestamp != that.Timestamp { + return false + } + if this.Id != that.Id { + return false + } + if this.Value != that.Value { + return false + } + if len(this.Labels) != len(that.Labels) { + return false + } + for i, vx := range this.Labels { + vy := that.Labels[i] + if p, q := vx, vy; p != q { + if p == nil { + p = &LabelPair{} + } + if q == nil { + q = &LabelPair{} + } + if !p.EqualVT(q) { + return false + } + } + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *Exemplar) EqualMessageVT(thatMsg proto.Message) bool { + that, ok := thatMsg.(*Exemplar) + if !ok { + return false + } + return this.EqualVT(that) +} func (m *LabelPair) MarshalVT() (dAtA []byte, err error) { if m == nil { return nil, nil @@ -1163,6 +1255,18 @@ func (m *Point) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if len(m.Exemplars) > 0 { + for iNdEx := len(m.Exemplars) - 1; iNdEx >= 0; iNdEx-- { + size, err := m.Exemplars[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x22 + } + } if len(m.Annotations) > 0 { for iNdEx := len(m.Annotations) - 1; iNdEx >= 0; iNdEx-- { size, err := m.Annotations[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) @@ -1788,6 +1892,68 @@ func (m *GetProfileStatsResponse) MarshalToSizedBufferVT(dAtA []byte) (int, erro return len(dAtA) - i, nil } +func (m *Exemplar) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Exemplar) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *Exemplar) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if len(m.Labels) > 0 { + for iNdEx := len(m.Labels) - 1; iNdEx >= 0; iNdEx-- { + size, err := m.Labels[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x22 + } + } + if m.Value != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Value)) + i-- + dAtA[i] = 0x18 + } + if len(m.Id) > 0 { + i -= len(m.Id) + copy(dAtA[i:], m.Id) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Id))) + i-- + dAtA[i] = 0x12 + } + if m.Timestamp != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Timestamp)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + func (m *LabelPair) SizeVT() (n int) { if m == nil { return 0 @@ -1896,6 +2062,12 @@ func (m *Point) SizeVT() (n int) { n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } } + if len(m.Exemplars) > 0 { + for _, e := range m.Exemplars { + l = e.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + } n += len(m.unknownFields) return n } @@ -2132,6 +2304,32 @@ func (m *GetProfileStatsResponse) SizeVT() (n int) { return n } +func (m *Exemplar) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Timestamp != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.Timestamp)) + } + l = len(m.Id) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.Value != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.Value)) + } + if len(m.Labels) > 0 { + for _, e := range m.Labels { + l = e.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + } + n += len(m.unknownFields) + return n +} + func (m *LabelPair) UnmarshalVT(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -2787,6 +2985,40 @@ func (m *Point) UnmarshalVT(dAtA []byte) error { return err } iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Exemplars", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Exemplars = append(m.Exemplars, &Exemplar{}) + if err := m.Exemplars[len(m.Exemplars)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) @@ -4143,3 +4375,158 @@ func (m *GetProfileStatsResponse) UnmarshalVT(dAtA []byte) error { } return nil } +func (m *Exemplar) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Exemplar: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Exemplar: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType) + } + m.Timestamp = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Timestamp |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Id = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) + } + m.Value = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Value |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Labels", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Labels = append(m.Labels, &LabelPair{}) + if err := m.Labels[len(m.Labels)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} diff --git a/api/openapiv2/gen/phlare.swagger.json b/api/openapiv2/gen/phlare.swagger.json index 25e04f0a34..10f2f96537 100644 --- a/api/openapiv2/gen/phlare.swagger.json +++ b/api/openapiv2/gen/phlare.swagger.json @@ -961,6 +961,34 @@ } } }, + "v1Exemplar": { + "type": "object", + "properties": { + "timestamp": { + "type": "string", + "format": "int64", + "description": "Milliseconds since epoch when the profile was captured." + }, + "id": { + "type": "string", + "description": "Unique identifier for the profile (UUID)." + }, + "value": { + "type": "string", + "format": "uint64", + "description": "Total sample value for this profile (e.g., CPU nanoseconds, bytes allocated)." + }, + "labels": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1LabelPair" + }, + "description": "Optional dynamic labels that were used to split the profile during ingestion\n(e.g., trace_id, span_id). Static series labels are not included as they're\nalready present in the parent Series." + } + }, + "description": "Exemplar represents metadata for an individual profile sample.\nExemplars allow users to drill down from aggregated timeline views\nto specific profile instances." + }, "v1FeatureFlag": { "type": "object", "properties": { @@ -1197,6 +1225,15 @@ } } }, + "v1GetProfileResponse": { + "type": "object", + "properties": { + "profile": { + "$ref": "#/definitions/googlev1Profile", + "title": "Complete pprof profile (merged if it was split during ingestion)" + } + } + }, "v1GetProfileStatsResponse": { "type": "object", "properties": { @@ -1653,6 +1690,14 @@ "type": "object", "$ref": "#/definitions/v1ProfileAnnotation" } + }, + "exemplars": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1Exemplar" + }, + "title": "Exemplars are samples of individual profiles that contributed to this aggregated point" } } }, diff --git a/api/querier/v1/querier.proto b/api/querier/v1/querier.proto index e44d78cdc0..27deee9757 100644 --- a/api/querier/v1/querier.proto +++ b/api/querier/v1/querier.proto @@ -62,6 +62,13 @@ service QuerierService { rpc AnalyzeQuery(AnalyzeQueryRequest) returns (AnalyzeQueryResponse) { option (gnostic.openapi.v3.operation).tags = "scope/internal"; } + + // GetProfile retrieves an individual profile by its UUID. + // If the profile was split during ingestion (e.g., by span_id), all parts + // will be merged before returning. + rpc GetProfile(GetProfileRequest) returns (GetProfileResponse) { + option (gnostic.openapi.v3.operation).tags = "scope/public"; + } } message ProfileTypesRequest { @@ -254,3 +261,18 @@ message QueryImpact { uint64 total_queried_series = 3; bool deduplication_needed = 4; } + +message GetProfileRequest { + // Profile UUID from Exemplar.id field (required) + string id = 1 [(gnostic.openapi.v3.property).example = {yaml: "7c9e6679-7425-40de-944b-e07fc1f90ae7"}]; + + // Profile timestamp in milliseconds since epoch. + // Used to narrow down which blocks to query. If not provided, all blocks will be scanned which is expensive. + // This timestamp is available from Exemplar.timestamp in the SelectSeries response. + int64 timestamp = 2 [(gnostic.openapi.v3.property).example = {yaml: "1730000023000"}]; +} + +message GetProfileResponse { + // Complete pprof profile (merged if it was split during ingestion) + google.v1.Profile profile = 1; +} diff --git a/api/types/v1/types.proto b/api/types/v1/types.proto index d090d71cf5..7cb7482682 100644 --- a/api/types/v1/types.proto +++ b/api/types/v1/types.proto @@ -36,6 +36,8 @@ message Point { // Milliseconds unix timestamp int64 timestamp = 2; repeated ProfileAnnotation annotations = 3; + // Exemplars are samples of individual profiles that contributed to this aggregated point + repeated Exemplar exemplars = 4; } // Annotations provide additional metadata for a profile. @@ -133,3 +135,22 @@ message GetProfileStatsResponse { // Milliseconds since epoch. int64 newest_profile_time = 3; } + +// Exemplar represents metadata for an individual profile sample. +// Exemplars allow users to drill down from aggregated timeline views +// to specific profile instances. +message Exemplar { + // Milliseconds since epoch when the profile was captured. + int64 timestamp = 1 [(gnostic.openapi.v3.property).example = {yaml: "1730000023000"}]; + + // Unique identifier for the profile (UUID). + string id = 2 [(gnostic.openapi.v3.property).example = {yaml: "7c9e6679-7425-40de-944b-e07fc1f90ae7"}]; + + // Total sample value for this profile (e.g., CPU nanoseconds, bytes allocated). + uint64 value = 3 [(gnostic.openapi.v3.property).example = {yaml: "2450000000"}]; + + // Optional dynamic labels that were used to split the profile during ingestion + // (e.g., trace_id, span_id). Static series labels are not included as they're + // already present in the parent Series. + repeated LabelPair labels = 4; +} diff --git a/docs/sources/reference-server-api/index.md b/docs/sources/reference-server-api/index.md index 46c76fcb8f..f2b91dcfe3 100644 --- a/docs/sources/reference-server-api/index.md +++ b/docs/sources/reference-server-api/index.md @@ -184,6 +184,43 @@ print(resp) print(resp.content) ``` +{{% /code %}} +#### `/querier.v1.QuerierService/GetProfile` + +GetProfile retrieves an individual profile by its UUID. + If the profile was split during ingestion (e.g., by span_id), all parts + will be merged before returning. + +A request body with the following fields is required: + +|Field | Description | Example | +|:-----|:------------|:--------| +|`id` | Profile UUID from Exemplar.id field (required) | `7c9e6679-7425-40de-944b-e07fc1f90ae7` | +|`timestamp` | Profile timestamp in milliseconds since epoch. Used to narrow down which blocks to query. If not provided, all blocks will be scanned which is expensive. This timestamp is available from Exemplar.timestamp in the SelectSeries response. | `1730000023000` | + +{{% code %}} +```curl +curl \ + -H "Content-Type: application/json" \ + -d '{ + "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", + "timestamp": "1730000023000" + }' \ + http://localhost:4040/querier.v1.QuerierService/GetProfile +``` + +```python +import requests +body = { + "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", + "timestamp": "1730000023000" + } +url = 'http://localhost:4040/querier.v1.QuerierService/GetProfile' +resp = requests.post(url, json=body) +print(resp) +print(resp.content) +``` + {{% /code %}} #### `/querier.v1.QuerierService/LabelNames` diff --git a/pkg/frontend/frontend_get_profile.go b/pkg/frontend/frontend_get_profile.go new file mode 100644 index 0000000000..89fd8b2814 --- /dev/null +++ b/pkg/frontend/frontend_get_profile.go @@ -0,0 +1,18 @@ +package frontend + +import ( + "context" + + "connectrpc.com/connect" + + querierv1 "github.com/grafana/pyroscope/api/gen/proto/go/querier/v1" +) + +// GetProfile is a stub for v1 frontend compatibility. +// This feature only implemented in v2 architecture. +func (f *Frontend) GetProfile( + ctx context.Context, + req *connect.Request[querierv1.GetProfileRequest], +) (*connect.Response[querierv1.GetProfileResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, nil) +} diff --git a/pkg/frontend/readpath/query_service_handler.go b/pkg/frontend/readpath/query_service_handler.go index 2a771e1afb..c549d9caa0 100644 --- a/pkg/frontend/readpath/query_service_handler.go +++ b/pkg/frontend/readpath/query_service_handler.go @@ -248,3 +248,11 @@ func (r *Router) ProfileTypes( return &querierv1.ProfileTypesResponse{ProfileTypes: pTypes}, nil }) } + +func (r *Router) GetProfile( + ctx context.Context, + c *connect.Request[querierv1.GetProfileRequest], +) (*connect.Response[querierv1.GetProfileResponse], error) { + // GetProfile is a v2-only feature. + return r.newFrontend.GetProfile(ctx, c) +} diff --git a/pkg/frontend/readpath/queryfrontend/query_frontend_get_profile.go b/pkg/frontend/readpath/queryfrontend/query_frontend_get_profile.go new file mode 100644 index 0000000000..d8b920babf --- /dev/null +++ b/pkg/frontend/readpath/queryfrontend/query_frontend_get_profile.go @@ -0,0 +1,21 @@ +package queryfrontend + +import ( + "context" + + "connectrpc.com/connect" + + querierv1 "github.com/grafana/pyroscope/api/gen/proto/go/querier/v1" +) + +func (q *QueryFrontend) GetProfile( + ctx context.Context, + req *connect.Request[querierv1.GetProfileRequest], +) (*connect.Response[querierv1.GetProfileResponse], error) { + // TODO: Implement profile retrieval by UUID for v2 architecture + // This should: + // 1. Query blocks via metastore for rows matching the UUID + // 2. Merge split profiles if necessary (using pprof.ProfileMerge) + // 3. Return the complete profile + return nil, connect.NewError(connect.CodeUnimplemented, nil) +} diff --git a/pkg/querier/querier_get_profile.go b/pkg/querier/querier_get_profile.go new file mode 100644 index 0000000000..46bca400bd --- /dev/null +++ b/pkg/querier/querier_get_profile.go @@ -0,0 +1,18 @@ +package querier + +import ( + "context" + + "connectrpc.com/connect" + + querierv1 "github.com/grafana/pyroscope/api/gen/proto/go/querier/v1" +) + +// GetProfile is a stub for v1 architecture compatibility. +// This feature is only implemented in v2 architecture. +func (q *Querier) GetProfile( + ctx context.Context, + req *connect.Request[querierv1.GetProfileRequest], +) (*connect.Response[querierv1.GetProfileResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, nil) +} diff --git a/pkg/test/mocks/mockquerierv1connect/mock_querier_service_client.go b/pkg/test/mocks/mockquerierv1connect/mock_querier_service_client.go index 4e0a442c69..365c2c69b9 100644 --- a/pkg/test/mocks/mockquerierv1connect/mock_querier_service_client.go +++ b/pkg/test/mocks/mockquerierv1connect/mock_querier_service_client.go @@ -147,6 +147,65 @@ func (_c *MockQuerierServiceClient_Diff_Call) RunAndReturn(run func(context.Cont return _c } +// GetProfile provides a mock function with given fields: _a0, _a1 +func (_m *MockQuerierServiceClient) GetProfile(_a0 context.Context, _a1 *connect.Request[querierv1.GetProfileRequest]) (*connect.Response[querierv1.GetProfileResponse], error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for GetProfile") + } + + var r0 *connect.Response[querierv1.GetProfileResponse] + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *connect.Request[querierv1.GetProfileRequest]) (*connect.Response[querierv1.GetProfileResponse], error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, *connect.Request[querierv1.GetProfileRequest]) *connect.Response[querierv1.GetProfileResponse]); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*connect.Response[querierv1.GetProfileResponse]) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *connect.Request[querierv1.GetProfileRequest]) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockQuerierServiceClient_GetProfile_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetProfile' +type MockQuerierServiceClient_GetProfile_Call struct { + *mock.Call +} + +// GetProfile is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 *connect.Request[querierv1.GetProfileRequest] +func (_e *MockQuerierServiceClient_Expecter) GetProfile(_a0 interface{}, _a1 interface{}) *MockQuerierServiceClient_GetProfile_Call { + return &MockQuerierServiceClient_GetProfile_Call{Call: _e.mock.On("GetProfile", _a0, _a1)} +} + +func (_c *MockQuerierServiceClient_GetProfile_Call) Run(run func(_a0 context.Context, _a1 *connect.Request[querierv1.GetProfileRequest])) *MockQuerierServiceClient_GetProfile_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*connect.Request[querierv1.GetProfileRequest])) + }) + return _c +} + +func (_c *MockQuerierServiceClient_GetProfile_Call) Return(_a0 *connect.Response[querierv1.GetProfileResponse], _a1 error) *MockQuerierServiceClient_GetProfile_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockQuerierServiceClient_GetProfile_Call) RunAndReturn(run func(context.Context, *connect.Request[querierv1.GetProfileRequest]) (*connect.Response[querierv1.GetProfileResponse], error)) *MockQuerierServiceClient_GetProfile_Call { + _c.Call.Return(run) + return _c +} + // GetProfileStats provides a mock function with given fields: _a0, _a1 func (_m *MockQuerierServiceClient) GetProfileStats(_a0 context.Context, _a1 *connect.Request[typesv1.GetProfileStatsRequest]) (*connect.Response[typesv1.GetProfileStatsResponse], error) { ret := _m.Called(_a0, _a1) diff --git a/pkg/util/spanlogger/query_log.go b/pkg/util/spanlogger/query_log.go index 2847f21168..e83fede251 100644 --- a/pkg/util/spanlogger/query_log.go +++ b/pkg/util/spanlogger/query_log.go @@ -211,6 +211,19 @@ func (l LogSpanParametersWrapper) AnalyzeQuery(ctx context.Context, c *connect.R return l.client.AnalyzeQuery(ctx, c) } +func (l LogSpanParametersWrapper) GetProfile(ctx context.Context, c *connect.Request[querierv1.GetProfileRequest]) (*connect.Response[querierv1.GetProfileResponse], error) { + spanName := "GetProfile" + sp, ctx := opentracing.StartSpanFromContext(ctx, spanName) + level.Info(FromContext(ctx, l.logger)).Log( + "method", spanName, + "id", c.Msg.Id, + "timestamp", model.Time(c.Msg.Timestamp).Time().String(), + ) + defer sp.Finish() + + return l.client.GetProfile(ctx, c) +} + type LazyJoin struct { strs []string sep string From ecc872c4fd010e273811eb6785c7d64622e99105 Mon Sep 17 00:00:00 2001 From: Marc Sanmiquel Date: Tue, 11 Nov 2025 16:13:22 +0100 Subject: [PATCH 2/6] update Exemplar labels proto description --- api/connect-openapi/gen/ingester/v1/ingester.openapi.yaml | 8 +++++--- api/connect-openapi/gen/querier/v1/querier.openapi.yaml | 8 +++++--- api/connect-openapi/gen/query/v1/query.openapi.yaml | 8 +++++--- .../gen/storegateway/v1/storegateway.openapi.yaml | 8 +++++--- api/gen/proto/go/types/v1/types.pb.go | 8 +++++--- api/openapiv2/gen/phlare.swagger.json | 2 +- api/types/v1/types.proto | 8 +++++--- 7 files changed, 31 insertions(+), 19 deletions(-) diff --git a/api/connect-openapi/gen/ingester/v1/ingester.openapi.yaml b/api/connect-openapi/gen/ingester/v1/ingester.openapi.yaml index da109da316..bdc08799f8 100644 --- a/api/connect-openapi/gen/ingester/v1/ingester.openapi.yaml +++ b/api/connect-openapi/gen/ingester/v1/ingester.openapi.yaml @@ -1081,9 +1081,11 @@ components: $ref: '#/components/schemas/types.v1.LabelPair' title: labels description: |- - Optional dynamic labels that were used to split the profile during ingestion - (e.g., trace_id, span_id). Static series labels are not included as they're - already present in the parent Series. + Series labels that are NOT included in the group_by query parameter. + These labels complete the full series identity of this exemplar's profile. + For example, if group_by=["service"], this would contain labels like "pod", + "namespace", "region" that were omitted from grouping. This allows identifying + which specific series instance this profile sample came from. title: Exemplar additionalProperties: false description: |- diff --git a/api/connect-openapi/gen/querier/v1/querier.openapi.yaml b/api/connect-openapi/gen/querier/v1/querier.openapi.yaml index 17f057964c..dc0650dfb8 100644 --- a/api/connect-openapi/gen/querier/v1/querier.openapi.yaml +++ b/api/connect-openapi/gen/querier/v1/querier.openapi.yaml @@ -1596,9 +1596,11 @@ components: $ref: '#/components/schemas/types.v1.LabelPair' title: labels description: |- - Optional dynamic labels that were used to split the profile during ingestion - (e.g., trace_id, span_id). Static series labels are not included as they're - already present in the parent Series. + Series labels that are NOT included in the group_by query parameter. + These labels complete the full series identity of this exemplar's profile. + For example, if group_by=["service"], this would contain labels like "pod", + "namespace", "region" that were omitted from grouping. This allows identifying + which specific series instance this profile sample came from. title: Exemplar additionalProperties: false description: |- diff --git a/api/connect-openapi/gen/query/v1/query.openapi.yaml b/api/connect-openapi/gen/query/v1/query.openapi.yaml index 617fb94bf8..9aed27e7f3 100644 --- a/api/connect-openapi/gen/query/v1/query.openapi.yaml +++ b/api/connect-openapi/gen/query/v1/query.openapi.yaml @@ -758,9 +758,11 @@ components: $ref: '#/components/schemas/types.v1.LabelPair' title: labels description: |- - Optional dynamic labels that were used to split the profile during ingestion - (e.g., trace_id, span_id). Static series labels are not included as they're - already present in the parent Series. + Series labels that are NOT included in the group_by query parameter. + These labels complete the full series identity of this exemplar's profile. + For example, if group_by=["service"], this would contain labels like "pod", + "namespace", "region" that were omitted from grouping. This allows identifying + which specific series instance this profile sample came from. title: Exemplar additionalProperties: false description: |- diff --git a/api/connect-openapi/gen/storegateway/v1/storegateway.openapi.yaml b/api/connect-openapi/gen/storegateway/v1/storegateway.openapi.yaml index 2cb3520c6a..28bea6ef01 100644 --- a/api/connect-openapi/gen/storegateway/v1/storegateway.openapi.yaml +++ b/api/connect-openapi/gen/storegateway/v1/storegateway.openapi.yaml @@ -900,9 +900,11 @@ components: $ref: '#/components/schemas/types.v1.LabelPair' title: labels description: |- - Optional dynamic labels that were used to split the profile during ingestion - (e.g., trace_id, span_id). Static series labels are not included as they're - already present in the parent Series. + Series labels that are NOT included in the group_by query parameter. + These labels complete the full series identity of this exemplar's profile. + For example, if group_by=["service"], this would contain labels like "pod", + "namespace", "region" that were omitted from grouping. This allows identifying + which specific series instance this profile sample came from. title: Exemplar additionalProperties: false description: |- diff --git a/api/gen/proto/go/types/v1/types.pb.go b/api/gen/proto/go/types/v1/types.pb.go index 6ad7322f66..c69a7d1306 100644 --- a/api/gen/proto/go/types/v1/types.pb.go +++ b/api/gen/proto/go/types/v1/types.pb.go @@ -1061,9 +1061,11 @@ type Exemplar struct { Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` // Total sample value for this profile (e.g., CPU nanoseconds, bytes allocated). Value uint64 `protobuf:"varint,3,opt,name=value,proto3" json:"value,omitempty"` - // Optional dynamic labels that were used to split the profile during ingestion - // (e.g., trace_id, span_id). Static series labels are not included as they're - // already present in the parent Series. + // Series labels that are NOT included in the group_by query parameter. + // These labels complete the full series identity of this exemplar's profile. + // For example, if group_by=["service"], this would contain labels like "pod", + // "namespace", "region" that were omitted from grouping. This allows identifying + // which specific series instance this profile sample came from. Labels []*LabelPair `protobuf:"bytes,4,rep,name=labels,proto3" json:"labels,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache diff --git a/api/openapiv2/gen/phlare.swagger.json b/api/openapiv2/gen/phlare.swagger.json index 10f2f96537..2aea172c53 100644 --- a/api/openapiv2/gen/phlare.swagger.json +++ b/api/openapiv2/gen/phlare.swagger.json @@ -984,7 +984,7 @@ "type": "object", "$ref": "#/definitions/v1LabelPair" }, - "description": "Optional dynamic labels that were used to split the profile during ingestion\n(e.g., trace_id, span_id). Static series labels are not included as they're\nalready present in the parent Series." + "description": "Series labels that are NOT included in the group_by query parameter.\nThese labels complete the full series identity of this exemplar's profile.\nFor example, if group_by=[\"service\"], this would contain labels like \"pod\",\n\"namespace\", \"region\" that were omitted from grouping. This allows identifying\nwhich specific series instance this profile sample came from." } }, "description": "Exemplar represents metadata for an individual profile sample.\nExemplars allow users to drill down from aggregated timeline views\nto specific profile instances." diff --git a/api/types/v1/types.proto b/api/types/v1/types.proto index 7cb7482682..06165a153f 100644 --- a/api/types/v1/types.proto +++ b/api/types/v1/types.proto @@ -149,8 +149,10 @@ message Exemplar { // Total sample value for this profile (e.g., CPU nanoseconds, bytes allocated). uint64 value = 3 [(gnostic.openapi.v3.property).example = {yaml: "2450000000"}]; - // Optional dynamic labels that were used to split the profile during ingestion - // (e.g., trace_id, span_id). Static series labels are not included as they're - // already present in the parent Series. + // Series labels that are NOT included in the group_by query parameter. + // These labels complete the full series identity of this exemplar's profile. + // For example, if group_by=["service"], this would contain labels like "pod", + // "namespace", "region" that were omitted from grouping. This allows identifying + // which specific series instance this profile sample came from. repeated LabelPair labels = 4; } From da300889e8f8d6bef0cd1a1494bf78ef8100bf30 Mon Sep 17 00:00:00 2001 From: Marc Sanmiquel Date: Wed, 12 Nov 2025 10:18:48 +0100 Subject: [PATCH 3/6] Remove GetProfile endpoint and add span_id --- .../gen/ingester/v1/ingester.openapi.yaml | 10 +- .../gen/querier/v1/querier.openapi.yaml | 92 +-- .../gen/query/v1/query.openapi.yaml | 10 +- .../storegateway/v1/storegateway.openapi.yaml | 10 +- api/gen/proto/go/querier/v1/querier.pb.go | 234 +++----- .../proto/go/querier/v1/querier_vtproto.pb.go | 523 ++---------------- .../v1/querierv1connect/querier.connect.go | 35 -- .../querierv1connect/querier.connect.mux.go | 5 - api/gen/proto/go/types/v1/types.pb.go | 31 +- api/gen/proto/go/types/v1/types_vtproto.pb.go | 71 ++- api/openapiv2/gen/phlare.swagger.json | 22 +- api/querier/v1/querier.proto | 24 +- api/types/v1/types.proto | 9 +- docs/sources/reference-server-api/index.md | 58 +- pkg/frontend/frontend_get_profile.go | 18 - .../readpath/query_service_handler.go | 8 - .../query_frontend_get_profile.go | 21 - pkg/querier/querier_get_profile.go | 18 - .../mock_querier_service_client.go | 59 -- pkg/util/spanlogger/query_log.go | 13 - 20 files changed, 289 insertions(+), 982 deletions(-) delete mode 100644 pkg/frontend/frontend_get_profile.go delete mode 100644 pkg/frontend/readpath/queryfrontend/query_frontend_get_profile.go delete mode 100644 pkg/querier/querier_get_profile.go diff --git a/api/connect-openapi/gen/ingester/v1/ingester.openapi.yaml b/api/connect-openapi/gen/ingester/v1/ingester.openapi.yaml index bdc08799f8..d2b70f4f04 100644 --- a/api/connect-openapi/gen/ingester/v1/ingester.openapi.yaml +++ b/api/connect-openapi/gen/ingester/v1/ingester.openapi.yaml @@ -1060,12 +1060,18 @@ components: title: timestamp format: int64 description: Milliseconds since epoch when the profile was captured. - id: + profileId: type: string examples: - 7c9e6679-7425-40de-944b-e07fc1f90ae7 - title: id + title: profile_id description: Unique identifier for the profile (UUID). + spanId: + type: string + examples: + - 00f067aa0ba902b7 + title: span_id + description: Span ID if this profile was split by span during ingestion. value: type: - integer diff --git a/api/connect-openapi/gen/querier/v1/querier.openapi.yaml b/api/connect-openapi/gen/querier/v1/querier.openapi.yaml index dc0650dfb8..86bf6b66ed 100644 --- a/api/connect-openapi/gen/querier/v1/querier.openapi.yaml +++ b/api/connect-openapi/gen/querier/v1/querier.openapi.yaml @@ -82,46 +82,6 @@ paths: application/json: schema: $ref: '#/components/schemas/querier.v1.DiffResponse' - /querier.v1.QuerierService/GetProfile: - post: - tags: - - scope/public - - querier.v1.QuerierService - summary: GetProfile retrieves an individual profile by its UUID. If the profile was split during ingestion (e.g., by span_id), all parts will be merged before returning. - description: |- - GetProfile retrieves an individual profile by its UUID. - If the profile was split during ingestion (e.g., by span_id), all parts - will be merged before returning. - operationId: querier.v1.QuerierService.GetProfile - parameters: - - name: Connect-Protocol-Version - in: header - required: true - schema: - $ref: '#/components/schemas/connect-protocol-version' - - name: Connect-Timeout-Ms - in: header - schema: - $ref: '#/components/schemas/connect-timeout-header' - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/querier.v1.GetProfileRequest' - required: true - responses: - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/connect.error' - "200": - description: Success - content: - application/json: - schema: - $ref: '#/components/schemas/querier.v1.GetProfileResponse' /querier.v1.QuerierService/GetProfileStats: post: tags: @@ -1064,38 +1024,6 @@ components: format: int64 title: FlameGraphDiff additionalProperties: false - querier.v1.GetProfileRequest: - type: object - properties: - id: - type: string - examples: - - 7c9e6679-7425-40de-944b-e07fc1f90ae7 - title: id - description: Profile UUID from Exemplar.id field (required) - timestamp: - type: - - integer - - string - examples: - - "1730000023000" - title: timestamp - format: int64 - description: |- - Profile timestamp in milliseconds since epoch. - Used to narrow down which blocks to query. If not provided, all blocks will be scanned which is expensive. - This timestamp is available from Exemplar.timestamp in the SelectSeries response. - title: GetProfileRequest - additionalProperties: false - querier.v1.GetProfileResponse: - type: object - properties: - profile: - title: profile - description: Complete pprof profile (merged if it was split during ingestion) - $ref: '#/components/schemas/google.v1.Profile' - title: GetProfileResponse - additionalProperties: false querier.v1.Level: type: object properties: @@ -1417,6 +1345,16 @@ components: description: Select stack traces that match the provided selector. nullable: true $ref: '#/components/schemas/types.v1.StackTraceSelector' + profileIdSelector: + type: array + examples: + - - 7c9e6679-7425-40de-944b-e07fc1f90ae7 + items: + type: string + examples: + - - 7c9e6679-7425-40de-944b-e07fc1f90ae7 + title: profile_id_selector + description: List of Profile UUIDs to query title: SelectMergeStacktracesRequest additionalProperties: false querier.v1.SelectMergeStacktracesResponse: @@ -1575,12 +1513,18 @@ components: title: timestamp format: int64 description: Milliseconds since epoch when the profile was captured. - id: + profileId: type: string examples: - 7c9e6679-7425-40de-944b-e07fc1f90ae7 - title: id + title: profile_id description: Unique identifier for the profile (UUID). + spanId: + type: string + examples: + - 00f067aa0ba902b7 + title: span_id + description: Span ID if this profile was split by span during ingestion. value: type: - integer diff --git a/api/connect-openapi/gen/query/v1/query.openapi.yaml b/api/connect-openapi/gen/query/v1/query.openapi.yaml index 9aed27e7f3..f33ce803ba 100644 --- a/api/connect-openapi/gen/query/v1/query.openapi.yaml +++ b/api/connect-openapi/gen/query/v1/query.openapi.yaml @@ -737,12 +737,18 @@ components: title: timestamp format: int64 description: Milliseconds since epoch when the profile was captured. - id: + profileId: type: string examples: - 7c9e6679-7425-40de-944b-e07fc1f90ae7 - title: id + title: profile_id description: Unique identifier for the profile (UUID). + spanId: + type: string + examples: + - 00f067aa0ba902b7 + title: span_id + description: Span ID if this profile was split by span during ingestion. value: type: - integer diff --git a/api/connect-openapi/gen/storegateway/v1/storegateway.openapi.yaml b/api/connect-openapi/gen/storegateway/v1/storegateway.openapi.yaml index 28bea6ef01..ed59a8f9df 100644 --- a/api/connect-openapi/gen/storegateway/v1/storegateway.openapi.yaml +++ b/api/connect-openapi/gen/storegateway/v1/storegateway.openapi.yaml @@ -879,12 +879,18 @@ components: title: timestamp format: int64 description: Milliseconds since epoch when the profile was captured. - id: + profileId: type: string examples: - 7c9e6679-7425-40de-944b-e07fc1f90ae7 - title: id + title: profile_id description: Unique identifier for the profile (UUID). + spanId: + type: string + examples: + - 00f067aa0ba902b7 + title: span_id + description: Span ID if this profile was split by span during ingestion. value: type: - integer diff --git a/api/gen/proto/go/querier/v1/querier.pb.go b/api/gen/proto/go/querier/v1/querier.pb.go index 516f102f5c..0fde787ee8 100644 --- a/api/gen/proto/go/querier/v1/querier.pb.go +++ b/api/gen/proto/go/querier/v1/querier.pb.go @@ -313,8 +313,10 @@ type SelectMergeStacktracesRequest struct { Format ProfileFormat `protobuf:"varint,6,opt,name=format,proto3,enum=querier.v1.ProfileFormat" json:"format,omitempty"` // Select stack traces that match the provided selector. StackTraceSelector *v1.StackTraceSelector `protobuf:"bytes,7,opt,name=stack_trace_selector,json=stackTraceSelector,proto3,oneof" json:"stack_trace_selector,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // List of Profile UUIDs to query + ProfileIdSelector []string `protobuf:"bytes,8,rep,name=profile_id_selector,json=profileIdSelector,proto3" json:"profile_id_selector,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *SelectMergeStacktracesRequest) Reset() { @@ -396,6 +398,13 @@ func (x *SelectMergeStacktracesRequest) GetStackTraceSelector() *v1.StackTraceSe return nil } +func (x *SelectMergeStacktracesRequest) GetProfileIdSelector() []string { + if x != nil { + return x.ProfileIdSelector + } + return nil +} + type SelectMergeStacktracesResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Flamegraph *FlameGraph `protobuf:"bytes,1,opt,name=flamegraph,proto3" json:"flamegraph,omitempty"` @@ -1429,107 +1438,6 @@ func (x *QueryImpact) GetDeduplicationNeeded() bool { return false } -type GetProfileRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Profile UUID from Exemplar.id field (required) - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - // Profile timestamp in milliseconds since epoch. - // Used to narrow down which blocks to query. If not provided, all blocks will be scanned which is expensive. - // This timestamp is available from Exemplar.timestamp in the SelectSeries response. - Timestamp int64 `protobuf:"varint,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *GetProfileRequest) Reset() { - *x = GetProfileRequest{} - mi := &file_querier_v1_querier_proto_msgTypes[20] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *GetProfileRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetProfileRequest) ProtoMessage() {} - -func (x *GetProfileRequest) ProtoReflect() protoreflect.Message { - mi := &file_querier_v1_querier_proto_msgTypes[20] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetProfileRequest.ProtoReflect.Descriptor instead. -func (*GetProfileRequest) Descriptor() ([]byte, []int) { - return file_querier_v1_querier_proto_rawDescGZIP(), []int{20} -} - -func (x *GetProfileRequest) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -func (x *GetProfileRequest) GetTimestamp() int64 { - if x != nil { - return x.Timestamp - } - return 0 -} - -type GetProfileResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Complete pprof profile (merged if it was split during ingestion) - Profile *v11.Profile `protobuf:"bytes,1,opt,name=profile,proto3" json:"profile,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *GetProfileResponse) Reset() { - *x = GetProfileResponse{} - mi := &file_querier_v1_querier_proto_msgTypes[21] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *GetProfileResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetProfileResponse) ProtoMessage() {} - -func (x *GetProfileResponse) ProtoReflect() protoreflect.Message { - mi := &file_querier_v1_querier_proto_msgTypes[21] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetProfileResponse.ProtoReflect.Descriptor instead. -func (*GetProfileResponse) Descriptor() ([]byte, []int) { - return file_querier_v1_querier_proto_rawDescGZIP(), []int{21} -} - -func (x *GetProfileResponse) GetProfile() *v11.Profile { - if x != nil { - return x.Profile - } - return nil -} - var File_querier_v1_querier_proto protoreflect.FileDescriptor const file_querier_v1_querier_proto_rawDesc = "" + @@ -1549,7 +1457,7 @@ const file_querier_v1_querier_proto_rawDesc = "" + "\x03end\x18\x04 \x01(\x03B\x14\xbaG\x11:\x0f\x12\r1676289600000R\x03end\"A\n" + "\x0eSeriesResponse\x12/\n" + "\n" + - "labels_set\x18\x02 \x03(\v2\x10.types.v1.LabelsR\tlabelsSet\"\xeb\x03\n" + + "labels_set\x18\x02 \x03(\v2\x10.types.v1.LabelsR\tlabelsSet\"\xcc\x04\n" + "\x1dSelectMergeStacktracesRequest\x12Y\n" + "\x0eprofile_typeID\x18\x01 \x01(\tB2\xbaG/:-\x12+process_cpu:cpu:nanoseconds:cpu:nanosecondsR\rprofileTypeID\x12J\n" + "\x0elabel_selector\x18\x02 \x01(\tB#\xbaG :\x1e\x12\x1c'{namespace=\"my-namespace\"}'R\rlabelSelector\x12*\n" + @@ -1557,7 +1465,8 @@ const file_querier_v1_querier_proto_rawDesc = "" + "\x03end\x18\x04 \x01(\x03B\x14\xbaG\x11:\x0f\x12\r1676289600000R\x03end\x12 \n" + "\tmax_nodes\x18\x05 \x01(\x03H\x00R\bmaxNodes\x88\x01\x01\x121\n" + "\x06format\x18\x06 \x01(\x0e2\x19.querier.v1.ProfileFormatR\x06format\x12S\n" + - "\x14stack_trace_selector\x18\a \x01(\v2\x1c.types.v1.StackTraceSelectorH\x01R\x12stackTraceSelector\x88\x01\x01B\f\n" + + "\x14stack_trace_selector\x18\a \x01(\v2\x1c.types.v1.StackTraceSelectorH\x01R\x12stackTraceSelector\x88\x01\x01\x12_\n" + + "\x13profile_id_selector\x18\b \x03(\tB/\xbaG,:*\x12(['7c9e6679-7425-40de-944b-e07fc1f90ae7']R\x11profileIdSelectorB\f\n" + "\n" + "_max_nodesB\x17\n" + "\x15_stack_trace_selector\"l\n" + @@ -1653,16 +1562,11 @@ const file_querier_v1_querier_proto_rawDesc = "" + "\vQueryImpact\x128\n" + "\x19total_bytes_in_time_range\x18\x02 \x01(\x04R\x15totalBytesInTimeRange\x120\n" + "\x14total_queried_series\x18\x03 \x01(\x04R\x12totalQueriedSeries\x121\n" + - "\x14deduplication_needed\x18\x04 \x01(\bR\x13deduplicationNeeded\"\x84\x01\n" + - "\x11GetProfileRequest\x12;\n" + - "\x02id\x18\x01 \x01(\tB+\xbaG(:&\x12$7c9e6679-7425-40de-944b-e07fc1f90ae7R\x02id\x122\n" + - "\ttimestamp\x18\x02 \x01(\x03B\x14\xbaG\x11:\x0f\x12\r1730000023000R\ttimestamp\"B\n" + - "\x12GetProfileResponse\x12,\n" + - "\aprofile\x18\x01 \x01(\v2\x12.google.v1.ProfileR\aprofile*g\n" + + "\x14deduplication_needed\x18\x04 \x01(\bR\x13deduplicationNeeded*g\n" + "\rProfileFormat\x12\x1e\n" + "\x1aPROFILE_FORMAT_UNSPECIFIED\x10\x00\x12\x1d\n" + "\x19PROFILE_FORMAT_FLAMEGRAPH\x10\x01\x12\x17\n" + - "\x13PROFILE_FORMAT_TREE\x10\x022\xdc\t\n" + + "\x13PROFILE_FORMAT_TREE\x10\x022\xfc\b\n" + "\x0eQuerierService\x12d\n" + "\fProfileTypes\x12\x1f.querier.v1.ProfileTypesRequest\x1a .querier.v1.ProfileTypesResponse\"\x11\xbaG\x0e\n" + "\fscope/public\x12]\n" + @@ -1686,10 +1590,7 @@ const file_querier_v1_querier_proto_rawDesc = "" + "\x0fGetProfileStats\x12 .types.v1.GetProfileStatsRequest\x1a!.types.v1.GetProfileStatsResponse\"\x13\xbaG\x10\n" + "\x0escope/internal\x12f\n" + "\fAnalyzeQuery\x12\x1f.querier.v1.AnalyzeQueryRequest\x1a .querier.v1.AnalyzeQueryResponse\"\x13\xbaG\x10\n" + - "\x0escope/internal\x12^\n" + - "\n" + - "GetProfile\x12\x1d.querier.v1.GetProfileRequest\x1a\x1e.querier.v1.GetProfileResponse\"\x11\xbaG\x0e\n" + - "\fscope/publicB\xab\x01\n" + + "\x0escope/internalB\xab\x01\n" + "\x0ecom.querier.v1B\fQuerierProtoP\x01ZBgithub.com/grafana/pyroscope/api/gen/proto/go/querier/v1;querierv1\xa2\x02\x03QXX\xaa\x02\n" + "Querier.V1\xca\x02\n" + "Querier\\V1\xe2\x02\x16Querier\\V1\\GPBMetadata\xea\x02\vQuerier::V1b\x06proto3" @@ -1707,7 +1608,7 @@ func file_querier_v1_querier_proto_rawDescGZIP() []byte { } var file_querier_v1_querier_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_querier_v1_querier_proto_msgTypes = make([]protoimpl.MessageInfo, 22) +var file_querier_v1_querier_proto_msgTypes = make([]protoimpl.MessageInfo, 20) var file_querier_v1_querier_proto_goTypes = []any{ (ProfileFormat)(0), // 0: querier.v1.ProfileFormat (*ProfileTypesRequest)(nil), // 1: querier.v1.ProfileTypesRequest @@ -1730,26 +1631,24 @@ var file_querier_v1_querier_proto_goTypes = []any{ (*AnalyzeQueryResponse)(nil), // 18: querier.v1.AnalyzeQueryResponse (*QueryScope)(nil), // 19: querier.v1.QueryScope (*QueryImpact)(nil), // 20: querier.v1.QueryImpact - (*GetProfileRequest)(nil), // 21: querier.v1.GetProfileRequest - (*GetProfileResponse)(nil), // 22: querier.v1.GetProfileResponse - (*v1.ProfileType)(nil), // 23: types.v1.ProfileType - (*v1.Labels)(nil), // 24: types.v1.Labels - (*v1.StackTraceSelector)(nil), // 25: types.v1.StackTraceSelector - (v1.TimeSeriesAggregationType)(0), // 26: types.v1.TimeSeriesAggregationType - (*v1.Series)(nil), // 27: types.v1.Series - (*v11.Profile)(nil), // 28: google.v1.Profile - (*v1.LabelValuesRequest)(nil), // 29: types.v1.LabelValuesRequest - (*v1.LabelNamesRequest)(nil), // 30: types.v1.LabelNamesRequest - (*v1.GetProfileStatsRequest)(nil), // 31: types.v1.GetProfileStatsRequest - (*v1.LabelValuesResponse)(nil), // 32: types.v1.LabelValuesResponse - (*v1.LabelNamesResponse)(nil), // 33: types.v1.LabelNamesResponse - (*v1.GetProfileStatsResponse)(nil), // 34: types.v1.GetProfileStatsResponse + (*v1.ProfileType)(nil), // 21: types.v1.ProfileType + (*v1.Labels)(nil), // 22: types.v1.Labels + (*v1.StackTraceSelector)(nil), // 23: types.v1.StackTraceSelector + (v1.TimeSeriesAggregationType)(0), // 24: types.v1.TimeSeriesAggregationType + (*v1.Series)(nil), // 25: types.v1.Series + (*v1.LabelValuesRequest)(nil), // 26: types.v1.LabelValuesRequest + (*v1.LabelNamesRequest)(nil), // 27: types.v1.LabelNamesRequest + (*v1.GetProfileStatsRequest)(nil), // 28: types.v1.GetProfileStatsRequest + (*v1.LabelValuesResponse)(nil), // 29: types.v1.LabelValuesResponse + (*v1.LabelNamesResponse)(nil), // 30: types.v1.LabelNamesResponse + (*v11.Profile)(nil), // 31: google.v1.Profile + (*v1.GetProfileStatsResponse)(nil), // 32: types.v1.GetProfileStatsResponse } var file_querier_v1_querier_proto_depIdxs = []int32{ - 23, // 0: querier.v1.ProfileTypesResponse.profile_types:type_name -> types.v1.ProfileType - 24, // 1: querier.v1.SeriesResponse.labels_set:type_name -> types.v1.Labels + 21, // 0: querier.v1.ProfileTypesResponse.profile_types:type_name -> types.v1.ProfileType + 22, // 1: querier.v1.SeriesResponse.labels_set:type_name -> types.v1.Labels 0, // 2: querier.v1.SelectMergeStacktracesRequest.format:type_name -> querier.v1.ProfileFormat - 25, // 3: querier.v1.SelectMergeStacktracesRequest.stack_trace_selector:type_name -> types.v1.StackTraceSelector + 23, // 3: querier.v1.SelectMergeStacktracesRequest.stack_trace_selector:type_name -> types.v1.StackTraceSelector 11, // 4: querier.v1.SelectMergeStacktracesResponse.flamegraph:type_name -> querier.v1.FlameGraph 0, // 5: querier.v1.SelectMergeSpanProfileRequest.format:type_name -> querier.v1.ProfileFormat 11, // 6: querier.v1.SelectMergeSpanProfileResponse.flamegraph:type_name -> querier.v1.FlameGraph @@ -1758,42 +1657,39 @@ var file_querier_v1_querier_proto_depIdxs = []int32{ 12, // 9: querier.v1.DiffResponse.flamegraph:type_name -> querier.v1.FlameGraphDiff 13, // 10: querier.v1.FlameGraph.levels:type_name -> querier.v1.Level 13, // 11: querier.v1.FlameGraphDiff.levels:type_name -> querier.v1.Level - 25, // 12: querier.v1.SelectMergeProfileRequest.stack_trace_selector:type_name -> types.v1.StackTraceSelector - 26, // 13: querier.v1.SelectSeriesRequest.aggregation:type_name -> types.v1.TimeSeriesAggregationType - 25, // 14: querier.v1.SelectSeriesRequest.stack_trace_selector:type_name -> types.v1.StackTraceSelector - 27, // 15: querier.v1.SelectSeriesResponse.series:type_name -> types.v1.Series + 23, // 12: querier.v1.SelectMergeProfileRequest.stack_trace_selector:type_name -> types.v1.StackTraceSelector + 24, // 13: querier.v1.SelectSeriesRequest.aggregation:type_name -> types.v1.TimeSeriesAggregationType + 23, // 14: querier.v1.SelectSeriesRequest.stack_trace_selector:type_name -> types.v1.StackTraceSelector + 25, // 15: querier.v1.SelectSeriesResponse.series:type_name -> types.v1.Series 19, // 16: querier.v1.AnalyzeQueryResponse.query_scopes:type_name -> querier.v1.QueryScope 20, // 17: querier.v1.AnalyzeQueryResponse.query_impact:type_name -> querier.v1.QueryImpact - 28, // 18: querier.v1.GetProfileResponse.profile:type_name -> google.v1.Profile - 1, // 19: querier.v1.QuerierService.ProfileTypes:input_type -> querier.v1.ProfileTypesRequest - 29, // 20: querier.v1.QuerierService.LabelValues:input_type -> types.v1.LabelValuesRequest - 30, // 21: querier.v1.QuerierService.LabelNames:input_type -> types.v1.LabelNamesRequest - 3, // 22: querier.v1.QuerierService.Series:input_type -> querier.v1.SeriesRequest - 5, // 23: querier.v1.QuerierService.SelectMergeStacktraces:input_type -> querier.v1.SelectMergeStacktracesRequest - 7, // 24: querier.v1.QuerierService.SelectMergeSpanProfile:input_type -> querier.v1.SelectMergeSpanProfileRequest - 14, // 25: querier.v1.QuerierService.SelectMergeProfile:input_type -> querier.v1.SelectMergeProfileRequest - 15, // 26: querier.v1.QuerierService.SelectSeries:input_type -> querier.v1.SelectSeriesRequest - 9, // 27: querier.v1.QuerierService.Diff:input_type -> querier.v1.DiffRequest - 31, // 28: querier.v1.QuerierService.GetProfileStats:input_type -> types.v1.GetProfileStatsRequest - 17, // 29: querier.v1.QuerierService.AnalyzeQuery:input_type -> querier.v1.AnalyzeQueryRequest - 21, // 30: querier.v1.QuerierService.GetProfile:input_type -> querier.v1.GetProfileRequest - 2, // 31: querier.v1.QuerierService.ProfileTypes:output_type -> querier.v1.ProfileTypesResponse - 32, // 32: querier.v1.QuerierService.LabelValues:output_type -> types.v1.LabelValuesResponse - 33, // 33: querier.v1.QuerierService.LabelNames:output_type -> types.v1.LabelNamesResponse - 4, // 34: querier.v1.QuerierService.Series:output_type -> querier.v1.SeriesResponse - 6, // 35: querier.v1.QuerierService.SelectMergeStacktraces:output_type -> querier.v1.SelectMergeStacktracesResponse - 8, // 36: querier.v1.QuerierService.SelectMergeSpanProfile:output_type -> querier.v1.SelectMergeSpanProfileResponse - 28, // 37: querier.v1.QuerierService.SelectMergeProfile:output_type -> google.v1.Profile - 16, // 38: querier.v1.QuerierService.SelectSeries:output_type -> querier.v1.SelectSeriesResponse - 10, // 39: querier.v1.QuerierService.Diff:output_type -> querier.v1.DiffResponse - 34, // 40: querier.v1.QuerierService.GetProfileStats:output_type -> types.v1.GetProfileStatsResponse - 18, // 41: querier.v1.QuerierService.AnalyzeQuery:output_type -> querier.v1.AnalyzeQueryResponse - 22, // 42: querier.v1.QuerierService.GetProfile:output_type -> querier.v1.GetProfileResponse - 31, // [31:43] is the sub-list for method output_type - 19, // [19:31] is the sub-list for method input_type - 19, // [19:19] is the sub-list for extension type_name - 19, // [19:19] is the sub-list for extension extendee - 0, // [0:19] is the sub-list for field type_name + 1, // 18: querier.v1.QuerierService.ProfileTypes:input_type -> querier.v1.ProfileTypesRequest + 26, // 19: querier.v1.QuerierService.LabelValues:input_type -> types.v1.LabelValuesRequest + 27, // 20: querier.v1.QuerierService.LabelNames:input_type -> types.v1.LabelNamesRequest + 3, // 21: querier.v1.QuerierService.Series:input_type -> querier.v1.SeriesRequest + 5, // 22: querier.v1.QuerierService.SelectMergeStacktraces:input_type -> querier.v1.SelectMergeStacktracesRequest + 7, // 23: querier.v1.QuerierService.SelectMergeSpanProfile:input_type -> querier.v1.SelectMergeSpanProfileRequest + 14, // 24: querier.v1.QuerierService.SelectMergeProfile:input_type -> querier.v1.SelectMergeProfileRequest + 15, // 25: querier.v1.QuerierService.SelectSeries:input_type -> querier.v1.SelectSeriesRequest + 9, // 26: querier.v1.QuerierService.Diff:input_type -> querier.v1.DiffRequest + 28, // 27: querier.v1.QuerierService.GetProfileStats:input_type -> types.v1.GetProfileStatsRequest + 17, // 28: querier.v1.QuerierService.AnalyzeQuery:input_type -> querier.v1.AnalyzeQueryRequest + 2, // 29: querier.v1.QuerierService.ProfileTypes:output_type -> querier.v1.ProfileTypesResponse + 29, // 30: querier.v1.QuerierService.LabelValues:output_type -> types.v1.LabelValuesResponse + 30, // 31: querier.v1.QuerierService.LabelNames:output_type -> types.v1.LabelNamesResponse + 4, // 32: querier.v1.QuerierService.Series:output_type -> querier.v1.SeriesResponse + 6, // 33: querier.v1.QuerierService.SelectMergeStacktraces:output_type -> querier.v1.SelectMergeStacktracesResponse + 8, // 34: querier.v1.QuerierService.SelectMergeSpanProfile:output_type -> querier.v1.SelectMergeSpanProfileResponse + 31, // 35: querier.v1.QuerierService.SelectMergeProfile:output_type -> google.v1.Profile + 16, // 36: querier.v1.QuerierService.SelectSeries:output_type -> querier.v1.SelectSeriesResponse + 10, // 37: querier.v1.QuerierService.Diff:output_type -> querier.v1.DiffResponse + 32, // 38: querier.v1.QuerierService.GetProfileStats:output_type -> types.v1.GetProfileStatsResponse + 18, // 39: querier.v1.QuerierService.AnalyzeQuery:output_type -> querier.v1.AnalyzeQueryResponse + 29, // [29:40] is the sub-list for method output_type + 18, // [18:29] is the sub-list for method input_type + 18, // [18:18] is the sub-list for extension type_name + 18, // [18:18] is the sub-list for extension extendee + 0, // [0:18] is the sub-list for field type_name } func init() { file_querier_v1_querier_proto_init() } @@ -1811,7 +1707,7 @@ func file_querier_v1_querier_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_querier_v1_querier_proto_rawDesc), len(file_querier_v1_querier_proto_rawDesc)), NumEnums: 1, - NumMessages: 22, + NumMessages: 20, NumExtensions: 0, NumServices: 1, }, diff --git a/api/gen/proto/go/querier/v1/querier_vtproto.pb.go b/api/gen/proto/go/querier/v1/querier_vtproto.pb.go index a195675a14..0d465a583e 100644 --- a/api/gen/proto/go/querier/v1/querier_vtproto.pb.go +++ b/api/gen/proto/go/querier/v1/querier_vtproto.pb.go @@ -148,6 +148,11 @@ func (m *SelectMergeStacktracesRequest) CloneVT() *SelectMergeStacktracesRequest r.StackTraceSelector = proto.Clone(rhs).(*v1.StackTraceSelector) } } + if rhs := m.ProfileIdSelector; rhs != nil { + tmpContainer := make([]string, len(rhs)) + copy(tmpContainer, rhs) + r.ProfileIdSelector = tmpContainer + } if len(m.unknownFields) > 0 { r.unknownFields = make([]byte, len(m.unknownFields)) copy(r.unknownFields, m.unknownFields) @@ -537,47 +542,6 @@ func (m *QueryImpact) CloneMessageVT() proto.Message { return m.CloneVT() } -func (m *GetProfileRequest) CloneVT() *GetProfileRequest { - if m == nil { - return (*GetProfileRequest)(nil) - } - r := new(GetProfileRequest) - r.Id = m.Id - r.Timestamp = m.Timestamp - if len(m.unknownFields) > 0 { - r.unknownFields = make([]byte, len(m.unknownFields)) - copy(r.unknownFields, m.unknownFields) - } - return r -} - -func (m *GetProfileRequest) CloneMessageVT() proto.Message { - return m.CloneVT() -} - -func (m *GetProfileResponse) CloneVT() *GetProfileResponse { - if m == nil { - return (*GetProfileResponse)(nil) - } - r := new(GetProfileResponse) - if rhs := m.Profile; rhs != nil { - if vtpb, ok := interface{}(rhs).(interface{ CloneVT() *v11.Profile }); ok { - r.Profile = vtpb.CloneVT() - } else { - r.Profile = proto.Clone(rhs).(*v11.Profile) - } - } - if len(m.unknownFields) > 0 { - r.unknownFields = make([]byte, len(m.unknownFields)) - copy(r.unknownFields, m.unknownFields) - } - return r -} - -func (m *GetProfileResponse) CloneMessageVT() proto.Message { - return m.CloneVT() -} - func (this *ProfileTypesRequest) EqualVT(that *ProfileTypesRequest) bool { if this == that { return true @@ -747,6 +711,15 @@ func (this *SelectMergeStacktracesRequest) EqualVT(that *SelectMergeStacktracesR } else if !proto.Equal(this.StackTraceSelector, that.StackTraceSelector) { return false } + if len(this.ProfileIdSelector) != len(that.ProfileIdSelector) { + return false + } + for i, vx := range this.ProfileIdSelector { + vy := that.ProfileIdSelector[i] + if vx != vy { + return false + } + } return string(this.unknownFields) == string(that.unknownFields) } @@ -1273,51 +1246,6 @@ func (this *QueryImpact) EqualMessageVT(thatMsg proto.Message) bool { } return this.EqualVT(that) } -func (this *GetProfileRequest) EqualVT(that *GetProfileRequest) bool { - if this == that { - return true - } else if this == nil || that == nil { - return false - } - if this.Id != that.Id { - return false - } - if this.Timestamp != that.Timestamp { - return false - } - return string(this.unknownFields) == string(that.unknownFields) -} - -func (this *GetProfileRequest) EqualMessageVT(thatMsg proto.Message) bool { - that, ok := thatMsg.(*GetProfileRequest) - if !ok { - return false - } - return this.EqualVT(that) -} -func (this *GetProfileResponse) EqualVT(that *GetProfileResponse) bool { - if this == that { - return true - } else if this == nil || that == nil { - return false - } - if equal, ok := interface{}(this.Profile).(interface{ EqualVT(*v11.Profile) bool }); ok { - if !equal.EqualVT(that.Profile) { - return false - } - } else if !proto.Equal(this.Profile, that.Profile) { - return false - } - return string(this.unknownFields) == string(that.unknownFields) -} - -func (this *GetProfileResponse) EqualMessageVT(thatMsg proto.Message) bool { - that, ok := thatMsg.(*GetProfileResponse) - if !ok { - return false - } - return this.EqualVT(that) -} // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. @@ -1357,10 +1285,6 @@ type QuerierServiceClient interface { // GetProfileStats returns profile stats for the current tenant. GetProfileStats(ctx context.Context, in *v1.GetProfileStatsRequest, opts ...grpc.CallOption) (*v1.GetProfileStatsResponse, error) AnalyzeQuery(ctx context.Context, in *AnalyzeQueryRequest, opts ...grpc.CallOption) (*AnalyzeQueryResponse, error) - // GetProfile retrieves an individual profile by its UUID. - // If the profile was split during ingestion (e.g., by span_id), all parts - // will be merged before returning. - GetProfile(ctx context.Context, in *GetProfileRequest, opts ...grpc.CallOption) (*GetProfileResponse, error) } type querierServiceClient struct { @@ -1470,15 +1394,6 @@ func (c *querierServiceClient) AnalyzeQuery(ctx context.Context, in *AnalyzeQuer return out, nil } -func (c *querierServiceClient) GetProfile(ctx context.Context, in *GetProfileRequest, opts ...grpc.CallOption) (*GetProfileResponse, error) { - out := new(GetProfileResponse) - err := c.cc.Invoke(ctx, "/querier.v1.QuerierService/GetProfile", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - // QuerierServiceServer is the server API for QuerierService service. // All implementations must embed UnimplementedQuerierServiceServer // for forward compatibility @@ -1512,10 +1427,6 @@ type QuerierServiceServer interface { // GetProfileStats returns profile stats for the current tenant. GetProfileStats(context.Context, *v1.GetProfileStatsRequest) (*v1.GetProfileStatsResponse, error) AnalyzeQuery(context.Context, *AnalyzeQueryRequest) (*AnalyzeQueryResponse, error) - // GetProfile retrieves an individual profile by its UUID. - // If the profile was split during ingestion (e.g., by span_id), all parts - // will be merged before returning. - GetProfile(context.Context, *GetProfileRequest) (*GetProfileResponse, error) mustEmbedUnimplementedQuerierServiceServer() } @@ -1556,9 +1467,6 @@ func (UnimplementedQuerierServiceServer) GetProfileStats(context.Context, *v1.Ge func (UnimplementedQuerierServiceServer) AnalyzeQuery(context.Context, *AnalyzeQueryRequest) (*AnalyzeQueryResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method AnalyzeQuery not implemented") } -func (UnimplementedQuerierServiceServer) GetProfile(context.Context, *GetProfileRequest) (*GetProfileResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetProfile not implemented") -} func (UnimplementedQuerierServiceServer) mustEmbedUnimplementedQuerierServiceServer() {} // UnsafeQuerierServiceServer may be embedded to opt out of forward compatibility for this service. @@ -1770,24 +1678,6 @@ func _QuerierService_AnalyzeQuery_Handler(srv interface{}, ctx context.Context, return interceptor(ctx, in, info, handler) } -func _QuerierService_GetProfile_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(GetProfileRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(QuerierServiceServer).GetProfile(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/querier.v1.QuerierService/GetProfile", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(QuerierServiceServer).GetProfile(ctx, req.(*GetProfileRequest)) - } - return interceptor(ctx, in, info, handler) -} - // QuerierService_ServiceDesc is the grpc.ServiceDesc for QuerierService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -1839,10 +1729,6 @@ var QuerierService_ServiceDesc = grpc.ServiceDesc{ MethodName: "AnalyzeQuery", Handler: _QuerierService_AnalyzeQuery_Handler, }, - { - MethodName: "GetProfile", - Handler: _QuerierService_GetProfile_Handler, - }, }, Streams: []grpc.StreamDesc{}, Metadata: "querier/v1/querier.proto", @@ -2096,6 +1982,15 @@ func (m *SelectMergeStacktracesRequest) MarshalToSizedBufferVT(dAtA []byte) (int i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if len(m.ProfileIdSelector) > 0 { + for iNdEx := len(m.ProfileIdSelector) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.ProfileIdSelector[iNdEx]) + copy(dAtA[i:], m.ProfileIdSelector[iNdEx]) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.ProfileIdSelector[iNdEx]))) + i-- + dAtA[i] = 0x42 + } + } if m.StackTraceSelector != nil { if vtmsg, ok := interface{}(m.StackTraceSelector).(interface { MarshalToSizedBufferVT([]byte) (int, error) @@ -3102,106 +2997,6 @@ func (m *QueryImpact) MarshalToSizedBufferVT(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *GetProfileRequest) MarshalVT() (dAtA []byte, err error) { - if m == nil { - return nil, nil - } - size := m.SizeVT() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBufferVT(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *GetProfileRequest) MarshalToVT(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVT(dAtA[:size]) -} - -func (m *GetProfileRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { - if m == nil { - return 0, nil - } - i := len(dAtA) - _ = i - var l int - _ = l - if m.unknownFields != nil { - i -= len(m.unknownFields) - copy(dAtA[i:], m.unknownFields) - } - if m.Timestamp != 0 { - i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Timestamp)) - i-- - dAtA[i] = 0x10 - } - if len(m.Id) > 0 { - i -= len(m.Id) - copy(dAtA[i:], m.Id) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Id))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *GetProfileResponse) MarshalVT() (dAtA []byte, err error) { - if m == nil { - return nil, nil - } - size := m.SizeVT() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBufferVT(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *GetProfileResponse) MarshalToVT(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVT(dAtA[:size]) -} - -func (m *GetProfileResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) { - if m == nil { - return 0, nil - } - i := len(dAtA) - _ = i - var l int - _ = l - if m.unknownFields != nil { - i -= len(m.unknownFields) - copy(dAtA[i:], m.unknownFields) - } - if m.Profile != nil { - if vtmsg, ok := interface{}(m.Profile).(interface { - MarshalToSizedBufferVT([]byte) (int, error) - }); ok { - size, err := vtmsg.MarshalToSizedBufferVT(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - } else { - encoded, err := proto.Marshal(m.Profile) - if err != nil { - return 0, err - } - i -= len(encoded) - copy(dAtA[i:], encoded) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(encoded))) - } - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - func (m *ProfileTypesRequest) SizeVT() (n int) { if m == nil { return 0 @@ -3326,6 +3121,12 @@ func (m *SelectMergeStacktracesRequest) SizeVT() (n int) { } n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } + if len(m.ProfileIdSelector) > 0 { + for _, s := range m.ProfileIdSelector { + l = len(s) + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + } n += len(m.unknownFields) return n } @@ -3718,43 +3519,6 @@ func (m *QueryImpact) SizeVT() (n int) { return n } -func (m *GetProfileRequest) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Id) - if l > 0 { - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if m.Timestamp != 0 { - n += 1 + protohelpers.SizeOfVarint(uint64(m.Timestamp)) - } - n += len(m.unknownFields) - return n -} - -func (m *GetProfileResponse) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.Profile != nil { - if size, ok := interface{}(m.Profile).(interface { - SizeVT() int - }); ok { - l = size.SizeVT() - } else { - l = proto.Size(m.Profile) - } - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - n += len(m.unknownFields) - return n -} - func (m *ProfileTypesRequest) UnmarshalVT(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -4397,6 +4161,38 @@ func (m *SelectMergeStacktracesRequest) UnmarshalVT(dAtA []byte) error { } } iNdEx = postIndex + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProfileIdSelector", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ProfileIdSelector = append(m.ProfileIdSelector, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) @@ -6746,200 +6542,3 @@ func (m *QueryImpact) UnmarshalVT(dAtA []byte) error { } return nil } -func (m *GetProfileRequest) UnmarshalVT(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: GetProfileRequest: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: GetProfileRequest: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return protohelpers.ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Id = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType) - } - m.Timestamp = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.Timestamp |= int64(b&0x7F) << shift - if b < 0x80 { - break - } - } - default: - iNdEx = preIndex - skippy, err := protohelpers.Skip(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return protohelpers.ErrInvalidLength - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *GetProfileResponse) UnmarshalVT(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: GetProfileResponse: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: GetProfileResponse: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Profile", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return protohelpers.ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Profile == nil { - m.Profile = &v11.Profile{} - } - if unmarshal, ok := interface{}(m.Profile).(interface { - UnmarshalVT([]byte) error - }); ok { - if err := unmarshal.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err - } - } else { - if err := proto.Unmarshal(dAtA[iNdEx:postIndex], m.Profile); err != nil { - return err - } - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := protohelpers.Skip(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return protohelpers.ErrInvalidLength - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} diff --git a/api/gen/proto/go/querier/v1/querierv1connect/querier.connect.go b/api/gen/proto/go/querier/v1/querierv1connect/querier.connect.go index c350e78508..542c09fce1 100644 --- a/api/gen/proto/go/querier/v1/querierv1connect/querier.connect.go +++ b/api/gen/proto/go/querier/v1/querierv1connect/querier.connect.go @@ -68,9 +68,6 @@ const ( // QuerierServiceAnalyzeQueryProcedure is the fully-qualified name of the QuerierService's // AnalyzeQuery RPC. QuerierServiceAnalyzeQueryProcedure = "/querier.v1.QuerierService/AnalyzeQuery" - // QuerierServiceGetProfileProcedure is the fully-qualified name of the QuerierService's GetProfile - // RPC. - QuerierServiceGetProfileProcedure = "/querier.v1.QuerierService/GetProfile" ) // QuerierServiceClient is a client for the querier.v1.QuerierService service. @@ -104,10 +101,6 @@ type QuerierServiceClient interface { // GetProfileStats returns profile stats for the current tenant. GetProfileStats(context.Context, *connect.Request[v11.GetProfileStatsRequest]) (*connect.Response[v11.GetProfileStatsResponse], error) AnalyzeQuery(context.Context, *connect.Request[v1.AnalyzeQueryRequest]) (*connect.Response[v1.AnalyzeQueryResponse], error) - // GetProfile retrieves an individual profile by its UUID. - // If the profile was split during ingestion (e.g., by span_id), all parts - // will be merged before returning. - GetProfile(context.Context, *connect.Request[v1.GetProfileRequest]) (*connect.Response[v1.GetProfileResponse], error) } // NewQuerierServiceClient constructs a client for the querier.v1.QuerierService service. By @@ -187,12 +180,6 @@ func NewQuerierServiceClient(httpClient connect.HTTPClient, baseURL string, opts connect.WithSchema(querierServiceMethods.ByName("AnalyzeQuery")), connect.WithClientOptions(opts...), ), - getProfile: connect.NewClient[v1.GetProfileRequest, v1.GetProfileResponse]( - httpClient, - baseURL+QuerierServiceGetProfileProcedure, - connect.WithSchema(querierServiceMethods.ByName("GetProfile")), - connect.WithClientOptions(opts...), - ), } } @@ -209,7 +196,6 @@ type querierServiceClient struct { diff *connect.Client[v1.DiffRequest, v1.DiffResponse] getProfileStats *connect.Client[v11.GetProfileStatsRequest, v11.GetProfileStatsResponse] analyzeQuery *connect.Client[v1.AnalyzeQueryRequest, v1.AnalyzeQueryResponse] - getProfile *connect.Client[v1.GetProfileRequest, v1.GetProfileResponse] } // ProfileTypes calls querier.v1.QuerierService.ProfileTypes. @@ -267,11 +253,6 @@ func (c *querierServiceClient) AnalyzeQuery(ctx context.Context, req *connect.Re return c.analyzeQuery.CallUnary(ctx, req) } -// GetProfile calls querier.v1.QuerierService.GetProfile. -func (c *querierServiceClient) GetProfile(ctx context.Context, req *connect.Request[v1.GetProfileRequest]) (*connect.Response[v1.GetProfileResponse], error) { - return c.getProfile.CallUnary(ctx, req) -} - // QuerierServiceHandler is an implementation of the querier.v1.QuerierService service. type QuerierServiceHandler interface { // ProfileType returns a list of the existing profile types. @@ -303,10 +284,6 @@ type QuerierServiceHandler interface { // GetProfileStats returns profile stats for the current tenant. GetProfileStats(context.Context, *connect.Request[v11.GetProfileStatsRequest]) (*connect.Response[v11.GetProfileStatsResponse], error) AnalyzeQuery(context.Context, *connect.Request[v1.AnalyzeQueryRequest]) (*connect.Response[v1.AnalyzeQueryResponse], error) - // GetProfile retrieves an individual profile by its UUID. - // If the profile was split during ingestion (e.g., by span_id), all parts - // will be merged before returning. - GetProfile(context.Context, *connect.Request[v1.GetProfileRequest]) (*connect.Response[v1.GetProfileResponse], error) } // NewQuerierServiceHandler builds an HTTP handler from the service implementation. It returns the @@ -382,12 +359,6 @@ func NewQuerierServiceHandler(svc QuerierServiceHandler, opts ...connect.Handler connect.WithSchema(querierServiceMethods.ByName("AnalyzeQuery")), connect.WithHandlerOptions(opts...), ) - querierServiceGetProfileHandler := connect.NewUnaryHandler( - QuerierServiceGetProfileProcedure, - svc.GetProfile, - connect.WithSchema(querierServiceMethods.ByName("GetProfile")), - connect.WithHandlerOptions(opts...), - ) return "/querier.v1.QuerierService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case QuerierServiceProfileTypesProcedure: @@ -412,8 +383,6 @@ func NewQuerierServiceHandler(svc QuerierServiceHandler, opts ...connect.Handler querierServiceGetProfileStatsHandler.ServeHTTP(w, r) case QuerierServiceAnalyzeQueryProcedure: querierServiceAnalyzeQueryHandler.ServeHTTP(w, r) - case QuerierServiceGetProfileProcedure: - querierServiceGetProfileHandler.ServeHTTP(w, r) default: http.NotFound(w, r) } @@ -466,7 +435,3 @@ func (UnimplementedQuerierServiceHandler) GetProfileStats(context.Context, *conn func (UnimplementedQuerierServiceHandler) AnalyzeQuery(context.Context, *connect.Request[v1.AnalyzeQueryRequest]) (*connect.Response[v1.AnalyzeQueryResponse], error) { return nil, connect.NewError(connect.CodeUnimplemented, errors.New("querier.v1.QuerierService.AnalyzeQuery is not implemented")) } - -func (UnimplementedQuerierServiceHandler) GetProfile(context.Context, *connect.Request[v1.GetProfileRequest]) (*connect.Response[v1.GetProfileResponse], error) { - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("querier.v1.QuerierService.GetProfile is not implemented")) -} diff --git a/api/gen/proto/go/querier/v1/querierv1connect/querier.connect.mux.go b/api/gen/proto/go/querier/v1/querierv1connect/querier.connect.mux.go index 012667c304..9a82702031 100644 --- a/api/gen/proto/go/querier/v1/querierv1connect/querier.connect.mux.go +++ b/api/gen/proto/go/querier/v1/querierv1connect/querier.connect.mux.go @@ -74,9 +74,4 @@ func RegisterQuerierServiceHandler(mux *mux.Router, svc QuerierServiceHandler, o svc.AnalyzeQuery, opts..., )) - mux.Handle("/querier.v1.QuerierService/GetProfile", connect.NewUnaryHandler( - "/querier.v1.QuerierService/GetProfile", - svc.GetProfile, - opts..., - )) } diff --git a/api/gen/proto/go/types/v1/types.pb.go b/api/gen/proto/go/types/v1/types.pb.go index c69a7d1306..d8eb9e2f9f 100644 --- a/api/gen/proto/go/types/v1/types.pb.go +++ b/api/gen/proto/go/types/v1/types.pb.go @@ -1058,15 +1058,17 @@ type Exemplar struct { // Milliseconds since epoch when the profile was captured. Timestamp int64 `protobuf:"varint,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"` // Unique identifier for the profile (UUID). - Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` + ProfileId string `protobuf:"bytes,2,opt,name=profile_id,json=profileId,proto3" json:"profile_id,omitempty"` + // Span ID if this profile was split by span during ingestion. + SpanId string `protobuf:"bytes,3,opt,name=span_id,json=spanId,proto3" json:"span_id,omitempty"` // Total sample value for this profile (e.g., CPU nanoseconds, bytes allocated). - Value uint64 `protobuf:"varint,3,opt,name=value,proto3" json:"value,omitempty"` + Value uint64 `protobuf:"varint,4,opt,name=value,proto3" json:"value,omitempty"` // Series labels that are NOT included in the group_by query parameter. // These labels complete the full series identity of this exemplar's profile. // For example, if group_by=["service"], this would contain labels like "pod", // "namespace", "region" that were omitted from grouping. This allows identifying // which specific series instance this profile sample came from. - Labels []*LabelPair `protobuf:"bytes,4,rep,name=labels,proto3" json:"labels,omitempty"` + Labels []*LabelPair `protobuf:"bytes,5,rep,name=labels,proto3" json:"labels,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1108,9 +1110,16 @@ func (x *Exemplar) GetTimestamp() int64 { return 0 } -func (x *Exemplar) GetId() string { +func (x *Exemplar) GetProfileId() string { if x != nil { - return x.Id + return x.ProfileId + } + return "" +} + +func (x *Exemplar) GetSpanId() string { + if x != nil { + return x.SpanId } return "" } @@ -1199,13 +1208,15 @@ const file_types_v1_types_proto_rawDesc = "" + "\x17GetProfileStatsResponse\x12#\n" + "\rdata_ingested\x18\x01 \x01(\bR\fdataIngested\x12.\n" + "\x13oldest_profile_time\x18\x02 \x01(\x03R\x11oldestProfileTime\x12.\n" + - "\x13newest_profile_time\x18\x03 \x01(\x03R\x11newestProfileTime\"\xd1\x01\n" + + "\x13newest_profile_time\x18\x03 \x01(\x03R\x11newestProfileTime\"\x92\x02\n" + "\bExemplar\x122\n" + - "\ttimestamp\x18\x01 \x01(\x03B\x14\xbaG\x11:\x0f\x12\r1730000023000R\ttimestamp\x12;\n" + - "\x02id\x18\x02 \x01(\tB+\xbaG(:&\x12$7c9e6679-7425-40de-944b-e07fc1f90ae7R\x02id\x12'\n" + - "\x05value\x18\x03 \x01(\x04B\x11\xbaG\x0e:\f\x12\n" + + "\ttimestamp\x18\x01 \x01(\x03B\x14\xbaG\x11:\x0f\x12\r1730000023000R\ttimestamp\x12J\n" + + "\n" + + "profile_id\x18\x02 \x01(\tB+\xbaG(:&\x12$7c9e6679-7425-40de-944b-e07fc1f90ae7R\tprofileId\x120\n" + + "\aspan_id\x18\x03 \x01(\tB\x17\xbaG\x14:\x12\x12\x1000f067aa0ba902b7R\x06spanId\x12'\n" + + "\x05value\x18\x04 \x01(\x04B\x11\xbaG\x0e:\f\x12\n" + "2450000000R\x05value\x12+\n" + - "\x06labels\x18\x04 \x03(\v2\x13.types.v1.LabelPairR\x06labels*k\n" + + "\x06labels\x18\x05 \x03(\v2\x13.types.v1.LabelPairR\x06labels*k\n" + "\x19TimeSeriesAggregationType\x12$\n" + " TIME_SERIES_AGGREGATION_TYPE_SUM\x10\x00\x12(\n" + "$TIME_SERIES_AGGREGATION_TYPE_AVERAGE\x10\x01B\x9b\x01\n" + diff --git a/api/gen/proto/go/types/v1/types_vtproto.pb.go b/api/gen/proto/go/types/v1/types_vtproto.pb.go index b18e6d598a..c84f97c44d 100644 --- a/api/gen/proto/go/types/v1/types_vtproto.pb.go +++ b/api/gen/proto/go/types/v1/types_vtproto.pb.go @@ -407,7 +407,8 @@ func (m *Exemplar) CloneVT() *Exemplar { } r := new(Exemplar) r.Timestamp = m.Timestamp - r.Id = m.Id + r.ProfileId = m.ProfileId + r.SpanId = m.SpanId r.Value = m.Value if rhs := m.Labels; rhs != nil { tmpContainer := make([]*LabelPair, len(rhs)) @@ -968,7 +969,10 @@ func (this *Exemplar) EqualVT(that *Exemplar) bool { if this.Timestamp != that.Timestamp { return false } - if this.Id != that.Id { + if this.ProfileId != that.ProfileId { + return false + } + if this.SpanId != that.SpanId { return false } if this.Value != that.Value { @@ -1931,18 +1935,25 @@ func (m *Exemplar) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= size i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) i-- - dAtA[i] = 0x22 + dAtA[i] = 0x2a } } if m.Value != 0 { i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Value)) i-- - dAtA[i] = 0x18 + dAtA[i] = 0x20 } - if len(m.Id) > 0 { - i -= len(m.Id) - copy(dAtA[i:], m.Id) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Id))) + if len(m.SpanId) > 0 { + i -= len(m.SpanId) + copy(dAtA[i:], m.SpanId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.SpanId))) + i-- + dAtA[i] = 0x1a + } + if len(m.ProfileId) > 0 { + i -= len(m.ProfileId) + copy(dAtA[i:], m.ProfileId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.ProfileId))) i-- dAtA[i] = 0x12 } @@ -2313,7 +2324,11 @@ func (m *Exemplar) SizeVT() (n int) { if m.Timestamp != 0 { n += 1 + protohelpers.SizeOfVarint(uint64(m.Timestamp)) } - l = len(m.Id) + l = len(m.ProfileId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.SpanId) if l > 0 { n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } @@ -4425,7 +4440,7 @@ func (m *Exemplar) UnmarshalVT(dAtA []byte) error { } case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ProfileId", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -4453,9 +4468,41 @@ func (m *Exemplar) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Id = string(dAtA[iNdEx:postIndex]) + m.ProfileId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SpanId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SpanId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) } @@ -4474,7 +4521,7 @@ func (m *Exemplar) UnmarshalVT(dAtA []byte) error { break } } - case 4: + case 5: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Labels", wireType) } diff --git a/api/openapiv2/gen/phlare.swagger.json b/api/openapiv2/gen/phlare.swagger.json index 2aea172c53..6bc839495c 100644 --- a/api/openapiv2/gen/phlare.swagger.json +++ b/api/openapiv2/gen/phlare.swagger.json @@ -969,10 +969,14 @@ "format": "int64", "description": "Milliseconds since epoch when the profile was captured." }, - "id": { + "profileId": { "type": "string", "description": "Unique identifier for the profile (UUID)." }, + "spanId": { + "type": "string", + "description": "Span ID if this profile was split by span during ingestion." + }, "value": { "type": "string", "format": "uint64", @@ -1225,15 +1229,6 @@ } } }, - "v1GetProfileResponse": { - "type": "object", - "properties": { - "profile": { - "$ref": "#/definitions/googlev1Profile", - "title": "Complete pprof profile (merged if it was split during ingestion)" - } - } - }, "v1GetProfileStatsResponse": { "type": "object", "properties": { @@ -2176,6 +2171,13 @@ "stackTraceSelector": { "$ref": "#/definitions/v1StackTraceSelector", "description": "Select stack traces that match the provided selector." + }, + "profileIdSelector": { + "type": "array", + "items": { + "type": "string" + }, + "title": "List of Profile UUIDs to query" } } }, diff --git a/api/querier/v1/querier.proto b/api/querier/v1/querier.proto index 27deee9757..f43ccd8f19 100644 --- a/api/querier/v1/querier.proto +++ b/api/querier/v1/querier.proto @@ -62,13 +62,6 @@ service QuerierService { rpc AnalyzeQuery(AnalyzeQueryRequest) returns (AnalyzeQueryResponse) { option (gnostic.openapi.v3.operation).tags = "scope/internal"; } - - // GetProfile retrieves an individual profile by its UUID. - // If the profile was split during ingestion (e.g., by span_id), all parts - // will be merged before returning. - rpc GetProfile(GetProfileRequest) returns (GetProfileResponse) { - option (gnostic.openapi.v3.operation).tags = "scope/public"; - } } message ProfileTypesRequest { @@ -119,6 +112,8 @@ message SelectMergeStacktracesRequest { ProfileFormat format = 6; // Select stack traces that match the provided selector. optional types.v1.StackTraceSelector stack_trace_selector = 7; + // List of Profile UUIDs to query + repeated string profile_id_selector = 8 [(gnostic.openapi.v3.property).example = {yaml: "['7c9e6679-7425-40de-944b-e07fc1f90ae7']"}]; } enum ProfileFormat { @@ -261,18 +256,3 @@ message QueryImpact { uint64 total_queried_series = 3; bool deduplication_needed = 4; } - -message GetProfileRequest { - // Profile UUID from Exemplar.id field (required) - string id = 1 [(gnostic.openapi.v3.property).example = {yaml: "7c9e6679-7425-40de-944b-e07fc1f90ae7"}]; - - // Profile timestamp in milliseconds since epoch. - // Used to narrow down which blocks to query. If not provided, all blocks will be scanned which is expensive. - // This timestamp is available from Exemplar.timestamp in the SelectSeries response. - int64 timestamp = 2 [(gnostic.openapi.v3.property).example = {yaml: "1730000023000"}]; -} - -message GetProfileResponse { - // Complete pprof profile (merged if it was split during ingestion) - google.v1.Profile profile = 1; -} diff --git a/api/types/v1/types.proto b/api/types/v1/types.proto index 06165a153f..f688dd8d7b 100644 --- a/api/types/v1/types.proto +++ b/api/types/v1/types.proto @@ -144,15 +144,18 @@ message Exemplar { int64 timestamp = 1 [(gnostic.openapi.v3.property).example = {yaml: "1730000023000"}]; // Unique identifier for the profile (UUID). - string id = 2 [(gnostic.openapi.v3.property).example = {yaml: "7c9e6679-7425-40de-944b-e07fc1f90ae7"}]; + string profile_id = 2 [(gnostic.openapi.v3.property).example = {yaml: "7c9e6679-7425-40de-944b-e07fc1f90ae7"}]; + + // Span ID if this profile was split by span during ingestion. + string span_id = 3 [(gnostic.openapi.v3.property).example = {yaml: "00f067aa0ba902b7"}]; // Total sample value for this profile (e.g., CPU nanoseconds, bytes allocated). - uint64 value = 3 [(gnostic.openapi.v3.property).example = {yaml: "2450000000"}]; + uint64 value = 4 [(gnostic.openapi.v3.property).example = {yaml: "2450000000"}]; // Series labels that are NOT included in the group_by query parameter. // These labels complete the full series identity of this exemplar's profile. // For example, if group_by=["service"], this would contain labels like "pod", // "namespace", "region" that were omitted from grouping. This allows identifying // which specific series instance this profile sample came from. - repeated LabelPair labels = 4; + repeated LabelPair labels = 5; } diff --git a/docs/sources/reference-server-api/index.md b/docs/sources/reference-server-api/index.md index f2b91dcfe3..d5f36604d4 100644 --- a/docs/sources/reference-server-api/index.md +++ b/docs/sources/reference-server-api/index.md @@ -126,6 +126,7 @@ A request body with the following fields is required: |`left.format` | | | |`left.labelSelector` | Label selector string | `{namespace="my-namespace"}` | |`left.maxNodes` | Limit the nodes returned to only show the node with the max_node's biggest total | | +|`left.profileIdSelector` | List of Profile UUIDs to query | `["7c9e6679-7425-40de-944b-e07fc1f90ae7"]` | |`left.profileTypeID` | Profile Type ID string in the form ::::. | `process_cpu:cpu:nanoseconds:cpu:nanoseconds` | |`left.stackTraceSelector.callSite[].name` | | | |`left.stackTraceSelector.goPgo.aggregateCallees` | Aggregate callees causes the leaf location line number to be ignored, thus aggregating all callee samples (but not callers). | | @@ -135,6 +136,7 @@ A request body with the following fields is required: |`right.format` | | | |`right.labelSelector` | Label selector string | `{namespace="my-namespace"}` | |`right.maxNodes` | Limit the nodes returned to only show the node with the max_node's biggest total | | +|`right.profileIdSelector` | List of Profile UUIDs to query | `["7c9e6679-7425-40de-944b-e07fc1f90ae7"]` | |`right.profileTypeID` | Profile Type ID string in the form ::::. | `process_cpu:cpu:nanoseconds:cpu:nanoseconds` | |`right.stackTraceSelector.callSite[].name` | | | |`right.stackTraceSelector.goPgo.aggregateCallees` | Aggregate callees causes the leaf location line number to be ignored, thus aggregating all callee samples (but not callers). | | @@ -148,12 +150,18 @@ curl \ "left": { "end": '$(date +%s)000', "labelSelector": "{namespace=\"my-namespace\"}", + "profileIdSelector": [ + "7c9e6679-7425-40de-944b-e07fc1f90ae7" + ], "profileTypeID": "process_cpu:cpu:nanoseconds:cpu:nanoseconds", "start": '$(expr $(date +%s) - 3600 )000' }, "right": { "end": '$(date +%s)000', "labelSelector": "{namespace=\"my-namespace\"}", + "profileIdSelector": [ + "7c9e6679-7425-40de-944b-e07fc1f90ae7" + ], "profileTypeID": "process_cpu:cpu:nanoseconds:cpu:nanoseconds", "start": '$(expr $(date +%s) - 3600 )000' } @@ -168,12 +176,18 @@ body = { "left": { "end": int(datetime.datetime.now().timestamp() * 1000), "labelSelector": "{namespace=\"my-namespace\"}", + "profileIdSelector": [ + "7c9e6679-7425-40de-944b-e07fc1f90ae7" + ], "profileTypeID": "process_cpu:cpu:nanoseconds:cpu:nanoseconds", "start": int((datetime.datetime.now()- datetime.timedelta(hours = 1)).timestamp() * 1000) }, "right": { "end": int(datetime.datetime.now().timestamp() * 1000), "labelSelector": "{namespace=\"my-namespace\"}", + "profileIdSelector": [ + "7c9e6679-7425-40de-944b-e07fc1f90ae7" + ], "profileTypeID": "process_cpu:cpu:nanoseconds:cpu:nanoseconds", "start": int((datetime.datetime.now()- datetime.timedelta(hours = 1)).timestamp() * 1000) } @@ -184,43 +198,6 @@ print(resp) print(resp.content) ``` -{{% /code %}} -#### `/querier.v1.QuerierService/GetProfile` - -GetProfile retrieves an individual profile by its UUID. - If the profile was split during ingestion (e.g., by span_id), all parts - will be merged before returning. - -A request body with the following fields is required: - -|Field | Description | Example | -|:-----|:------------|:--------| -|`id` | Profile UUID from Exemplar.id field (required) | `7c9e6679-7425-40de-944b-e07fc1f90ae7` | -|`timestamp` | Profile timestamp in milliseconds since epoch. Used to narrow down which blocks to query. If not provided, all blocks will be scanned which is expensive. This timestamp is available from Exemplar.timestamp in the SelectSeries response. | `1730000023000` | - -{{% code %}} -```curl -curl \ - -H "Content-Type: application/json" \ - -d '{ - "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", - "timestamp": "1730000023000" - }' \ - http://localhost:4040/querier.v1.QuerierService/GetProfile -``` - -```python -import requests -body = { - "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", - "timestamp": "1730000023000" - } -url = 'http://localhost:4040/querier.v1.QuerierService/GetProfile' -resp = requests.post(url, json=body) -print(resp) -print(resp.content) -``` - {{% /code %}} #### `/querier.v1.QuerierService/LabelNames` @@ -453,6 +430,7 @@ A request body with the following fields is required: |`format` | | | |`labelSelector` | Label selector string | `{namespace="my-namespace"}` | |`maxNodes` | Limit the nodes returned to only show the node with the max_node's biggest total | | +|`profileIdSelector` | List of Profile UUIDs to query | `["7c9e6679-7425-40de-944b-e07fc1f90ae7"]` | |`profileTypeID` | Profile Type ID string in the form ::::. | `process_cpu:cpu:nanoseconds:cpu:nanoseconds` | |`stackTraceSelector.callSite[].name` | | | |`stackTraceSelector.goPgo.aggregateCallees` | Aggregate callees causes the leaf location line number to be ignored, thus aggregating all callee samples (but not callers). | | @@ -465,6 +443,9 @@ curl \ -d '{ "end": '$(date +%s)000', "labelSelector": "{namespace=\"my-namespace\"}", + "profileIdSelector": [ + "7c9e6679-7425-40de-944b-e07fc1f90ae7" + ], "profileTypeID": "process_cpu:cpu:nanoseconds:cpu:nanoseconds", "start": '$(expr $(date +%s) - 3600 )000' }' \ @@ -477,6 +458,9 @@ import datetime body = { "end": int(datetime.datetime.now().timestamp() * 1000), "labelSelector": "{namespace=\"my-namespace\"}", + "profileIdSelector": [ + "7c9e6679-7425-40de-944b-e07fc1f90ae7" + ], "profileTypeID": "process_cpu:cpu:nanoseconds:cpu:nanoseconds", "start": int((datetime.datetime.now()- datetime.timedelta(hours = 1)).timestamp() * 1000) } diff --git a/pkg/frontend/frontend_get_profile.go b/pkg/frontend/frontend_get_profile.go deleted file mode 100644 index 89fd8b2814..0000000000 --- a/pkg/frontend/frontend_get_profile.go +++ /dev/null @@ -1,18 +0,0 @@ -package frontend - -import ( - "context" - - "connectrpc.com/connect" - - querierv1 "github.com/grafana/pyroscope/api/gen/proto/go/querier/v1" -) - -// GetProfile is a stub for v1 frontend compatibility. -// This feature only implemented in v2 architecture. -func (f *Frontend) GetProfile( - ctx context.Context, - req *connect.Request[querierv1.GetProfileRequest], -) (*connect.Response[querierv1.GetProfileResponse], error) { - return nil, connect.NewError(connect.CodeUnimplemented, nil) -} diff --git a/pkg/frontend/readpath/query_service_handler.go b/pkg/frontend/readpath/query_service_handler.go index c549d9caa0..2a771e1afb 100644 --- a/pkg/frontend/readpath/query_service_handler.go +++ b/pkg/frontend/readpath/query_service_handler.go @@ -248,11 +248,3 @@ func (r *Router) ProfileTypes( return &querierv1.ProfileTypesResponse{ProfileTypes: pTypes}, nil }) } - -func (r *Router) GetProfile( - ctx context.Context, - c *connect.Request[querierv1.GetProfileRequest], -) (*connect.Response[querierv1.GetProfileResponse], error) { - // GetProfile is a v2-only feature. - return r.newFrontend.GetProfile(ctx, c) -} diff --git a/pkg/frontend/readpath/queryfrontend/query_frontend_get_profile.go b/pkg/frontend/readpath/queryfrontend/query_frontend_get_profile.go deleted file mode 100644 index d8b920babf..0000000000 --- a/pkg/frontend/readpath/queryfrontend/query_frontend_get_profile.go +++ /dev/null @@ -1,21 +0,0 @@ -package queryfrontend - -import ( - "context" - - "connectrpc.com/connect" - - querierv1 "github.com/grafana/pyroscope/api/gen/proto/go/querier/v1" -) - -func (q *QueryFrontend) GetProfile( - ctx context.Context, - req *connect.Request[querierv1.GetProfileRequest], -) (*connect.Response[querierv1.GetProfileResponse], error) { - // TODO: Implement profile retrieval by UUID for v2 architecture - // This should: - // 1. Query blocks via metastore for rows matching the UUID - // 2. Merge split profiles if necessary (using pprof.ProfileMerge) - // 3. Return the complete profile - return nil, connect.NewError(connect.CodeUnimplemented, nil) -} diff --git a/pkg/querier/querier_get_profile.go b/pkg/querier/querier_get_profile.go deleted file mode 100644 index 46bca400bd..0000000000 --- a/pkg/querier/querier_get_profile.go +++ /dev/null @@ -1,18 +0,0 @@ -package querier - -import ( - "context" - - "connectrpc.com/connect" - - querierv1 "github.com/grafana/pyroscope/api/gen/proto/go/querier/v1" -) - -// GetProfile is a stub for v1 architecture compatibility. -// This feature is only implemented in v2 architecture. -func (q *Querier) GetProfile( - ctx context.Context, - req *connect.Request[querierv1.GetProfileRequest], -) (*connect.Response[querierv1.GetProfileResponse], error) { - return nil, connect.NewError(connect.CodeUnimplemented, nil) -} diff --git a/pkg/test/mocks/mockquerierv1connect/mock_querier_service_client.go b/pkg/test/mocks/mockquerierv1connect/mock_querier_service_client.go index 365c2c69b9..4e0a442c69 100644 --- a/pkg/test/mocks/mockquerierv1connect/mock_querier_service_client.go +++ b/pkg/test/mocks/mockquerierv1connect/mock_querier_service_client.go @@ -147,65 +147,6 @@ func (_c *MockQuerierServiceClient_Diff_Call) RunAndReturn(run func(context.Cont return _c } -// GetProfile provides a mock function with given fields: _a0, _a1 -func (_m *MockQuerierServiceClient) GetProfile(_a0 context.Context, _a1 *connect.Request[querierv1.GetProfileRequest]) (*connect.Response[querierv1.GetProfileResponse], error) { - ret := _m.Called(_a0, _a1) - - if len(ret) == 0 { - panic("no return value specified for GetProfile") - } - - var r0 *connect.Response[querierv1.GetProfileResponse] - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *connect.Request[querierv1.GetProfileRequest]) (*connect.Response[querierv1.GetProfileResponse], error)); ok { - return rf(_a0, _a1) - } - if rf, ok := ret.Get(0).(func(context.Context, *connect.Request[querierv1.GetProfileRequest]) *connect.Response[querierv1.GetProfileResponse]); ok { - r0 = rf(_a0, _a1) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*connect.Response[querierv1.GetProfileResponse]) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, *connect.Request[querierv1.GetProfileRequest]) error); ok { - r1 = rf(_a0, _a1) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// MockQuerierServiceClient_GetProfile_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetProfile' -type MockQuerierServiceClient_GetProfile_Call struct { - *mock.Call -} - -// GetProfile is a helper method to define mock.On call -// - _a0 context.Context -// - _a1 *connect.Request[querierv1.GetProfileRequest] -func (_e *MockQuerierServiceClient_Expecter) GetProfile(_a0 interface{}, _a1 interface{}) *MockQuerierServiceClient_GetProfile_Call { - return &MockQuerierServiceClient_GetProfile_Call{Call: _e.mock.On("GetProfile", _a0, _a1)} -} - -func (_c *MockQuerierServiceClient_GetProfile_Call) Run(run func(_a0 context.Context, _a1 *connect.Request[querierv1.GetProfileRequest])) *MockQuerierServiceClient_GetProfile_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*connect.Request[querierv1.GetProfileRequest])) - }) - return _c -} - -func (_c *MockQuerierServiceClient_GetProfile_Call) Return(_a0 *connect.Response[querierv1.GetProfileResponse], _a1 error) *MockQuerierServiceClient_GetProfile_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *MockQuerierServiceClient_GetProfile_Call) RunAndReturn(run func(context.Context, *connect.Request[querierv1.GetProfileRequest]) (*connect.Response[querierv1.GetProfileResponse], error)) *MockQuerierServiceClient_GetProfile_Call { - _c.Call.Return(run) - return _c -} - // GetProfileStats provides a mock function with given fields: _a0, _a1 func (_m *MockQuerierServiceClient) GetProfileStats(_a0 context.Context, _a1 *connect.Request[typesv1.GetProfileStatsRequest]) (*connect.Response[typesv1.GetProfileStatsResponse], error) { ret := _m.Called(_a0, _a1) diff --git a/pkg/util/spanlogger/query_log.go b/pkg/util/spanlogger/query_log.go index e83fede251..2847f21168 100644 --- a/pkg/util/spanlogger/query_log.go +++ b/pkg/util/spanlogger/query_log.go @@ -211,19 +211,6 @@ func (l LogSpanParametersWrapper) AnalyzeQuery(ctx context.Context, c *connect.R return l.client.AnalyzeQuery(ctx, c) } -func (l LogSpanParametersWrapper) GetProfile(ctx context.Context, c *connect.Request[querierv1.GetProfileRequest]) (*connect.Response[querierv1.GetProfileResponse], error) { - spanName := "GetProfile" - sp, ctx := opentracing.StartSpanFromContext(ctx, spanName) - level.Info(FromContext(ctx, l.logger)).Log( - "method", spanName, - "id", c.Msg.Id, - "timestamp", model.Time(c.Msg.Timestamp).Time().String(), - ) - defer sp.Finish() - - return l.client.GetProfile(ctx, c) -} - type LazyJoin struct { strs []string sep string From 52cbf75dc41401d3fee844c8fe51a0db7c071960 Mon Sep 17 00:00:00 2001 From: Marc Sanmiquel Date: Fri, 14 Nov 2025 10:18:27 +0100 Subject: [PATCH 4/6] feat: add include_exemplars flag to timeseries APIs --- .../gen/querier/v1/querier.openapi.yaml | 4 ++ .../gen/query/v1/query.openapi.yaml | 3 ++ api/gen/proto/go/querier/v1/querier.pb.go | 23 ++++++++--- .../proto/go/querier/v1/querier_vtproto.pb.go | 41 +++++++++++++++++++ api/gen/proto/go/query/v1/query.pb.go | 25 +++++++---- api/gen/proto/go/query/v1/query_vtproto.pb.go | 37 +++++++++++++++++ api/openapiv2/gen/phlare.swagger.json | 3 ++ api/querier/v1/querier.proto | 1 + api/query/v1/query.proto | 1 + docs/sources/reference-server-api/index.md | 1 + 10 files changed, 125 insertions(+), 14 deletions(-) diff --git a/api/connect-openapi/gen/querier/v1/querier.openapi.yaml b/api/connect-openapi/gen/querier/v1/querier.openapi.yaml index 86bf6b66ed..38c8bbfb04 100644 --- a/api/connect-openapi/gen/querier/v1/querier.openapi.yaml +++ b/api/connect-openapi/gen/querier/v1/querier.openapi.yaml @@ -1436,6 +1436,10 @@ components: format: int64 description: Select the top N series by total value. nullable: true + includeExemplars: + type: boolean + title: include_exemplars + nullable: true title: SelectSeriesRequest additionalProperties: false querier.v1.SelectSeriesResponse: diff --git a/api/connect-openapi/gen/query/v1/query.openapi.yaml b/api/connect-openapi/gen/query/v1/query.openapi.yaml index f33ce803ba..c8fb8fa89e 100644 --- a/api/connect-openapi/gen/query/v1/query.openapi.yaml +++ b/api/connect-openapi/gen/query/v1/query.openapi.yaml @@ -678,6 +678,9 @@ components: - string title: limit format: int64 + includeExemplars: + type: boolean + title: include_exemplars title: TimeSeriesQuery additionalProperties: false query.v1.TimeSeriesReport: diff --git a/api/gen/proto/go/querier/v1/querier.pb.go b/api/gen/proto/go/querier/v1/querier.pb.go index 0fde787ee8..16d2148755 100644 --- a/api/gen/proto/go/querier/v1/querier.pb.go +++ b/api/gen/proto/go/querier/v1/querier.pb.go @@ -1015,9 +1015,10 @@ type SelectSeriesRequest struct { // Select stack traces that match the provided selector. StackTraceSelector *v1.StackTraceSelector `protobuf:"bytes,8,opt,name=stack_trace_selector,json=stackTraceSelector,proto3,oneof" json:"stack_trace_selector,omitempty"` // Select the top N series by total value. - Limit *int64 `protobuf:"varint,9,opt,name=limit,proto3,oneof" json:"limit,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Limit *int64 `protobuf:"varint,9,opt,name=limit,proto3,oneof" json:"limit,omitempty"` + IncludeExemplars *bool `protobuf:"varint,10,opt,name=include_exemplars,json=includeExemplars,proto3,oneof" json:"include_exemplars,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *SelectSeriesRequest) Reset() { @@ -1113,6 +1114,13 @@ func (x *SelectSeriesRequest) GetLimit() int64 { return 0 } +func (x *SelectSeriesRequest) GetIncludeExemplars() bool { + if x != nil && x.IncludeExemplars != nil { + return *x.IncludeExemplars + } + return false +} + type SelectSeriesResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Series []*v1.Series `protobuf:"bytes,1,rep,name=series,proto3" json:"series,omitempty"` @@ -1523,7 +1531,7 @@ const file_querier_v1_querier_proto_rawDesc = "" + "\x14stack_trace_selector\x18\x06 \x01(\v2\x1c.types.v1.StackTraceSelectorH\x01R\x12stackTraceSelector\x88\x01\x01B\f\n" + "\n" + "_max_nodesB\x17\n" + - "\x15_stack_trace_selector\"\xbe\x04\n" + + "\x15_stack_trace_selector\"\x86\x05\n" + "\x13SelectSeriesRequest\x12Y\n" + "\x0eprofile_typeID\x18\x01 \x01(\tB2\xbaG/:-\x12+process_cpu:cpu:nanoseconds:cpu:nanosecondsR\rprofileTypeID\x12J\n" + "\x0elabel_selector\x18\x02 \x01(\tB#\xbaG :\x1e\x12\x1c'{namespace=\"my-namespace\"}'R\rlabelSelector\x12*\n" + @@ -1533,10 +1541,13 @@ const file_querier_v1_querier_proto_rawDesc = "" + "\x04step\x18\x06 \x01(\x01R\x04step\x12J\n" + "\vaggregation\x18\a \x01(\x0e2#.types.v1.TimeSeriesAggregationTypeH\x00R\vaggregation\x88\x01\x01\x12S\n" + "\x14stack_trace_selector\x18\b \x01(\v2\x1c.types.v1.StackTraceSelectorH\x01R\x12stackTraceSelector\x88\x01\x01\x12\x19\n" + - "\x05limit\x18\t \x01(\x03H\x02R\x05limit\x88\x01\x01B\x0e\n" + + "\x05limit\x18\t \x01(\x03H\x02R\x05limit\x88\x01\x01\x120\n" + + "\x11include_exemplars\x18\n" + + " \x01(\bH\x03R\x10includeExemplars\x88\x01\x01B\x0e\n" + "\f_aggregationB\x17\n" + "\x15_stack_trace_selectorB\b\n" + - "\x06_limit\"@\n" + + "\x06_limitB\x14\n" + + "\x12_include_exemplars\"@\n" + "\x14SelectSeriesResponse\x12(\n" + "\x06series\x18\x01 \x03(\v2\x10.types.v1.SeriesR\x06series\"S\n" + "\x13AnalyzeQueryRequest\x12\x14\n" + diff --git a/api/gen/proto/go/querier/v1/querier_vtproto.pb.go b/api/gen/proto/go/querier/v1/querier_vtproto.pb.go index 0d465a583e..f6f6863ee3 100644 --- a/api/gen/proto/go/querier/v1/querier_vtproto.pb.go +++ b/api/gen/proto/go/querier/v1/querier_vtproto.pb.go @@ -417,6 +417,10 @@ func (m *SelectSeriesRequest) CloneVT() *SelectSeriesRequest { tmpVal := *rhs r.Limit = &tmpVal } + if rhs := m.IncludeExemplars; rhs != nil { + tmpVal := *rhs + r.IncludeExemplars = &tmpVal + } if len(m.unknownFields) > 0 { r.unknownFields = make([]byte, len(m.unknownFields)) copy(r.unknownFields, m.unknownFields) @@ -1070,6 +1074,9 @@ func (this *SelectSeriesRequest) EqualVT(that *SelectSeriesRequest) bool { if p, q := this.Limit, that.Limit; (p == nil && q != nil) || (p != nil && (q == nil || *p != *q)) { return false } + if p, q := this.IncludeExemplars, that.IncludeExemplars; (p == nil && q != nil) || (p != nil && (q == nil || *p != *q)) { + return false + } return string(this.unknownFields) == string(that.unknownFields) } @@ -2628,6 +2635,16 @@ func (m *SelectSeriesRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if m.IncludeExemplars != nil { + i-- + if *m.IncludeExemplars { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x50 + } if m.Limit != nil { i = protohelpers.EncodeVarint(dAtA, i, uint64(*m.Limit)) i-- @@ -3396,6 +3413,9 @@ func (m *SelectSeriesRequest) SizeVT() (n int) { if m.Limit != nil { n += 1 + protohelpers.SizeOfVarint(uint64(*m.Limit)) } + if m.IncludeExemplars != nil { + n += 2 + } n += len(m.unknownFields) return n } @@ -5841,6 +5861,27 @@ func (m *SelectSeriesRequest) UnmarshalVT(dAtA []byte) error { } } m.Limit = &v + case 10: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field IncludeExemplars", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + b := bool(v != 0) + m.IncludeExemplars = &b default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) diff --git a/api/gen/proto/go/query/v1/query.pb.go b/api/gen/proto/go/query/v1/query.pb.go index d0d08d352a..7380ec4c00 100644 --- a/api/gen/proto/go/query/v1/query.pb.go +++ b/api/gen/proto/go/query/v1/query.pb.go @@ -1121,12 +1121,13 @@ func (x *SeriesLabelsReport) GetSeriesLabels() []*v11.Labels { } type TimeSeriesQuery struct { - state protoimpl.MessageState `protogen:"open.v1"` - Step float64 `protobuf:"fixed64,1,opt,name=step,proto3" json:"step,omitempty"` - GroupBy []string `protobuf:"bytes,2,rep,name=group_by,json=groupBy,proto3" json:"group_by,omitempty"` - Limit int64 `protobuf:"varint,3,opt,name=limit,proto3" json:"limit,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Step float64 `protobuf:"fixed64,1,opt,name=step,proto3" json:"step,omitempty"` + GroupBy []string `protobuf:"bytes,2,rep,name=group_by,json=groupBy,proto3" json:"group_by,omitempty"` + Limit int64 `protobuf:"varint,3,opt,name=limit,proto3" json:"limit,omitempty"` + IncludeExemplars bool `protobuf:"varint,4,opt,name=include_exemplars,json=includeExemplars,proto3" json:"include_exemplars,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *TimeSeriesQuery) Reset() { @@ -1180,6 +1181,13 @@ func (x *TimeSeriesQuery) GetLimit() int64 { return 0 } +func (x *TimeSeriesQuery) GetIncludeExemplars() bool { + if x != nil { + return x.IncludeExemplars + } + return false +} + type TimeSeriesReport struct { state protoimpl.MessageState `protogen:"open.v1"` Query *TimeSeriesQuery `protobuf:"bytes,1,opt,name=query,proto3" json:"query,omitempty"` @@ -1527,11 +1535,12 @@ const file_query_v1_query_proto_rawDesc = "" + "labelNames\"~\n" + "\x12SeriesLabelsReport\x121\n" + "\x05query\x18\x01 \x01(\v2\x1b.query.v1.SeriesLabelsQueryR\x05query\x125\n" + - "\rseries_labels\x18\x02 \x03(\v2\x10.types.v1.LabelsR\fseriesLabels\"V\n" + + "\rseries_labels\x18\x02 \x03(\v2\x10.types.v1.LabelsR\fseriesLabels\"\x83\x01\n" + "\x0fTimeSeriesQuery\x12\x12\n" + "\x04step\x18\x01 \x01(\x01R\x04step\x12\x19\n" + "\bgroup_by\x18\x02 \x03(\tR\agroupBy\x12\x14\n" + - "\x05limit\x18\x03 \x01(\x03R\x05limit\"v\n" + + "\x05limit\x18\x03 \x01(\x03R\x05limit\x12+\n" + + "\x11include_exemplars\x18\x04 \x01(\bR\x10includeExemplars\"v\n" + "\x10TimeSeriesReport\x12/\n" + "\x05query\x18\x01 \x01(\v2\x19.query.v1.TimeSeriesQueryR\x05query\x121\n" + "\vtime_series\x18\x02 \x03(\v2\x10.types.v1.SeriesR\n" + diff --git a/api/gen/proto/go/query/v1/query_vtproto.pb.go b/api/gen/proto/go/query/v1/query_vtproto.pb.go index 08be94451f..fa168b1afe 100644 --- a/api/gen/proto/go/query/v1/query_vtproto.pb.go +++ b/api/gen/proto/go/query/v1/query_vtproto.pb.go @@ -398,6 +398,7 @@ func (m *TimeSeriesQuery) CloneVT() *TimeSeriesQuery { r := new(TimeSeriesQuery) r.Step = m.Step r.Limit = m.Limit + r.IncludeExemplars = m.IncludeExemplars if rhs := m.GroupBy; rhs != nil { tmpContainer := make([]string, len(rhs)) copy(tmpContainer, rhs) @@ -1076,6 +1077,9 @@ func (this *TimeSeriesQuery) EqualVT(that *TimeSeriesQuery) bool { if this.Limit != that.Limit { return false } + if this.IncludeExemplars != that.IncludeExemplars { + return false + } return string(this.unknownFields) == string(that.unknownFields) } @@ -2381,6 +2385,16 @@ func (m *TimeSeriesQuery) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if m.IncludeExemplars { + i-- + if m.IncludeExemplars { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x20 + } if m.Limit != 0 { i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Limit)) i-- @@ -3072,6 +3086,9 @@ func (m *TimeSeriesQuery) SizeVT() (n int) { if m.Limit != 0 { n += 1 + protohelpers.SizeOfVarint(uint64(m.Limit)) } + if m.IncludeExemplars { + n += 2 + } n += len(m.unknownFields) return n } @@ -5448,6 +5465,26 @@ func (m *TimeSeriesQuery) UnmarshalVT(dAtA []byte) error { break } } + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field IncludeExemplars", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.IncludeExemplars = bool(v != 0) default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) diff --git a/api/openapiv2/gen/phlare.swagger.json b/api/openapiv2/gen/phlare.swagger.json index 6bc839495c..ee2ea67c7d 100644 --- a/api/openapiv2/gen/phlare.swagger.json +++ b/api/openapiv2/gen/phlare.swagger.json @@ -2460,6 +2460,9 @@ "limit": { "type": "string", "format": "int64" + }, + "includeExemplars": { + "type": "boolean" } } }, diff --git a/api/querier/v1/querier.proto b/api/querier/v1/querier.proto index f43ccd8f19..856c70fd2c 100644 --- a/api/querier/v1/querier.proto +++ b/api/querier/v1/querier.proto @@ -219,6 +219,7 @@ message SelectSeriesRequest { optional types.v1.StackTraceSelector stack_trace_selector = 8; // Select the top N series by total value. optional int64 limit = 9; + optional bool include_exemplars = 10; } message SelectSeriesResponse { diff --git a/api/query/v1/query.proto b/api/query/v1/query.proto index 70e00e1d8b..f303a46b41 100644 --- a/api/query/v1/query.proto +++ b/api/query/v1/query.proto @@ -161,6 +161,7 @@ message TimeSeriesQuery { double step = 1; repeated string group_by = 2; int64 limit = 3; + bool include_exemplars = 4; } message TimeSeriesReport { diff --git a/docs/sources/reference-server-api/index.md b/docs/sources/reference-server-api/index.md index d5f36604d4..5b3e4ac579 100644 --- a/docs/sources/reference-server-api/index.md +++ b/docs/sources/reference-server-api/index.md @@ -484,6 +484,7 @@ A request body with the following fields is required: |`end` | Milliseconds since epoch. | `1676289600000` | |`aggregation` | | | |`groupBy` | | `["pod"]` | +|`includeExemplars` | | | |`labelSelector` | Label selector string | `{namespace="my-namespace"}` | |`limit` | Select the top N series by total value. | | |`profileTypeID` | Profile Type ID string in the form ::::. | `process_cpu:cpu:nanoseconds:cpu:nanoseconds` | From f133b642fe36dfcdbf17d8b491a368a5c1a968d5 Mon Sep 17 00:00:00 2001 From: Marc Sanmiquel Date: Fri, 14 Nov 2025 16:13:45 +0100 Subject: [PATCH 5/6] Add ExemplarType enum --- .../gen/querier/v1/querier.openapi.yaml | 15 ++- .../gen/query/v1/query.openapi.yaml | 14 +- api/gen/proto/go/querier/v1/querier.pb.go | 107 ++++++++-------- .../proto/go/querier/v1/querier_vtproto.pb.go | 28 ++-- api/gen/proto/go/query/v1/query.pb.go | 62 ++++----- api/gen/proto/go/query/v1/query_vtproto.pb.go | 24 ++-- api/gen/proto/go/types/v1/types.pb.go | 120 +++++++++++++----- api/openapiv2/gen/phlare.swagger.json | 14 +- api/querier/v1/querier.proto | 3 +- api/query/v1/query.proto | 2 +- api/types/v1/types.proto | 7 + docs/sources/reference-server-api/index.md | 2 +- 12 files changed, 242 insertions(+), 156 deletions(-) diff --git a/api/connect-openapi/gen/querier/v1/querier.openapi.yaml b/api/connect-openapi/gen/querier/v1/querier.openapi.yaml index 38c8bbfb04..39ced81124 100644 --- a/api/connect-openapi/gen/querier/v1/querier.openapi.yaml +++ b/api/connect-openapi/gen/querier/v1/querier.openapi.yaml @@ -1436,10 +1436,11 @@ components: format: int64 description: Select the top N series by total value. nullable: true - includeExemplars: - type: boolean - title: include_exemplars + exemplarType: + title: exemplar_type + description: Type of exemplars to include in the response. nullable: true + $ref: '#/components/schemas/types.v1.ExemplarType' title: SelectSeriesRequest additionalProperties: false querier.v1.SelectSeriesResponse: @@ -1555,6 +1556,14 @@ components: Exemplar represents metadata for an individual profile sample. Exemplars allow users to drill down from aggregated timeline views to specific profile instances. + types.v1.ExemplarType: + type: string + title: ExemplarType + enum: + - EXEMPLAR_TYPE_UNSPECIFIED + - EXEMPLAR_TYPE_NONE + - EXEMPLAR_TYPE_INDIVIDUAL + - EXEMPLAR_TYPE_SPAN types.v1.GetProfileStatsRequest: type: object title: GetProfileStatsRequest diff --git a/api/connect-openapi/gen/query/v1/query.openapi.yaml b/api/connect-openapi/gen/query/v1/query.openapi.yaml index c8fb8fa89e..7cfffaaec0 100644 --- a/api/connect-openapi/gen/query/v1/query.openapi.yaml +++ b/api/connect-openapi/gen/query/v1/query.openapi.yaml @@ -678,9 +678,9 @@ components: - string title: limit format: int64 - includeExemplars: - type: boolean - title: include_exemplars + exemplarType: + title: exemplar_type + $ref: '#/components/schemas/types.v1.ExemplarType' title: TimeSeriesQuery additionalProperties: false query.v1.TimeSeriesReport: @@ -778,6 +778,14 @@ components: Exemplar represents metadata for an individual profile sample. Exemplars allow users to drill down from aggregated timeline views to specific profile instances. + types.v1.ExemplarType: + type: string + title: ExemplarType + enum: + - EXEMPLAR_TYPE_UNSPECIFIED + - EXEMPLAR_TYPE_NONE + - EXEMPLAR_TYPE_INDIVIDUAL + - EXEMPLAR_TYPE_SPAN types.v1.GoPGO: type: object properties: diff --git a/api/gen/proto/go/querier/v1/querier.pb.go b/api/gen/proto/go/querier/v1/querier.pb.go index 16d2148755..ef4b36e38e 100644 --- a/api/gen/proto/go/querier/v1/querier.pb.go +++ b/api/gen/proto/go/querier/v1/querier.pb.go @@ -1015,10 +1015,11 @@ type SelectSeriesRequest struct { // Select stack traces that match the provided selector. StackTraceSelector *v1.StackTraceSelector `protobuf:"bytes,8,opt,name=stack_trace_selector,json=stackTraceSelector,proto3,oneof" json:"stack_trace_selector,omitempty"` // Select the top N series by total value. - Limit *int64 `protobuf:"varint,9,opt,name=limit,proto3,oneof" json:"limit,omitempty"` - IncludeExemplars *bool `protobuf:"varint,10,opt,name=include_exemplars,json=includeExemplars,proto3,oneof" json:"include_exemplars,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Limit *int64 `protobuf:"varint,9,opt,name=limit,proto3,oneof" json:"limit,omitempty"` + // Type of exemplars to include in the response. + ExemplarType *v1.ExemplarType `protobuf:"varint,10,opt,name=exemplar_type,json=exemplarType,proto3,enum=types.v1.ExemplarType,oneof" json:"exemplar_type,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *SelectSeriesRequest) Reset() { @@ -1114,11 +1115,11 @@ func (x *SelectSeriesRequest) GetLimit() int64 { return 0 } -func (x *SelectSeriesRequest) GetIncludeExemplars() bool { - if x != nil && x.IncludeExemplars != nil { - return *x.IncludeExemplars +func (x *SelectSeriesRequest) GetExemplarType() v1.ExemplarType { + if x != nil && x.ExemplarType != nil { + return *x.ExemplarType } - return false + return v1.ExemplarType(0) } type SelectSeriesResponse struct { @@ -1531,7 +1532,7 @@ const file_querier_v1_querier_proto_rawDesc = "" + "\x14stack_trace_selector\x18\x06 \x01(\v2\x1c.types.v1.StackTraceSelectorH\x01R\x12stackTraceSelector\x88\x01\x01B\f\n" + "\n" + "_max_nodesB\x17\n" + - "\x15_stack_trace_selector\"\x86\x05\n" + + "\x15_stack_trace_selector\"\x92\x05\n" + "\x13SelectSeriesRequest\x12Y\n" + "\x0eprofile_typeID\x18\x01 \x01(\tB2\xbaG/:-\x12+process_cpu:cpu:nanoseconds:cpu:nanosecondsR\rprofileTypeID\x12J\n" + "\x0elabel_selector\x18\x02 \x01(\tB#\xbaG :\x1e\x12\x1c'{namespace=\"my-namespace\"}'R\rlabelSelector\x12*\n" + @@ -1541,13 +1542,13 @@ const file_querier_v1_querier_proto_rawDesc = "" + "\x04step\x18\x06 \x01(\x01R\x04step\x12J\n" + "\vaggregation\x18\a \x01(\x0e2#.types.v1.TimeSeriesAggregationTypeH\x00R\vaggregation\x88\x01\x01\x12S\n" + "\x14stack_trace_selector\x18\b \x01(\v2\x1c.types.v1.StackTraceSelectorH\x01R\x12stackTraceSelector\x88\x01\x01\x12\x19\n" + - "\x05limit\x18\t \x01(\x03H\x02R\x05limit\x88\x01\x01\x120\n" + - "\x11include_exemplars\x18\n" + - " \x01(\bH\x03R\x10includeExemplars\x88\x01\x01B\x0e\n" + + "\x05limit\x18\t \x01(\x03H\x02R\x05limit\x88\x01\x01\x12@\n" + + "\rexemplar_type\x18\n" + + " \x01(\x0e2\x16.types.v1.ExemplarTypeH\x03R\fexemplarType\x88\x01\x01B\x0e\n" + "\f_aggregationB\x17\n" + "\x15_stack_trace_selectorB\b\n" + - "\x06_limitB\x14\n" + - "\x12_include_exemplars\"@\n" + + "\x06_limitB\x10\n" + + "\x0e_exemplar_type\"@\n" + "\x14SelectSeriesResponse\x12(\n" + "\x06series\x18\x01 \x03(\v2\x10.types.v1.SeriesR\x06series\"S\n" + "\x13AnalyzeQueryRequest\x12\x14\n" + @@ -1646,14 +1647,15 @@ var file_querier_v1_querier_proto_goTypes = []any{ (*v1.Labels)(nil), // 22: types.v1.Labels (*v1.StackTraceSelector)(nil), // 23: types.v1.StackTraceSelector (v1.TimeSeriesAggregationType)(0), // 24: types.v1.TimeSeriesAggregationType - (*v1.Series)(nil), // 25: types.v1.Series - (*v1.LabelValuesRequest)(nil), // 26: types.v1.LabelValuesRequest - (*v1.LabelNamesRequest)(nil), // 27: types.v1.LabelNamesRequest - (*v1.GetProfileStatsRequest)(nil), // 28: types.v1.GetProfileStatsRequest - (*v1.LabelValuesResponse)(nil), // 29: types.v1.LabelValuesResponse - (*v1.LabelNamesResponse)(nil), // 30: types.v1.LabelNamesResponse - (*v11.Profile)(nil), // 31: google.v1.Profile - (*v1.GetProfileStatsResponse)(nil), // 32: types.v1.GetProfileStatsResponse + (v1.ExemplarType)(0), // 25: types.v1.ExemplarType + (*v1.Series)(nil), // 26: types.v1.Series + (*v1.LabelValuesRequest)(nil), // 27: types.v1.LabelValuesRequest + (*v1.LabelNamesRequest)(nil), // 28: types.v1.LabelNamesRequest + (*v1.GetProfileStatsRequest)(nil), // 29: types.v1.GetProfileStatsRequest + (*v1.LabelValuesResponse)(nil), // 30: types.v1.LabelValuesResponse + (*v1.LabelNamesResponse)(nil), // 31: types.v1.LabelNamesResponse + (*v11.Profile)(nil), // 32: google.v1.Profile + (*v1.GetProfileStatsResponse)(nil), // 33: types.v1.GetProfileStatsResponse } var file_querier_v1_querier_proto_depIdxs = []int32{ 21, // 0: querier.v1.ProfileTypesResponse.profile_types:type_name -> types.v1.ProfileType @@ -1671,36 +1673,37 @@ var file_querier_v1_querier_proto_depIdxs = []int32{ 23, // 12: querier.v1.SelectMergeProfileRequest.stack_trace_selector:type_name -> types.v1.StackTraceSelector 24, // 13: querier.v1.SelectSeriesRequest.aggregation:type_name -> types.v1.TimeSeriesAggregationType 23, // 14: querier.v1.SelectSeriesRequest.stack_trace_selector:type_name -> types.v1.StackTraceSelector - 25, // 15: querier.v1.SelectSeriesResponse.series:type_name -> types.v1.Series - 19, // 16: querier.v1.AnalyzeQueryResponse.query_scopes:type_name -> querier.v1.QueryScope - 20, // 17: querier.v1.AnalyzeQueryResponse.query_impact:type_name -> querier.v1.QueryImpact - 1, // 18: querier.v1.QuerierService.ProfileTypes:input_type -> querier.v1.ProfileTypesRequest - 26, // 19: querier.v1.QuerierService.LabelValues:input_type -> types.v1.LabelValuesRequest - 27, // 20: querier.v1.QuerierService.LabelNames:input_type -> types.v1.LabelNamesRequest - 3, // 21: querier.v1.QuerierService.Series:input_type -> querier.v1.SeriesRequest - 5, // 22: querier.v1.QuerierService.SelectMergeStacktraces:input_type -> querier.v1.SelectMergeStacktracesRequest - 7, // 23: querier.v1.QuerierService.SelectMergeSpanProfile:input_type -> querier.v1.SelectMergeSpanProfileRequest - 14, // 24: querier.v1.QuerierService.SelectMergeProfile:input_type -> querier.v1.SelectMergeProfileRequest - 15, // 25: querier.v1.QuerierService.SelectSeries:input_type -> querier.v1.SelectSeriesRequest - 9, // 26: querier.v1.QuerierService.Diff:input_type -> querier.v1.DiffRequest - 28, // 27: querier.v1.QuerierService.GetProfileStats:input_type -> types.v1.GetProfileStatsRequest - 17, // 28: querier.v1.QuerierService.AnalyzeQuery:input_type -> querier.v1.AnalyzeQueryRequest - 2, // 29: querier.v1.QuerierService.ProfileTypes:output_type -> querier.v1.ProfileTypesResponse - 29, // 30: querier.v1.QuerierService.LabelValues:output_type -> types.v1.LabelValuesResponse - 30, // 31: querier.v1.QuerierService.LabelNames:output_type -> types.v1.LabelNamesResponse - 4, // 32: querier.v1.QuerierService.Series:output_type -> querier.v1.SeriesResponse - 6, // 33: querier.v1.QuerierService.SelectMergeStacktraces:output_type -> querier.v1.SelectMergeStacktracesResponse - 8, // 34: querier.v1.QuerierService.SelectMergeSpanProfile:output_type -> querier.v1.SelectMergeSpanProfileResponse - 31, // 35: querier.v1.QuerierService.SelectMergeProfile:output_type -> google.v1.Profile - 16, // 36: querier.v1.QuerierService.SelectSeries:output_type -> querier.v1.SelectSeriesResponse - 10, // 37: querier.v1.QuerierService.Diff:output_type -> querier.v1.DiffResponse - 32, // 38: querier.v1.QuerierService.GetProfileStats:output_type -> types.v1.GetProfileStatsResponse - 18, // 39: querier.v1.QuerierService.AnalyzeQuery:output_type -> querier.v1.AnalyzeQueryResponse - 29, // [29:40] is the sub-list for method output_type - 18, // [18:29] is the sub-list for method input_type - 18, // [18:18] is the sub-list for extension type_name - 18, // [18:18] is the sub-list for extension extendee - 0, // [0:18] is the sub-list for field type_name + 25, // 15: querier.v1.SelectSeriesRequest.exemplar_type:type_name -> types.v1.ExemplarType + 26, // 16: querier.v1.SelectSeriesResponse.series:type_name -> types.v1.Series + 19, // 17: querier.v1.AnalyzeQueryResponse.query_scopes:type_name -> querier.v1.QueryScope + 20, // 18: querier.v1.AnalyzeQueryResponse.query_impact:type_name -> querier.v1.QueryImpact + 1, // 19: querier.v1.QuerierService.ProfileTypes:input_type -> querier.v1.ProfileTypesRequest + 27, // 20: querier.v1.QuerierService.LabelValues:input_type -> types.v1.LabelValuesRequest + 28, // 21: querier.v1.QuerierService.LabelNames:input_type -> types.v1.LabelNamesRequest + 3, // 22: querier.v1.QuerierService.Series:input_type -> querier.v1.SeriesRequest + 5, // 23: querier.v1.QuerierService.SelectMergeStacktraces:input_type -> querier.v1.SelectMergeStacktracesRequest + 7, // 24: querier.v1.QuerierService.SelectMergeSpanProfile:input_type -> querier.v1.SelectMergeSpanProfileRequest + 14, // 25: querier.v1.QuerierService.SelectMergeProfile:input_type -> querier.v1.SelectMergeProfileRequest + 15, // 26: querier.v1.QuerierService.SelectSeries:input_type -> querier.v1.SelectSeriesRequest + 9, // 27: querier.v1.QuerierService.Diff:input_type -> querier.v1.DiffRequest + 29, // 28: querier.v1.QuerierService.GetProfileStats:input_type -> types.v1.GetProfileStatsRequest + 17, // 29: querier.v1.QuerierService.AnalyzeQuery:input_type -> querier.v1.AnalyzeQueryRequest + 2, // 30: querier.v1.QuerierService.ProfileTypes:output_type -> querier.v1.ProfileTypesResponse + 30, // 31: querier.v1.QuerierService.LabelValues:output_type -> types.v1.LabelValuesResponse + 31, // 32: querier.v1.QuerierService.LabelNames:output_type -> types.v1.LabelNamesResponse + 4, // 33: querier.v1.QuerierService.Series:output_type -> querier.v1.SeriesResponse + 6, // 34: querier.v1.QuerierService.SelectMergeStacktraces:output_type -> querier.v1.SelectMergeStacktracesResponse + 8, // 35: querier.v1.QuerierService.SelectMergeSpanProfile:output_type -> querier.v1.SelectMergeSpanProfileResponse + 32, // 36: querier.v1.QuerierService.SelectMergeProfile:output_type -> google.v1.Profile + 16, // 37: querier.v1.QuerierService.SelectSeries:output_type -> querier.v1.SelectSeriesResponse + 10, // 38: querier.v1.QuerierService.Diff:output_type -> querier.v1.DiffResponse + 33, // 39: querier.v1.QuerierService.GetProfileStats:output_type -> types.v1.GetProfileStatsResponse + 18, // 40: querier.v1.QuerierService.AnalyzeQuery:output_type -> querier.v1.AnalyzeQueryResponse + 30, // [30:41] is the sub-list for method output_type + 19, // [19:30] is the sub-list for method input_type + 19, // [19:19] is the sub-list for extension type_name + 19, // [19:19] is the sub-list for extension extendee + 0, // [0:19] is the sub-list for field type_name } func init() { file_querier_v1_querier_proto_init() } diff --git a/api/gen/proto/go/querier/v1/querier_vtproto.pb.go b/api/gen/proto/go/querier/v1/querier_vtproto.pb.go index f6f6863ee3..9dc5cd7782 100644 --- a/api/gen/proto/go/querier/v1/querier_vtproto.pb.go +++ b/api/gen/proto/go/querier/v1/querier_vtproto.pb.go @@ -417,9 +417,9 @@ func (m *SelectSeriesRequest) CloneVT() *SelectSeriesRequest { tmpVal := *rhs r.Limit = &tmpVal } - if rhs := m.IncludeExemplars; rhs != nil { + if rhs := m.ExemplarType; rhs != nil { tmpVal := *rhs - r.IncludeExemplars = &tmpVal + r.ExemplarType = &tmpVal } if len(m.unknownFields) > 0 { r.unknownFields = make([]byte, len(m.unknownFields)) @@ -1074,7 +1074,7 @@ func (this *SelectSeriesRequest) EqualVT(that *SelectSeriesRequest) bool { if p, q := this.Limit, that.Limit; (p == nil && q != nil) || (p != nil && (q == nil || *p != *q)) { return false } - if p, q := this.IncludeExemplars, that.IncludeExemplars; (p == nil && q != nil) || (p != nil && (q == nil || *p != *q)) { + if p, q := this.ExemplarType, that.ExemplarType; (p == nil && q != nil) || (p != nil && (q == nil || *p != *q)) { return false } return string(this.unknownFields) == string(that.unknownFields) @@ -2635,13 +2635,8 @@ func (m *SelectSeriesRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } - if m.IncludeExemplars != nil { - i-- - if *m.IncludeExemplars { - dAtA[i] = 1 - } else { - dAtA[i] = 0 - } + if m.ExemplarType != nil { + i = protohelpers.EncodeVarint(dAtA, i, uint64(*m.ExemplarType)) i-- dAtA[i] = 0x50 } @@ -3413,8 +3408,8 @@ func (m *SelectSeriesRequest) SizeVT() (n int) { if m.Limit != nil { n += 1 + protohelpers.SizeOfVarint(uint64(*m.Limit)) } - if m.IncludeExemplars != nil { - n += 2 + if m.ExemplarType != nil { + n += 1 + protohelpers.SizeOfVarint(uint64(*m.ExemplarType)) } n += len(m.unknownFields) return n @@ -5863,9 +5858,9 @@ func (m *SelectSeriesRequest) UnmarshalVT(dAtA []byte) error { m.Limit = &v case 10: if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field IncludeExemplars", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ExemplarType", wireType) } - var v int + var v v1.ExemplarType for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -5875,13 +5870,12 @@ func (m *SelectSeriesRequest) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - v |= int(b&0x7F) << shift + v |= v1.ExemplarType(b&0x7F) << shift if b < 0x80 { break } } - b := bool(v != 0) - m.IncludeExemplars = &b + m.ExemplarType = &v default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) diff --git a/api/gen/proto/go/query/v1/query.pb.go b/api/gen/proto/go/query/v1/query.pb.go index 7380ec4c00..a055e32c50 100644 --- a/api/gen/proto/go/query/v1/query.pb.go +++ b/api/gen/proto/go/query/v1/query.pb.go @@ -1121,13 +1121,13 @@ func (x *SeriesLabelsReport) GetSeriesLabels() []*v11.Labels { } type TimeSeriesQuery struct { - state protoimpl.MessageState `protogen:"open.v1"` - Step float64 `protobuf:"fixed64,1,opt,name=step,proto3" json:"step,omitempty"` - GroupBy []string `protobuf:"bytes,2,rep,name=group_by,json=groupBy,proto3" json:"group_by,omitempty"` - Limit int64 `protobuf:"varint,3,opt,name=limit,proto3" json:"limit,omitempty"` - IncludeExemplars bool `protobuf:"varint,4,opt,name=include_exemplars,json=includeExemplars,proto3" json:"include_exemplars,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Step float64 `protobuf:"fixed64,1,opt,name=step,proto3" json:"step,omitempty"` + GroupBy []string `protobuf:"bytes,2,rep,name=group_by,json=groupBy,proto3" json:"group_by,omitempty"` + Limit int64 `protobuf:"varint,3,opt,name=limit,proto3" json:"limit,omitempty"` + ExemplarType v11.ExemplarType `protobuf:"varint,4,opt,name=exemplar_type,json=exemplarType,proto3,enum=types.v1.ExemplarType" json:"exemplar_type,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *TimeSeriesQuery) Reset() { @@ -1181,11 +1181,11 @@ func (x *TimeSeriesQuery) GetLimit() int64 { return 0 } -func (x *TimeSeriesQuery) GetIncludeExemplars() bool { +func (x *TimeSeriesQuery) GetExemplarType() v11.ExemplarType { if x != nil { - return x.IncludeExemplars + return x.ExemplarType } - return false + return v11.ExemplarType(0) } type TimeSeriesReport struct { @@ -1535,12 +1535,12 @@ const file_query_v1_query_proto_rawDesc = "" + "labelNames\"~\n" + "\x12SeriesLabelsReport\x121\n" + "\x05query\x18\x01 \x01(\v2\x1b.query.v1.SeriesLabelsQueryR\x05query\x125\n" + - "\rseries_labels\x18\x02 \x03(\v2\x10.types.v1.LabelsR\fseriesLabels\"\x83\x01\n" + + "\rseries_labels\x18\x02 \x03(\v2\x10.types.v1.LabelsR\fseriesLabels\"\x93\x01\n" + "\x0fTimeSeriesQuery\x12\x12\n" + "\x04step\x18\x01 \x01(\x01R\x04step\x12\x19\n" + "\bgroup_by\x18\x02 \x03(\tR\agroupBy\x12\x14\n" + - "\x05limit\x18\x03 \x01(\x03R\x05limit\x12+\n" + - "\x11include_exemplars\x18\x04 \x01(\bR\x10includeExemplars\"v\n" + + "\x05limit\x18\x03 \x01(\x03R\x05limit\x12;\n" + + "\rexemplar_type\x18\x04 \x01(\x0e2\x16.types.v1.ExemplarTypeR\fexemplarType\"v\n" + "\x10TimeSeriesReport\x12/\n" + "\x05query\x18\x01 \x01(\v2\x19.query.v1.TimeSeriesQueryR\x05query\x121\n" + "\vtime_series\x18\x02 \x03(\v2\x10.types.v1.SeriesR\n" + @@ -1629,8 +1629,9 @@ var file_query_v1_query_proto_goTypes = []any{ (*PprofReport)(nil), // 24: query.v1.PprofReport (*v1.BlockMeta)(nil), // 25: metastore.v1.BlockMeta (*v11.Labels)(nil), // 26: types.v1.Labels - (*v11.Series)(nil), // 27: types.v1.Series - (*v11.StackTraceSelector)(nil), // 28: types.v1.StackTraceSelector + (v11.ExemplarType)(0), // 27: types.v1.ExemplarType + (*v11.Series)(nil), // 28: types.v1.Series + (*v11.StackTraceSelector)(nil), // 29: types.v1.StackTraceSelector } var file_query_v1_query_proto_depIdxs = []int32{ 9, // 0: query.v1.QueryRequest.query:type_name -> query.v1.Query @@ -1663,21 +1664,22 @@ var file_query_v1_query_proto_depIdxs = []int32{ 15, // 27: query.v1.LabelValuesReport.query:type_name -> query.v1.LabelValuesQuery 17, // 28: query.v1.SeriesLabelsReport.query:type_name -> query.v1.SeriesLabelsQuery 26, // 29: query.v1.SeriesLabelsReport.series_labels:type_name -> types.v1.Labels - 19, // 30: query.v1.TimeSeriesReport.query:type_name -> query.v1.TimeSeriesQuery - 27, // 31: query.v1.TimeSeriesReport.time_series:type_name -> types.v1.Series - 28, // 32: query.v1.TreeQuery.stack_trace_selector:type_name -> types.v1.StackTraceSelector - 21, // 33: query.v1.TreeReport.query:type_name -> query.v1.TreeQuery - 28, // 34: query.v1.PprofQuery.stack_trace_selector:type_name -> types.v1.StackTraceSelector - 23, // 35: query.v1.PprofReport.query:type_name -> query.v1.PprofQuery - 3, // 36: query.v1.QueryFrontendService.Query:input_type -> query.v1.QueryRequest - 6, // 37: query.v1.QueryBackendService.Invoke:input_type -> query.v1.InvokeRequest - 4, // 38: query.v1.QueryFrontendService.Query:output_type -> query.v1.QueryResponse - 10, // 39: query.v1.QueryBackendService.Invoke:output_type -> query.v1.InvokeResponse - 38, // [38:40] is the sub-list for method output_type - 36, // [36:38] is the sub-list for method input_type - 36, // [36:36] is the sub-list for extension type_name - 36, // [36:36] is the sub-list for extension extendee - 0, // [0:36] is the sub-list for field type_name + 27, // 30: query.v1.TimeSeriesQuery.exemplar_type:type_name -> types.v1.ExemplarType + 19, // 31: query.v1.TimeSeriesReport.query:type_name -> query.v1.TimeSeriesQuery + 28, // 32: query.v1.TimeSeriesReport.time_series:type_name -> types.v1.Series + 29, // 33: query.v1.TreeQuery.stack_trace_selector:type_name -> types.v1.StackTraceSelector + 21, // 34: query.v1.TreeReport.query:type_name -> query.v1.TreeQuery + 29, // 35: query.v1.PprofQuery.stack_trace_selector:type_name -> types.v1.StackTraceSelector + 23, // 36: query.v1.PprofReport.query:type_name -> query.v1.PprofQuery + 3, // 37: query.v1.QueryFrontendService.Query:input_type -> query.v1.QueryRequest + 6, // 38: query.v1.QueryBackendService.Invoke:input_type -> query.v1.InvokeRequest + 4, // 39: query.v1.QueryFrontendService.Query:output_type -> query.v1.QueryResponse + 10, // 40: query.v1.QueryBackendService.Invoke:output_type -> query.v1.InvokeResponse + 39, // [39:41] is the sub-list for method output_type + 37, // [37:39] is the sub-list for method input_type + 37, // [37:37] is the sub-list for extension type_name + 37, // [37:37] is the sub-list for extension extendee + 0, // [0:37] is the sub-list for field type_name } func init() { file_query_v1_query_proto_init() } diff --git a/api/gen/proto/go/query/v1/query_vtproto.pb.go b/api/gen/proto/go/query/v1/query_vtproto.pb.go index fa168b1afe..2186c9c91b 100644 --- a/api/gen/proto/go/query/v1/query_vtproto.pb.go +++ b/api/gen/proto/go/query/v1/query_vtproto.pb.go @@ -398,7 +398,7 @@ func (m *TimeSeriesQuery) CloneVT() *TimeSeriesQuery { r := new(TimeSeriesQuery) r.Step = m.Step r.Limit = m.Limit - r.IncludeExemplars = m.IncludeExemplars + r.ExemplarType = m.ExemplarType if rhs := m.GroupBy; rhs != nil { tmpContainer := make([]string, len(rhs)) copy(tmpContainer, rhs) @@ -1077,7 +1077,7 @@ func (this *TimeSeriesQuery) EqualVT(that *TimeSeriesQuery) bool { if this.Limit != that.Limit { return false } - if this.IncludeExemplars != that.IncludeExemplars { + if this.ExemplarType != that.ExemplarType { return false } return string(this.unknownFields) == string(that.unknownFields) @@ -2385,13 +2385,8 @@ func (m *TimeSeriesQuery) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } - if m.IncludeExemplars { - i-- - if m.IncludeExemplars { - dAtA[i] = 1 - } else { - dAtA[i] = 0 - } + if m.ExemplarType != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.ExemplarType)) i-- dAtA[i] = 0x20 } @@ -3086,8 +3081,8 @@ func (m *TimeSeriesQuery) SizeVT() (n int) { if m.Limit != 0 { n += 1 + protohelpers.SizeOfVarint(uint64(m.Limit)) } - if m.IncludeExemplars { - n += 2 + if m.ExemplarType != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.ExemplarType)) } n += len(m.unknownFields) return n @@ -5467,9 +5462,9 @@ func (m *TimeSeriesQuery) UnmarshalVT(dAtA []byte) error { } case 4: if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field IncludeExemplars", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ExemplarType", wireType) } - var v int + m.ExemplarType = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -5479,12 +5474,11 @@ func (m *TimeSeriesQuery) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - v |= int(b&0x7F) << shift + m.ExemplarType |= v11.ExemplarType(b&0x7F) << shift if b < 0x80 { break } } - m.IncludeExemplars = bool(v != 0) default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) diff --git a/api/gen/proto/go/types/v1/types.pb.go b/api/gen/proto/go/types/v1/types.pb.go index d8eb9e2f9f..acbd83ecb9 100644 --- a/api/gen/proto/go/types/v1/types.pb.go +++ b/api/gen/proto/go/types/v1/types.pb.go @@ -68,6 +68,58 @@ func (TimeSeriesAggregationType) EnumDescriptor() ([]byte, []int) { return file_types_v1_types_proto_rawDescGZIP(), []int{0} } +type ExemplarType int32 + +const ( + ExemplarType_EXEMPLAR_TYPE_UNSPECIFIED ExemplarType = 0 + ExemplarType_EXEMPLAR_TYPE_NONE ExemplarType = 1 + ExemplarType_EXEMPLAR_TYPE_INDIVIDUAL ExemplarType = 2 + ExemplarType_EXEMPLAR_TYPE_SPAN ExemplarType = 3 +) + +// Enum value maps for ExemplarType. +var ( + ExemplarType_name = map[int32]string{ + 0: "EXEMPLAR_TYPE_UNSPECIFIED", + 1: "EXEMPLAR_TYPE_NONE", + 2: "EXEMPLAR_TYPE_INDIVIDUAL", + 3: "EXEMPLAR_TYPE_SPAN", + } + ExemplarType_value = map[string]int32{ + "EXEMPLAR_TYPE_UNSPECIFIED": 0, + "EXEMPLAR_TYPE_NONE": 1, + "EXEMPLAR_TYPE_INDIVIDUAL": 2, + "EXEMPLAR_TYPE_SPAN": 3, + } +) + +func (x ExemplarType) Enum() *ExemplarType { + p := new(ExemplarType) + *p = x + return p +} + +func (x ExemplarType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ExemplarType) Descriptor() protoreflect.EnumDescriptor { + return file_types_v1_types_proto_enumTypes[1].Descriptor() +} + +func (ExemplarType) Type() protoreflect.EnumType { + return &file_types_v1_types_proto_enumTypes[1] +} + +func (x ExemplarType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ExemplarType.Descriptor instead. +func (ExemplarType) EnumDescriptor() ([]byte, []int) { + return file_types_v1_types_proto_rawDescGZIP(), []int{1} +} + type LabelPair struct { state protoimpl.MessageState `protogen:"open.v1"` // Label name @@ -1219,7 +1271,12 @@ const file_types_v1_types_proto_rawDesc = "" + "\x06labels\x18\x05 \x03(\v2\x13.types.v1.LabelPairR\x06labels*k\n" + "\x19TimeSeriesAggregationType\x12$\n" + " TIME_SERIES_AGGREGATION_TYPE_SUM\x10\x00\x12(\n" + - "$TIME_SERIES_AGGREGATION_TYPE_AVERAGE\x10\x01B\x9b\x01\n" + + "$TIME_SERIES_AGGREGATION_TYPE_AVERAGE\x10\x01*{\n" + + "\fExemplarType\x12\x1d\n" + + "\x19EXEMPLAR_TYPE_UNSPECIFIED\x10\x00\x12\x16\n" + + "\x12EXEMPLAR_TYPE_NONE\x10\x01\x12\x1c\n" + + "\x18EXEMPLAR_TYPE_INDIVIDUAL\x10\x02\x12\x16\n" + + "\x12EXEMPLAR_TYPE_SPAN\x10\x03B\x9b\x01\n" + "\fcom.types.v1B\n" + "TypesProtoP\x01Z>github.com/grafana/pyroscope/api/gen/proto/go/types/v1;typesv1\xa2\x02\x03TXX\xaa\x02\bTypes.V1\xca\x02\bTypes\\V1\xe2\x02\x14Types\\V1\\GPBMetadata\xea\x02\tTypes::V1b\x06proto3" @@ -1235,40 +1292,41 @@ func file_types_v1_types_proto_rawDescGZIP() []byte { return file_types_v1_types_proto_rawDescData } -var file_types_v1_types_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_types_v1_types_proto_enumTypes = make([]protoimpl.EnumInfo, 2) var file_types_v1_types_proto_msgTypes = make([]protoimpl.MessageInfo, 18) var file_types_v1_types_proto_goTypes = []any{ (TimeSeriesAggregationType)(0), // 0: types.v1.TimeSeriesAggregationType - (*LabelPair)(nil), // 1: types.v1.LabelPair - (*ProfileType)(nil), // 2: types.v1.ProfileType - (*Labels)(nil), // 3: types.v1.Labels - (*Series)(nil), // 4: types.v1.Series - (*Point)(nil), // 5: types.v1.Point - (*ProfileAnnotation)(nil), // 6: types.v1.ProfileAnnotation - (*LabelValuesRequest)(nil), // 7: types.v1.LabelValuesRequest - (*LabelValuesResponse)(nil), // 8: types.v1.LabelValuesResponse - (*LabelNamesRequest)(nil), // 9: types.v1.LabelNamesRequest - (*LabelNamesResponse)(nil), // 10: types.v1.LabelNamesResponse - (*BlockInfo)(nil), // 11: types.v1.BlockInfo - (*BlockCompaction)(nil), // 12: types.v1.BlockCompaction - (*StackTraceSelector)(nil), // 13: types.v1.StackTraceSelector - (*Location)(nil), // 14: types.v1.Location - (*GoPGO)(nil), // 15: types.v1.GoPGO - (*GetProfileStatsRequest)(nil), // 16: types.v1.GetProfileStatsRequest - (*GetProfileStatsResponse)(nil), // 17: types.v1.GetProfileStatsResponse - (*Exemplar)(nil), // 18: types.v1.Exemplar + (ExemplarType)(0), // 1: types.v1.ExemplarType + (*LabelPair)(nil), // 2: types.v1.LabelPair + (*ProfileType)(nil), // 3: types.v1.ProfileType + (*Labels)(nil), // 4: types.v1.Labels + (*Series)(nil), // 5: types.v1.Series + (*Point)(nil), // 6: types.v1.Point + (*ProfileAnnotation)(nil), // 7: types.v1.ProfileAnnotation + (*LabelValuesRequest)(nil), // 8: types.v1.LabelValuesRequest + (*LabelValuesResponse)(nil), // 9: types.v1.LabelValuesResponse + (*LabelNamesRequest)(nil), // 10: types.v1.LabelNamesRequest + (*LabelNamesResponse)(nil), // 11: types.v1.LabelNamesResponse + (*BlockInfo)(nil), // 12: types.v1.BlockInfo + (*BlockCompaction)(nil), // 13: types.v1.BlockCompaction + (*StackTraceSelector)(nil), // 14: types.v1.StackTraceSelector + (*Location)(nil), // 15: types.v1.Location + (*GoPGO)(nil), // 16: types.v1.GoPGO + (*GetProfileStatsRequest)(nil), // 17: types.v1.GetProfileStatsRequest + (*GetProfileStatsResponse)(nil), // 18: types.v1.GetProfileStatsResponse + (*Exemplar)(nil), // 19: types.v1.Exemplar } var file_types_v1_types_proto_depIdxs = []int32{ - 1, // 0: types.v1.Labels.labels:type_name -> types.v1.LabelPair - 1, // 1: types.v1.Series.labels:type_name -> types.v1.LabelPair - 5, // 2: types.v1.Series.points:type_name -> types.v1.Point - 6, // 3: types.v1.Point.annotations:type_name -> types.v1.ProfileAnnotation - 18, // 4: types.v1.Point.exemplars:type_name -> types.v1.Exemplar - 12, // 5: types.v1.BlockInfo.compaction:type_name -> types.v1.BlockCompaction - 1, // 6: types.v1.BlockInfo.labels:type_name -> types.v1.LabelPair - 14, // 7: types.v1.StackTraceSelector.call_site:type_name -> types.v1.Location - 15, // 8: types.v1.StackTraceSelector.go_pgo:type_name -> types.v1.GoPGO - 1, // 9: types.v1.Exemplar.labels:type_name -> types.v1.LabelPair + 2, // 0: types.v1.Labels.labels:type_name -> types.v1.LabelPair + 2, // 1: types.v1.Series.labels:type_name -> types.v1.LabelPair + 6, // 2: types.v1.Series.points:type_name -> types.v1.Point + 7, // 3: types.v1.Point.annotations:type_name -> types.v1.ProfileAnnotation + 19, // 4: types.v1.Point.exemplars:type_name -> types.v1.Exemplar + 13, // 5: types.v1.BlockInfo.compaction:type_name -> types.v1.BlockCompaction + 2, // 6: types.v1.BlockInfo.labels:type_name -> types.v1.LabelPair + 15, // 7: types.v1.StackTraceSelector.call_site:type_name -> types.v1.Location + 16, // 8: types.v1.StackTraceSelector.go_pgo:type_name -> types.v1.GoPGO + 2, // 9: types.v1.Exemplar.labels:type_name -> types.v1.LabelPair 10, // [10:10] is the sub-list for method output_type 10, // [10:10] is the sub-list for method input_type 10, // [10:10] is the sub-list for extension type_name @@ -1286,7 +1344,7 @@ func file_types_v1_types_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_types_v1_types_proto_rawDesc), len(file_types_v1_types_proto_rawDesc)), - NumEnums: 1, + NumEnums: 2, NumMessages: 18, NumExtensions: 0, NumServices: 0, diff --git a/api/openapiv2/gen/phlare.swagger.json b/api/openapiv2/gen/phlare.swagger.json index ee2ea67c7d..22a67ab3f0 100644 --- a/api/openapiv2/gen/phlare.swagger.json +++ b/api/openapiv2/gen/phlare.swagger.json @@ -993,6 +993,16 @@ }, "description": "Exemplar represents metadata for an individual profile sample.\nExemplars allow users to drill down from aggregated timeline views\nto specific profile instances." }, + "v1ExemplarType": { + "type": "string", + "enum": [ + "EXEMPLAR_TYPE_UNSPECIFIED", + "EXEMPLAR_TYPE_NONE", + "EXEMPLAR_TYPE_INDIVIDUAL", + "EXEMPLAR_TYPE_SPAN" + ], + "default": "EXEMPLAR_TYPE_UNSPECIFIED" + }, "v1FeatureFlag": { "type": "object", "properties": { @@ -2461,8 +2471,8 @@ "type": "string", "format": "int64" }, - "includeExemplars": { - "type": "boolean" + "exemplarType": { + "$ref": "#/definitions/v1ExemplarType" } } }, diff --git a/api/querier/v1/querier.proto b/api/querier/v1/querier.proto index 856c70fd2c..ea8fe9b3c2 100644 --- a/api/querier/v1/querier.proto +++ b/api/querier/v1/querier.proto @@ -219,7 +219,8 @@ message SelectSeriesRequest { optional types.v1.StackTraceSelector stack_trace_selector = 8; // Select the top N series by total value. optional int64 limit = 9; - optional bool include_exemplars = 10; + // Type of exemplars to include in the response. + optional types.v1.ExemplarType exemplar_type = 10; } message SelectSeriesResponse { diff --git a/api/query/v1/query.proto b/api/query/v1/query.proto index f303a46b41..ce30905b41 100644 --- a/api/query/v1/query.proto +++ b/api/query/v1/query.proto @@ -161,7 +161,7 @@ message TimeSeriesQuery { double step = 1; repeated string group_by = 2; int64 limit = 3; - bool include_exemplars = 4; + types.v1.ExemplarType exemplar_type = 4; } message TimeSeriesReport { diff --git a/api/types/v1/types.proto b/api/types/v1/types.proto index f688dd8d7b..9149906e7a 100644 --- a/api/types/v1/types.proto +++ b/api/types/v1/types.proto @@ -102,6 +102,13 @@ enum TimeSeriesAggregationType { TIME_SERIES_AGGREGATION_TYPE_AVERAGE = 1; } +enum ExemplarType { + EXEMPLAR_TYPE_UNSPECIFIED = 0; + EXEMPLAR_TYPE_NONE = 1; + EXEMPLAR_TYPE_INDIVIDUAL = 2; + EXEMPLAR_TYPE_SPAN = 3; +} + // StackTraceSelector is used for filtering stack traces by locations. message StackTraceSelector { // Stack trace of the call site. Root at call_site[0]. diff --git a/docs/sources/reference-server-api/index.md b/docs/sources/reference-server-api/index.md index 5b3e4ac579..23bfa03ae8 100644 --- a/docs/sources/reference-server-api/index.md +++ b/docs/sources/reference-server-api/index.md @@ -483,8 +483,8 @@ A request body with the following fields is required: |`start` | Milliseconds since epoch. | `1676282400000` | |`end` | Milliseconds since epoch. | `1676289600000` | |`aggregation` | | | +|`exemplarType` | | | |`groupBy` | | `["pod"]` | -|`includeExemplars` | | | |`labelSelector` | Label selector string | `{namespace="my-namespace"}` | |`limit` | Select the top N series by total value. | | |`profileTypeID` | Profile Type ID string in the form ::::. | `process_cpu:cpu:nanoseconds:cpu:nanoseconds` | From 98b88e595b0bd6bc78fcc4b753dd64c0eee16fdd Mon Sep 17 00:00:00 2001 From: Marc Sanmiquel Date: Mon, 17 Nov 2025 13:44:23 +0100 Subject: [PATCH 6/6] Remove option from exemplar_type --- .../gen/querier/v1/querier.openapi.yaml | 1 - api/gen/proto/go/querier/v1/querier.pb.go | 15 +++++++------- .../proto/go/querier/v1/querier_vtproto.pb.go | 20 ++++++++----------- api/querier/v1/querier.proto | 2 +- 4 files changed, 16 insertions(+), 22 deletions(-) diff --git a/api/connect-openapi/gen/querier/v1/querier.openapi.yaml b/api/connect-openapi/gen/querier/v1/querier.openapi.yaml index 39ced81124..7dfdf8ea07 100644 --- a/api/connect-openapi/gen/querier/v1/querier.openapi.yaml +++ b/api/connect-openapi/gen/querier/v1/querier.openapi.yaml @@ -1439,7 +1439,6 @@ components: exemplarType: title: exemplar_type description: Type of exemplars to include in the response. - nullable: true $ref: '#/components/schemas/types.v1.ExemplarType' title: SelectSeriesRequest additionalProperties: false diff --git a/api/gen/proto/go/querier/v1/querier.pb.go b/api/gen/proto/go/querier/v1/querier.pb.go index ef4b36e38e..4ba89f377c 100644 --- a/api/gen/proto/go/querier/v1/querier.pb.go +++ b/api/gen/proto/go/querier/v1/querier.pb.go @@ -1017,7 +1017,7 @@ type SelectSeriesRequest struct { // Select the top N series by total value. Limit *int64 `protobuf:"varint,9,opt,name=limit,proto3,oneof" json:"limit,omitempty"` // Type of exemplars to include in the response. - ExemplarType *v1.ExemplarType `protobuf:"varint,10,opt,name=exemplar_type,json=exemplarType,proto3,enum=types.v1.ExemplarType,oneof" json:"exemplar_type,omitempty"` + ExemplarType v1.ExemplarType `protobuf:"varint,10,opt,name=exemplar_type,json=exemplarType,proto3,enum=types.v1.ExemplarType" json:"exemplar_type,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1116,8 +1116,8 @@ func (x *SelectSeriesRequest) GetLimit() int64 { } func (x *SelectSeriesRequest) GetExemplarType() v1.ExemplarType { - if x != nil && x.ExemplarType != nil { - return *x.ExemplarType + if x != nil { + return x.ExemplarType } return v1.ExemplarType(0) } @@ -1532,7 +1532,7 @@ const file_querier_v1_querier_proto_rawDesc = "" + "\x14stack_trace_selector\x18\x06 \x01(\v2\x1c.types.v1.StackTraceSelectorH\x01R\x12stackTraceSelector\x88\x01\x01B\f\n" + "\n" + "_max_nodesB\x17\n" + - "\x15_stack_trace_selector\"\x92\x05\n" + + "\x15_stack_trace_selector\"\xfb\x04\n" + "\x13SelectSeriesRequest\x12Y\n" + "\x0eprofile_typeID\x18\x01 \x01(\tB2\xbaG/:-\x12+process_cpu:cpu:nanoseconds:cpu:nanosecondsR\rprofileTypeID\x12J\n" + "\x0elabel_selector\x18\x02 \x01(\tB#\xbaG :\x1e\x12\x1c'{namespace=\"my-namespace\"}'R\rlabelSelector\x12*\n" + @@ -1542,13 +1542,12 @@ const file_querier_v1_querier_proto_rawDesc = "" + "\x04step\x18\x06 \x01(\x01R\x04step\x12J\n" + "\vaggregation\x18\a \x01(\x0e2#.types.v1.TimeSeriesAggregationTypeH\x00R\vaggregation\x88\x01\x01\x12S\n" + "\x14stack_trace_selector\x18\b \x01(\v2\x1c.types.v1.StackTraceSelectorH\x01R\x12stackTraceSelector\x88\x01\x01\x12\x19\n" + - "\x05limit\x18\t \x01(\x03H\x02R\x05limit\x88\x01\x01\x12@\n" + + "\x05limit\x18\t \x01(\x03H\x02R\x05limit\x88\x01\x01\x12;\n" + "\rexemplar_type\x18\n" + - " \x01(\x0e2\x16.types.v1.ExemplarTypeH\x03R\fexemplarType\x88\x01\x01B\x0e\n" + + " \x01(\x0e2\x16.types.v1.ExemplarTypeR\fexemplarTypeB\x0e\n" + "\f_aggregationB\x17\n" + "\x15_stack_trace_selectorB\b\n" + - "\x06_limitB\x10\n" + - "\x0e_exemplar_type\"@\n" + + "\x06_limit\"@\n" + "\x14SelectSeriesResponse\x12(\n" + "\x06series\x18\x01 \x03(\v2\x10.types.v1.SeriesR\x06series\"S\n" + "\x13AnalyzeQueryRequest\x12\x14\n" + diff --git a/api/gen/proto/go/querier/v1/querier_vtproto.pb.go b/api/gen/proto/go/querier/v1/querier_vtproto.pb.go index 9dc5cd7782..2e0837e93c 100644 --- a/api/gen/proto/go/querier/v1/querier_vtproto.pb.go +++ b/api/gen/proto/go/querier/v1/querier_vtproto.pb.go @@ -397,6 +397,7 @@ func (m *SelectSeriesRequest) CloneVT() *SelectSeriesRequest { r.Start = m.Start r.End = m.End r.Step = m.Step + r.ExemplarType = m.ExemplarType if rhs := m.GroupBy; rhs != nil { tmpContainer := make([]string, len(rhs)) copy(tmpContainer, rhs) @@ -417,10 +418,6 @@ func (m *SelectSeriesRequest) CloneVT() *SelectSeriesRequest { tmpVal := *rhs r.Limit = &tmpVal } - if rhs := m.ExemplarType; rhs != nil { - tmpVal := *rhs - r.ExemplarType = &tmpVal - } if len(m.unknownFields) > 0 { r.unknownFields = make([]byte, len(m.unknownFields)) copy(r.unknownFields, m.unknownFields) @@ -1074,7 +1071,7 @@ func (this *SelectSeriesRequest) EqualVT(that *SelectSeriesRequest) bool { if p, q := this.Limit, that.Limit; (p == nil && q != nil) || (p != nil && (q == nil || *p != *q)) { return false } - if p, q := this.ExemplarType, that.ExemplarType; (p == nil && q != nil) || (p != nil && (q == nil || *p != *q)) { + if this.ExemplarType != that.ExemplarType { return false } return string(this.unknownFields) == string(that.unknownFields) @@ -2635,8 +2632,8 @@ func (m *SelectSeriesRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } - if m.ExemplarType != nil { - i = protohelpers.EncodeVarint(dAtA, i, uint64(*m.ExemplarType)) + if m.ExemplarType != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.ExemplarType)) i-- dAtA[i] = 0x50 } @@ -3408,8 +3405,8 @@ func (m *SelectSeriesRequest) SizeVT() (n int) { if m.Limit != nil { n += 1 + protohelpers.SizeOfVarint(uint64(*m.Limit)) } - if m.ExemplarType != nil { - n += 1 + protohelpers.SizeOfVarint(uint64(*m.ExemplarType)) + if m.ExemplarType != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.ExemplarType)) } n += len(m.unknownFields) return n @@ -5860,7 +5857,7 @@ func (m *SelectSeriesRequest) UnmarshalVT(dAtA []byte) error { if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field ExemplarType", wireType) } - var v v1.ExemplarType + m.ExemplarType = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -5870,12 +5867,11 @@ func (m *SelectSeriesRequest) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - v |= v1.ExemplarType(b&0x7F) << shift + m.ExemplarType |= v1.ExemplarType(b&0x7F) << shift if b < 0x80 { break } } - m.ExemplarType = &v default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) diff --git a/api/querier/v1/querier.proto b/api/querier/v1/querier.proto index ea8fe9b3c2..a27d01248e 100644 --- a/api/querier/v1/querier.proto +++ b/api/querier/v1/querier.proto @@ -220,7 +220,7 @@ message SelectSeriesRequest { // Select the top N series by total value. optional int64 limit = 9; // Type of exemplars to include in the response. - optional types.v1.ExemplarType exemplar_type = 10; + types.v1.ExemplarType exemplar_type = 10; } message SelectSeriesResponse {