Skip to content

Conversation

@tomerqodo
Copy link

@tomerqodo tomerqodo commented Dec 4, 2025

User description

Benchmark PR getsentry#103102

Type: Clean (correct implementation)

Original PR Title: feat(tracemetrics): Parse metric from aggregations
Original PR Description: This parses the metric from aggregations instead of relying on the top level metric name/type. The top level ones still take precedence for better compatibility. It has the limitation that it only allows a single metric across all the aggregations.
Original PR URL: getsentry#103102


PR Type

Enhancement


Description

  • Parse metric information from aggregation functions instead of relying solely on top-level metric configuration

  • Extract metric name, type, and unit from function arguments and store in new ResolvedMetricAggregate class

  • Refactor metric filter generation into reusable utility functions for cleaner code organization

  • Add support for multiple aggregations with the same metric while preventing cross-metric aggregations


Diagram Walkthrough

flowchart LR
  A["Aggregation Functions"] -->|extract metric info| B["ResolvedMetricAggregate"]
  C["Top-level Config"] -->|fallback| B
  B -->|generate filters| D["Metric Filters"]
  D -->|apply to query| E["Query Execution"]
Loading

File Walkthrough

Relevant files
Enhancement
columns.py
Add metric extraction from aggregation arguments                 

src/sentry/search/eap/columns.py

  • Add cast import for type casting
  • Import MetricType from types module
  • Create new ResolvedMetricAggregate dataclass extending
    ResolvedAggregate with metric metadata fields
  • Update TraceMetricAggregateDefinition.resolve() to extract and store
    metric name, type, and unit from function arguments
  • Return ResolvedMetricAggregate instead of ResolvedAggregate with
    populated metric fields
+24/-6   
resolver.py
Add column-aware query resolution with dataset conditions

src/sentry/search/eap/resolver.py

  • Import and_trace_item_filters utility function
  • Refactor resolve_query() to use and_trace_item_filters() for cleaner
    filter combination logic
  • Add new resolve_query_with_columns() method that accepts selected
    columns and equations
  • Implement resolve_dataset_conditions() to extract dataset-specific
    conditions from selected columns
  • Call config.extra_conditions() with column and equation information
    for metric-aware filtering
+35/-22 
config.py
Implement metric parsing from aggregations with fallback 

src/sentry/search/eap/trace_metrics/config.py

  • Move MetricType definition to types module
  • Create new Metric dataclass to represent metric with name, type, and
    unit
  • Refactor extra_conditions() signature to accept selected columns and
    equations
  • Add _extra_conditions_from_metric() to handle top-level config metric
    filtering
  • Add _extra_conditions_from_columns() to parse metrics from aggregation
    functions
  • Extract get_metric_filter() as standalone function for reusability
  • Add validation to prevent aggregating multiple different metrics in
    single query
+118/-37
types.py
Update config interface and centralize type definitions   

src/sentry/search/eap/types.py

  • Update SearchResolverConfig.extra_conditions() signature to accept
    selected columns and equations parameters
  • Move MetricType type alias definition from trace_metrics config to
    types module
+9/-1     
Refactoring
rpc_utils.py
Extract filter combination utility function                           

src/sentry/search/eap/rpc_utils.py

  • Create new utility module with and_trace_item_filters() function
  • Consolidate filter combination logic to handle multiple
    TraceItemFilter objects
  • Return single filter, combined filter, or None based on input count
+14/-0   
rpc_dataset_common.py
Integrate column-aware query resolution and consolidate utilities

src/sentry/snuba/rpc_dataset_common.py

  • Import and_trace_item_filters from rpc_utils module
  • Update get_table_rpc_request() to call resolve_query_with_columns()
    with selected columns and equations
  • Move variable declarations in get_timeseries_query() before filter
    resolution for proper metric extraction
  • Remove manual extra_conditions calls and let
    resolve_query_with_columns() handle dataset conditions
  • Remove duplicate and_trace_item_filters() function definition
+18/-29 
trace_metrics.py
Remove redundant extra conditions extraction                         

src/sentry/snuba/trace_metrics.py

  • Remove manual extra_conditions extraction calls from run_table_query()
    and run_timeseries_query()
  • Let resolve_query_with_columns() handle metric condition injection
    automatically
+0/-6     
Tests
test_organization_events_trace_metrics.py
Update tests for metric unit format and aggregation parsing

tests/snuba/api/endpoints/test_organization_events_trace_metrics.py

  • Update test cases to use - instead of none for missing metric unit
    parameter
  • Add three new test cases for metric extraction from aggregation
    functions
  • Test single metric aggregation with embedded metric info
  • Test multiple aggregations with same metric name
  • Test error handling for multiple different metrics in single query
+74/-11 

@qodo-code-review
Copy link

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
No audit logs: The new query resolution paths and condition injections (e.g., resolve_query_with_columns
and resolve_dataset_conditions) introduce critical behavior changes without any added
auditing or logging of these actions.

Referred Code
def resolve_query_with_columns(
    self,
    querystring: str | None,
    selected_columns: list[str] | None,
    equations: list[str] | None,
) -> tuple[
    TraceItemFilter | None,
    AggregationFilter | None,
    list[VirtualColumnDefinition | None],
]:
    where, having, contexts = self.resolve_query(querystring)

    # Some datasets like trace metrics require we inject additional
    # conditions in the top level.
    dataset_conditions = self.resolve_dataset_conditions(selected_columns, equations)
    where = and_trace_item_filters(where, dataset_conditions)

    return where, having, contexts

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Input validation: Metric parsing trusts argument positions and only validates emptiness/type loosely,
potentially leading to runtime issues if fewer than expected arguments are supplied or
wrong types are passed.

Referred Code
metric_name = None
metric_type = None
metric_unit = None

if all(
    isinstance(resolved_argument, str) and resolved_argument != ""
    for resolved_argument in resolved_arguments[1:]
):
    # a metric was passed
    metric_name = cast(str, resolved_arguments[1])
    metric_type = cast(MetricType, resolved_arguments[2])
    metric_unit = None if resolved_arguments[3] == "-" else cast(str, resolved_arguments[3])
elif all(resolved_argument == "" for resolved_argument in resolved_arguments[1:]):
    # no metrics were specified, assume we query all metrics
    pass
else:
    raise InvalidSearchQuery(
        f"Trace metric aggregates expect the full metric to be specified, got name:{resolved_arguments[1]} type:{resolved_arguments[2]} unit:{resolved_arguments[3]}"
    )

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Set handling risks: Using a set of Metric dataclass instances (selected_metrics: set[Metric]) relies on
dataclass hash/eq defaults which may not behave as intended for uniqueness and could
mis-detect multiple metrics.

Referred Code
selected_metrics: set[Metric] = set()

if selected_columns:
    stripped_columns = [column.strip() for column in selected_columns]
    for column in stripped_columns:
        match = fields.is_function(column)
        if not match:
            continue

        resolved_function, _ = search_resolver.resolve_function(column)
        if not isinstance(resolved_function, ResolvedMetricAggregate):
            continue

        if not resolved_function.metric_name or not resolved_function.metric_type:
            continue

        metric = Metric(
            metric_name=resolved_function.metric_name,
            metric_type=resolved_function.metric_type,
            metric_unit=resolved_function.metric_unit,
        )


 ... (clipped 13 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Improve metric argument parsing logic

Refine the metric argument parsing logic to be more flexible. Handle partially
specified metrics and add validation for the metric_type to prevent potential
errors.

src/sentry/search/eap/columns.py [403-417]

-if all(
-    isinstance(resolved_argument, str) and resolved_argument != ""
-    for resolved_argument in resolved_arguments[1:]
-):
-    # a metric was passed
-    metric_name = cast(str, resolved_arguments[1])
-    metric_type = cast(MetricType, resolved_arguments[2])
-    metric_unit = None if resolved_arguments[3] == "-" else cast(str, resolved_arguments[3])
-elif all(resolved_argument == "" for resolved_argument in resolved_arguments[1:]):
-    # no metrics were specified, assume we query all metrics
-    pass
-else:
-    raise InvalidSearchQuery(
-        f"Trace metric aggregates expect the full metric to be specified, got name:{resolved_arguments[1]} type:{resolved_arguments[2]} unit:{resolved_arguments[3]}"
-    )
+metric_name = cast(str, resolved_arguments[1]) if resolved_arguments[1] else None
+metric_type_str = cast(str, resolved_arguments[2]) if resolved_arguments[2] else None
+metric_unit = cast(str, resolved_arguments[3]) if resolved_arguments[3] not in ("-", "") else None
 
+metric_type: MetricType | None = None
+if metric_type_str:
+    if metric_type_str not in get_args(MetricType):
+        raise InvalidSearchQuery(f"Invalid metric type: {metric_type_str}")
+    metric_type = cast(MetricType, metric_type_str)
+
+if metric_name and not metric_type:
+    raise InvalidSearchQuery("metric type is required when a metric name is provided")
+
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that the current logic is too strict and proposes a more flexible approach for parsing metric arguments, including validation for metric_type. This improves robustness and user experience by allowing partially specified metrics.

Medium
  • More

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants