Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/7076_1 | Added Inspect metrics API #7197

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
298 changes: 262 additions & 36 deletions pkg/query-service/app/clickhouseReader/reader.go

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions pkg/query-service/app/dashboards/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -617,10 +617,10 @@ func GetDashboardsWithMetricNames(ctx context.Context, metricNames []string) (ma
for _, metricName := range metricNames {
if strings.TrimSpace(key) == metricName {
result[metricName] = append(result[metricName], map[string]string{
"dashboard_id": dashboard.Uuid,
"widget_title": widgetTitle,
"widget_id": widgetID,
"dashboard_title": dashTitle,
"dashboard_id": dashboard.Uuid,
"widget_name": widgetTitle,
"widget_id": widgetID,
"dashboard_name": dashTitle,
})
}
}
Expand Down
3 changes: 3 additions & 0 deletions pkg/query-service/app/http_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,9 @@ func (ah *APIHandler) MetricExplorerRoutes(router *mux.Router, am *AuthMiddlewar
router.HandleFunc("/api/v1/metrics/related",
am.ViewAccess(ah.GetRelatedMetrics)).
Methods(http.MethodPost)
router.HandleFunc("/api/v1/metrics/inspect",
am.ViewAccess(ah.GetInspectMetricsData)).
Methods(http.MethodPost)
}

func Intersection(a, b []int) (c []int) {
Expand Down
11 changes: 11 additions & 0 deletions pkg/query-service/app/metricsexplorer/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,14 @@ func ParseRelatedMetricsParams(r *http.Request) (*metrics_explorer.RelatedMetric
}
return &relatedMetricParams, nil
}

func ParseInspectMetricsParams(r *http.Request) (*metrics_explorer.InspectMetricsRequest, *model.ApiError) {
var inspectMetricParams metrics_explorer.InspectMetricsRequest
if err := json.NewDecoder(r.Body).Decode(&inspectMetricParams); err != nil {
return nil, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("cannot parse the request body: %v", err)}
}
if inspectMetricParams.End-inspectMetricParams.Start > 1800000 { // half hour only
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extract magic number 1800000 into a named constant for clarity.

return nil, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("time duration shouldn't be more than 30 mins")}
}
return &inspectMetricParams, nil
}
91 changes: 88 additions & 3 deletions pkg/query-service/app/metricsexplorer/summary.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"sort"
"strings"
"time"

"go.uber.org/zap"
Expand Down Expand Up @@ -142,7 +143,7 @@ func (receiver *SummaryService) GetMetricsSummary(ctx context.Context, metricNam
})

g.Go(func() error {
attributes, err := receiver.reader.GetAttributesForMetricName(ctx, metricName)
attributes, err := receiver.reader.GetAttributesForMetricName(ctx, metricName, nil, nil)
if err != nil {
return err
}
Expand All @@ -166,13 +167,15 @@ func (receiver *SummaryService) GetMetricsSummary(ctx context.Context, metricNam
return &model.ApiError{Typ: "MarshallingErr", Err: err}
}

var dashboards []metrics_explorer.Dashboard
var dashboards map[string][]metrics_explorer.Dashboard
err = json.Unmarshal(jsonData, &dashboards)
if err != nil {
zap.L().Error("Error unmarshalling data:", zap.Error(err))
return &model.ApiError{Typ: "UnMarshallingErr", Err: err}
}
metricDetailsDTO.Dashboards = dashboards
if _, ok := dashboards[metricName]; ok {
metricDetailsDTO.Dashboards = dashboards[metricName]
}
}
return nil
})
Expand Down Expand Up @@ -447,3 +450,85 @@ func getQueryRangeForRelateMetricsList(metricName string, scores metrics_explore

return &query
}

func (receiver *SummaryService) GetInspectMetrics(ctx context.Context, params *metrics_explorer.InspectMetricsRequest) (*metrics_explorer.InspectMetricsResponse, *model.ApiError) {
// Capture the original context.
parentCtx := ctx

// Create an errgroup using the original context.
g, egCtx := errgroup.WithContext(ctx)

var attributes []metrics_explorer.Attribute
var resourceAttrs map[string]uint64

// Run the two queries concurrently using the derived context.
g.Go(func() error {
attrs, apiErr := receiver.reader.GetAttributesForMetricName(egCtx, params.MetricName, &params.Start, &params.End)
if apiErr != nil {
return apiErr
}
if attrs != nil {
attributes = *attrs
}
return nil
})

g.Go(func() error {
resAttrs, apiErr := receiver.reader.GetMetricsAllResourceAttributes(egCtx, params.Start, params.End)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the metadata table has the 6 hours rounded precision, directly passing start and end won't give data in many cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolved

if apiErr != nil {
return apiErr
}
if resAttrs != nil {
resourceAttrs = resAttrs
}
return nil
})

// Wait for the concurrent operations to complete.
if err := g.Wait(); err != nil {
return nil, &model.ApiError{Typ: "InternalError", Err: err}
}

// Use the parentCtx (or create a new context from it) for the rest of the calls.
if parentCtx.Err() != nil {
return nil, &model.ApiError{Typ: "ContextCanceled", Err: parentCtx.Err()}
}

// Build a set of attribute keys for O(1) lookup.
attributeKeys := make(map[string]struct{})
for _, attr := range attributes {
attributeKeys[attr.Key] = struct{}{}
}

// Filter resource attributes that are present in attributes.
var validAttrs []string
for attrName := range resourceAttrs {
normalizedAttrName := strings.ReplaceAll(attrName, ".", "_")
if _, ok := attributeKeys[normalizedAttrName]; ok {
validAttrs = append(validAttrs, normalizedAttrName)
}
}

// Get top 3 resource attributes (or use top attributes by valueCount if none match).
if len(validAttrs) > 3 {
validAttrs = validAttrs[:3]
} else if len(validAttrs) == 0 {
sort.Slice(attributes, func(i, j int) bool {
return attributes[i].ValueCount > attributes[j].ValueCount
})
for i := 0; i < len(attributes) && i < 3; i++ {
validAttrs = append(validAttrs, attributes[i].Key)
}
}
fingerprints, apiError := receiver.reader.GetInspectMetricsFingerprints(parentCtx, validAttrs, params)
if apiError != nil {
return nil, apiError
}

baseResponse, apiErr := receiver.reader.GetInspectMetrics(parentCtx, params, fingerprints)
if apiErr != nil {
return nil, apiErr
}

return baseResponse, nil
}
20 changes: 20 additions & 0 deletions pkg/query-service/app/summary.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,23 @@ func (aH *APIHandler) GetRelatedMetrics(w http.ResponseWriter, r *http.Request)
aH.Respond(w, result)

}

func (aH *APIHandler) GetInspectMetricsData(w http.ResponseWriter, r *http.Request) {
bodyBytes, _ := io.ReadAll(r.Body)
r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
ctx := r.Context()
params, apiError := explorer.ParseInspectMetricsParams(r)
if apiError != nil {
zap.L().Error("error parsing metric query range params", zap.Error(apiError.Err))
RespondError(w, apiError, nil)
return
}
result, apiError := aH.SummaryService.GetInspectMetrics(ctx, params)
if apiError != nil {
zap.L().Error("error getting inspect metrics data", zap.Error(apiError.Err))
RespondError(w, apiError, nil)
return
}
aH.Respond(w, result)

}
2 changes: 2 additions & 0 deletions pkg/query-service/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,8 @@ const (
SIGNOZ_TIMESERIES_v4_TABLENAME = "distributed_time_series_v4"
SIGNOZ_TIMESERIES_v4_1WEEK_TABLENAME = "distributed_time_series_v4_1week"
SIGNOZ_TIMESERIES_v4_6HRS_TABLENAME = "distributed_time_series_v4_6hrs"
SIGNOZ_ATTRIBUTES_METADATA_TABLENAME = "distributed_attributes_metadata"
SIGNOZ_ATTRIBUTES_METADATA_LOCAL_TABLENAME = "attributes_metadata"
)

// alert related constants
Expand Down
6 changes: 5 additions & 1 deletion pkg/query-service/interfaces/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ type Reader interface {
GetMetricsLastReceived(ctx context.Context, metricName string) (int64, *model.ApiError)
GetTotalTimeSeriesForMetricName(ctx context.Context, metricName string) (uint64, *model.ApiError)
GetActiveTimeSeriesForMetricName(ctx context.Context, metricName string, duration time.Duration) (uint64, *model.ApiError)
GetAttributesForMetricName(ctx context.Context, metricName string) (*[]metrics_explorer.Attribute, *model.ApiError)
GetAttributesForMetricName(ctx context.Context, metricName string, start, end *int64) (*[]metrics_explorer.Attribute, *model.ApiError)

ListSummaryMetrics(ctx context.Context, req *metrics_explorer.SummaryListMetricsRequest) (*metrics_explorer.SummaryListMetricsResponse, *model.ApiError)

Expand All @@ -135,6 +135,10 @@ type Reader interface {

GetNameSimilarity(ctx context.Context, req *metrics_explorer.RelatedMetricsRequest) (map[string]metrics_explorer.RelatedMetricsScore, *model.ApiError)
GetAttributeSimilarity(ctx context.Context, req *metrics_explorer.RelatedMetricsRequest) (map[string]metrics_explorer.RelatedMetricsScore, *model.ApiError)

GetMetricsAllResourceAttributes(ctx context.Context, start int64, end int64) (map[string]uint64, *model.ApiError)
GetInspectMetricsFingerprints(ctx context.Context, attributes []string, req *metrics_explorer.InspectMetricsRequest) ([]string, *model.ApiError)
GetInspectMetrics(ctx context.Context, req *metrics_explorer.InspectMetricsRequest, fingerprints []string) (*metrics_explorer.InspectMetricsResponse, *model.ApiError)
}

type Querier interface {
Expand Down
11 changes: 11 additions & 0 deletions pkg/query-service/model/metrics_explorer/summary.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,14 @@ type RelatedMetrics struct {
Dashboards []Dashboard `json:"dashboards"`
Alerts []Alert `json:"alerts"`
}

type InspectMetricsRequest struct {
MetricName string `json:"metricName"`
Filters v3.FilterSet `json:"filters"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adding filters wont be possible for cases like not in etc, as we are manually getting resource attributes for the user. so filters wont be applicable for the users

Start int64 `json:"start"`
End int64 `json:"end"`
}

type InspectMetricsResponse struct {
Series *[]v3.Series `json:"series,omitempty"`
}
7 changes: 7 additions & 0 deletions pkg/query-service/utils/filter_conditions.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,10 @@ func WhichSampleTableToUse(start, end int64) (string, string) {
return constants.SIGNOZ_SAMPLES_V4_AGG_30M_TABLENAME, "sum(count)"
}
}

func WhichAttributesTableToUse(start, end int64) (int64, int64, string, string) {
if end-start < sixHoursInMilliseconds {
start = start - (start % (time.Hour.Milliseconds() * 6))
}
return start, end, constants.SIGNOZ_ATTRIBUTES_METADATA_TABLENAME, constants.SIGNOZ_ATTRIBUTES_METADATA_LOCAL_TABLENAME
}
Loading