def get_single_metric(self, metric_name: str) -> MetricMetaWithTagKeys: """Get metadata for a single metric, without tag values""" metric_id = indexer.resolve(metric_name) if metric_id is None: raise InvalidParams for metric_type in ("counter", "set", "distribution"): # TODO: What if metric_id exists for multiple types / units? entity_key = METRIC_TYPE_TO_ENTITY[metric_type] data = self._get_data( entity_key=entity_key, select=[Column("metric_id"), Column("tags.key")], where=[Condition(Column("metric_id"), Op.EQ, metric_id)], groupby=[Column("metric_id"), Column("tags.key")], referrer="snuba.metrics.meta.get_single_metric", ) if data: tag_ids = {tag_id for row in data for tag_id in row["tags.key"]} return { "name": metric_name, "type": metric_type, "operations": _AVAILABLE_OPERATIONS[entity_key.value], "tags": sorted( ({"key": reverse_resolve(tag_id)} for tag_id in tag_ids), key=itemgetter("key"), ), "unit": None, } raise InvalidParams
def resolve_metric(self, value: str) -> int: metric_id = indexer.resolve(constants.METRICS_MAP.get(value, value)) if metric_id is None: raise IncompatibleMetricsQuery(f"Metric: {value} could not be resolved") self.builder.metric_ids.append(metric_id) return metric_id
def _translate_conditions(org_id: int, input_: Any) -> Any: if isinstance(input_, Column): # The only filterable tag keys are release and environment. assert input_.name in ("release", "environment") # It greatly simplifies code if we just assume that they exist. # Alternative would be: # * if tag key or value does not exist in AND-clause, return no data # * if tag key or value does not exist in OR-clause, remove condition return Column(resolve_tag_key(input_.name)) if isinstance(input_, str): # Assuming this is the right-hand side, we need to fetch a tag value. # It's OK if the tag value resolves to None, the snuba query will then # return no results, as is intended behavior return indexer.resolve(input_) if isinstance(input_, Function): return Function(function=input_.function, parameters=_translate_conditions( org_id, input_.parameters)) if isinstance(input_, Condition): return Condition( lhs=_translate_conditions(org_id, input_.lhs), op=input_.op, rhs=_translate_conditions(org_id, input_.rhs), ) if isinstance(input_, (int, float)): return input_ assert isinstance(input_, (tuple, list)), input_ return [_translate_conditions(org_id, item) for item in input_]
def _resolve_failure_count( self, _: Mapping[str, Union[str, Column, SelectType, int, float]], alias: Optional[str] = None, ) -> SelectType: statuses = [ indexer.resolve(status) for status in constants.NON_FAILURE_STATUS ] return self._resolve_count_if( Function( "equals", [ Column("metric_id"), self.resolve_metric("transaction.duration"), ], ), Function( "notIn", [ self.builder.column("transaction.status"), list(status for status in statuses if status is not None), ], ), alias, )
def resolve_tag_value(string: str) -> int: resolved = indexer.resolve(string) if resolved is None: # This delegates the problem of dealing with missing tag values to # snuba return 0 return resolved
def resolve_metric(self, value: str) -> int: metric_id = indexer.resolve(constants.METRICS_MAP.get(value, value)) if metric_id is None: # TODO: unsure if this should be incompatible or invalid raise InvalidSearchQuery(f"Metric: {value} could not be resolved") self.builder.metric_ids.append(metric_id) return metric_id
def get_column_for_status(function_name: str, prefix: str, status: str) -> Function: return Function( f"{function_name}If", [ Column("value"), Function( "equals", [Column(tag_key_session_status), indexer.resolve(status)], ), ], alias=f"{prefix}_{status}", )
def resolve_weak(string: str) -> int: """ A version of `resolve` that returns 0 for missing values. When using `resolve_weak` to produce a WHERE-clause, it is quite useful to make the WHERE-clause "impossible" with `WHERE x = 0` instead of explicitly handling that exception. """ resolved = indexer.resolve(string) if resolved is None: return 0 return resolved # type: ignore
def _get_common_where( total: bool) -> List[Union[BooleanCondition, Condition]]: where_common: List[Union[BooleanCondition, Condition]] = [ Condition(Column("org_id"), Op.EQ, org_id), Condition(Column("project_id"), Op.IN, project_ids), Condition(Column("timestamp"), Op.GTE, start), Condition(Column("timestamp"), Op.LT, now), Condition(Column(tag_key(org_id, "session.status")), Op.EQ, tag_value(org_id, "init")), ] if environments is not None: environment_tag_values = [] for environment in environments: value = indexer.resolve(org_id, environment) # type: ignore if value is not None: environment_tag_values.append(value) where_common.append( Condition(Column(tag_key(org_id, "environment")), Op.IN, environment_tag_values)) if not total: release_tag_values = [] for _, release in project_releases: value = indexer.resolve(org_id, release) # type: ignore if value is not None: # We should not append the value if it hasn't been # observed before. release_tag_values.append(value) where_common.append( Condition(Column(tag_key(org_id, "release")), Op.IN, release_tag_values)) return where_common
def resolve_team_key_transaction_alias( builder: QueryBuilder, resolve_metric_index: bool = False ) -> SelectType: org_id = builder.params.get("organization_id") project_ids = builder.params.get("project_id") team_ids = builder.params.get("team_id") if org_id is None or team_ids is None or project_ids is None: raise TypeError("Team key transactions parameters cannot be None") team_key_transactions = list( TeamKeyTransaction.objects.filter( organization_id=org_id, project_team__in=ProjectTeam.objects.filter( project_id__in=project_ids, team_id__in=team_ids ), ) .order_by("transaction", "project_team__project_id") .values_list("project_team__project_id", "transaction") .distinct("transaction", "project_team__project_id")[ : fields.MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS ] ) count = len(team_key_transactions) if resolve_metric_index: team_key_transactions = [ (project, indexer.resolve(transaction)) for project, transaction in team_key_transactions ] # NOTE: this raw count is not 100% accurate because if it exceeds # `MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS`, it will not be reflected sentry_sdk.set_tag("team_key_txns.count", count) sentry_sdk.set_tag( "team_key_txns.count.grouped", format_grouped_length(count, [10, 100, 250, 500]) ) if count == 0: return Function("toInt8", [0], constants.TEAM_KEY_TRANSACTION_ALIAS) return Function( "in", [ (builder.column("project_id"), builder.column("transaction")), team_key_transactions, ], constants.TEAM_KEY_TRANSACTION_ALIAS, )
def _resolve_apdex_function( self, _: Mapping[str, Union[str, Column, SelectType, int, float]], alias: Optional[str] = None, ) -> SelectType: metric_true = indexer.resolve(constants.METRIC_TRUE_TAG_VALUE) # Nothing is satisfied or tolerated, the score must be 0 if metric_true is None: return Function( "toUInt64", [0], alias, ) satisfied = Function("equals", [ self.builder.column(constants.METRIC_SATISFIED_TAG_KEY), metric_true ]) tolerable = Function("equals", [ self.builder.column(constants.METRIC_TOLERATED_TAG_KEY), metric_true ]) metric_condition = Function( "equals", [Column("metric_id"), self.resolve_metric("transaction.duration")]) return Function( "divide", [ Function( "plus", [ self._resolve_count_if(metric_condition, satisfied), Function( "divide", [ self._resolve_count_if(metric_condition, tolerable), 2 ], ), ], ), Function("countIf", [metric_condition]), ], alias, )
def _get_metrics_filter( self, metric_names: Optional[Sequence[str]] ) -> Optional[List[Condition]]: """Add a condition to filter by metrics. Return None if a name cannot be resolved.""" where = [] if metric_names is not None: metric_ids = [] for name in metric_names: resolved = indexer.resolve(name) if resolved is None: # We are looking for tags that appear in all given metrics. # A tag cannot appear in a metric if the metric is not even indexed. return None metric_ids.append(resolved) where.append(Condition(Column("metric_id"), Op.IN, metric_ids)) return where
def _get_metrics_filter_ids(metric_names: Sequence[str]) -> Set[int]: """ Returns a set of metric_ids that map to input metric names and raises an exception if metric cannot be resolved in the indexer """ if not metric_names: return set() metric_ids = set() for name in metric_names: if name not in DERIVED_METRICS: metric_ids.add(indexer.resolve(name)) else: metric_ids |= DERIVED_METRICS[name].generate_metric_ids() if None in metric_ids: # We are looking for tags that appear in all given metrics. # A tag cannot appear in a metric if the metric is not even indexed. raise MetricDoesNotExistInIndexer() return metric_ids
def _resolve_web_vital_function( self, args: Mapping[str, Union[str, Column, SelectType, int, float]], alias: str, ) -> SelectType: column = args["column"] metric_id = args["metric_id"] quality = args["quality"].lower() if column not in [ "measurements.lcp", "measurements.fcp", "measurements.fp", "measurements.fid", "measurements.cls", ]: raise InvalidSearchQuery( "count_web_vitals only supports measurements") measurement_rating = self.builder.resolve_column("measurement_rating") quality_id = indexer.resolve(quality) if quality_id is None: return Function( # This matches the type from doing `select toTypeName(count()) ...` from clickhouse "toUInt64", [0], alias, ) return Function( "countIf", [ Column("value"), Function( "and", [ Function("equals", [measurement_rating, quality_id]), Function("equals", [Column("metric_id"), metric_id]), ], ), ], alias, )
def get_single_metric_info(projects: Sequence[Project], metric_name: str) -> MetricMetaWithTagKeys: assert projects metric_id = indexer.resolve(metric_name) if metric_id is None: raise InvalidParams for metric_type in ("counter", "set", "distribution"): # TODO: What if metric_id exists for multiple types / units? entity_key = METRIC_TYPE_TO_ENTITY[metric_type] data = run_metrics_query( entity_key=entity_key, select=[Column("metric_id"), Column("tags.key")], where=[Condition(Column("metric_id"), Op.EQ, metric_id)], groupby=[Column("metric_id"), Column("tags.key")], referrer="snuba.metrics.meta.get_single_metric", projects=projects, org_id=projects[0].organization_id, ) if data: tag_ids = {tag_id for row in data for tag_id in row["tags.key"]} return { "name": metric_name, "type": metric_type, "operations": AVAILABLE_OPERATIONS[entity_key.value], "tags": sorted( ({ "key": reverse_resolve(tag_id) } for tag_id in tag_ids), key=itemgetter("key"), ), "unit": None, } raise InvalidParams(f"Raw metric {metric_name} does not exit")
def _build_where( self, query_definition: QueryDefinition ) -> List[Union[BooleanCondition, Condition]]: where: List[Union[BooleanCondition, Condition]] = [ Condition(Column("org_id"), Op.EQ, self._project.organization_id), Condition(Column("project_id"), Op.EQ, self._project.id), Condition( Column("metric_id"), Op.IN, [indexer.resolve(name) for _, name in query_definition.fields.values()], ), Condition(Column(TS_COL_QUERY), Op.GTE, query_definition.start), Condition(Column(TS_COL_QUERY), Op.LT, query_definition.end), ] filter_ = self._build_filter(query_definition) if filter_: where.append(filter_) return where
def get_tag_values( self, tag_name: str, metric_names: Optional[Sequence[str]] ) -> Sequence[TagValue]: """Get all known values for a specific tag""" tag_id = indexer.resolve(tag_name) if tag_id is None: raise InvalidParams where = self._get_metrics_filter(metric_names) if where is None: return [] tags = defaultdict(list) column_name = f"tags[{tag_id}]" for metric_type in ("counter", "set", "distribution"): # TODO: What if metric_id exists for multiple types / units? entity_key = METRIC_TYPE_TO_ENTITY[metric_type] rows = self._get_data( entity_key=entity_key, select=[Column("metric_id"), Column(column_name)], where=where, groupby=[Column("metric_id"), Column(column_name)], referrer="snuba.metrics.meta.get_tag_values", ) for row in rows: value_id = row[column_name] if value_id > 0: metric_id = row["metric_id"] tags[metric_id].append(value_id) value_id_lists = tags.values() if metric_names is not None: # Only return tags that occur in all metrics value_ids = set.intersection(*[set(ids) for ids in value_id_lists]) else: value_ids = {value_id for ids in value_id_lists for value_id in ids} tags = [{"key": tag_name, "value": reverse_resolve(value_id)} for value_id in value_ids] tags.sort(key=itemgetter("key")) return tags
def _resolve_count_miserable_function( self, args: Mapping[str, Union[str, Column, SelectType, int, float]], alias: Optional[str] = None, ) -> SelectType: metric_true = indexer.resolve(constants.METRIC_TRUE_TAG_VALUE) # Nobody is miserable, we can return 0 if metric_true is None: return Function( "toUInt64", [0], alias, ) return Function( "uniqIf", [ Column("value"), Function( "and", [ Function( "equals", [ Column("metric_id"), args["metric_id"], ], ), Function( "equals", [ self.builder.column( constants.METRIC_MISERABLE_TAG_KEY), metric_true ], ), ], ), ], alias, )
def get_tag_values( projects: Sequence[Project], tag_name: str, metric_names: Optional[Sequence[str]] ) -> Sequence[TagValue]: """Get all known values for a specific tag""" assert projects tag_id = indexer.resolve(tag_name) if tag_id is None: raise InvalidParams(f"Tag {tag_name} is not available in the indexer") try: tags, _ = _fetch_tags_or_values_per_ids( projects=projects, column=f"tags[{tag_id}]", metric_names=metric_names, referrer="snuba.metrics.meta.get_tag_values", ) except InvalidParams: return [] return tags
def _get_entity_of_metric_name(projects: Sequence[Project], metric_name: str) -> EntityKey: assert projects metric_id = indexer.resolve(metric_name) if metric_id is None: raise InvalidParams for metric_type in ("counter", "set", "distribution"): entity_key = METRIC_TYPE_TO_ENTITY[metric_type] data = run_metrics_query( entity_key=entity_key, select=[Column("metric_id")], where=[Condition(Column("metric_id"), Op.EQ, metric_id)], groupby=[Column("metric_id")], referrer="snuba.metrics.meta.get_entity_of_metric", projects=projects, org_id=projects[0].organization_id, ) if data: return entity_key raise InvalidParams(f"Raw metric {metric_name} does not exit")
def tag_key(org_id: int, name: str) -> str: index = indexer.resolve(org_id, name) # type: ignore if index is None: raise MetricIndexNotFound(name) return f"tags[{index}]"
def _get_release_adoption_impl( now: datetime, org_id: int, project_releases: Sequence[Tuple[ProjectId, ReleaseName]], project_ids: Sequence[ProjectId], environments: Optional[Sequence[EnvironmentName]] = None, ) -> ReleasesAdoption: start = now - timedelta(days=1) def _get_common_where( total: bool) -> List[Union[BooleanCondition, Condition]]: where_common: List[Union[BooleanCondition, Condition]] = [ Condition(Column("org_id"), Op.EQ, org_id), Condition(Column("project_id"), Op.IN, project_ids), Condition(Column("timestamp"), Op.GTE, start), Condition(Column("timestamp"), Op.LT, now), Condition(Column(tag_key(org_id, "session.status")), Op.EQ, tag_value(org_id, "init")), ] if environments is not None: environment_tag_values = [] for environment in environments: value = indexer.resolve(org_id, environment) # type: ignore if value is not None: environment_tag_values.append(value) where_common.append( Condition(Column(tag_key(org_id, "environment")), Op.IN, environment_tag_values)) if not total: release_tag_values = [] for _, release in project_releases: value = indexer.resolve(org_id, release) # type: ignore if value is not None: # We should not append the value if it hasn't been # observed before. release_tag_values.append(value) where_common.append( Condition(Column(tag_key(org_id, "release")), Op.IN, release_tag_values)) return where_common def _get_common_groupby(total: bool) -> List[SelectableExpression]: if total: return [Column("project_id")] else: return [ Column("project_id"), Column(tag_key(org_id, "release")) ] def _convert_results(data: Any, total: bool) -> Dict[Any, int]: if total: return {x["project_id"]: x["value"] for x in data} else: release_tag = tag_key(org_id, "release") return {(x["project_id"], x[release_tag]): x["value"] for x in data} def _count_sessions(total: bool, referrer: str) -> Dict[Any, int]: query = Query( dataset=Dataset.Metrics.value, match=Entity(EntityKey.MetricsCounters.value), select=[Column("value")], where=_get_common_where(total) + [ Condition(Column("metric_id"), Op.EQ, metric_id(org_id, "session")), ], groupby=_get_common_groupby(total), ) return _convert_results( raw_snql_query( query, referrer=referrer, use_cache=False, )["data"], total=total, ) def _count_users(total: bool, referrer: str) -> Dict[Any, int]: query = Query( dataset=Dataset.Metrics.value, match=Entity(EntityKey.MetricsSets.value), select=[Column("value")], where=_get_common_where(total) + [ Condition(Column("metric_id"), Op.EQ, metric_id(org_id, "user")), ], groupby=_get_common_groupby(total), ) return _convert_results( raw_snql_query( query, referrer=referrer, use_cache=False, )["data"], total=total, ) # XXX(markus): Four queries are quite horrible for this... the old code # sufficed with two. From what I understand, ClickHouse would have to # gain a function uniqCombined64MergeIf, i.e. a conditional variant of # what we already use. # # Alternatively we may want to use a threadpool here to send the # queries in parallel. # NOTE: referrers are spelled out as single static string literal so # S&S folks can search for it more easily. No string formatting # business please! # Count of sessions/users for given list of environments and timerange, per-project sessions_per_project: Dict[int, int] = _count_sessions( total=True, referrer= "release_health.metrics.get_release_adoption.total_sessions") users_per_project: Dict[int, int] = _count_users( total=True, referrer="release_health.metrics.get_release_adoption.total_users") # Count of sessions/users for given list of environments and timerange AND GIVEN RELEASES, per-project sessions_per_release: Dict[Tuple[int, int], int] = _count_sessions( total=False, referrer= "release_health.metrics.get_release_adoption.releases_sessions") users_per_release: Dict[Tuple[int, int], int] = _count_users( total=False, referrer= "release_health.metrics.get_release_adoption.releases_users") rv = {} for project_id, release in project_releases: release_tag_value = indexer.resolve(org_id, release) # type: ignore if release_tag_value is None: # Don't emit empty releases -- for exact compatibility with # sessions table backend. continue release_sessions = sessions_per_release.get( (project_id, release_tag_value)) release_users = users_per_release.get( (project_id, release_tag_value)) total_sessions = sessions_per_project.get(project_id) total_users = users_per_project.get(project_id) adoption: ReleaseAdoption = { "adoption": float(release_users) / total_users * 100 if release_users and total_users else None, "sessions_adoption": float(release_sessions) / total_sessions * 100 if release_sessions and total_sessions else None, "users_24h": release_users, "sessions_24h": release_sessions, "project_users_24h": total_users, "project_sessions_24h": total_sessions, } rv[project_id, release] = adoption return rv
def to_int(string): try: return indexer.resolve(self._project.organization_id, string) except KeyError: return None
def _fetch_data_for_field( org_id: int, query: QueryDefinition, raw_field: SessionsQueryFunction, limit_state: _LimitState, columns_fetched: Set[SelectableExpression], # output param ) -> Tuple[_SnubaDataByMetric, MutableMapping[Tuple[ MetricKey, _VirtualColumnName], _OutputField]]: tag_key_session_status = resolve_tag_key("session.status") data: _SnubaDataByMetric = [] # Find the field that needs a specific column in a specific metric metric_to_output_field: MutableMapping[Tuple[MetricKey, _VirtualColumnName], _OutputField] = {} group_by_status = "session.status" in query.raw_groupby # We limit the number of groups returned, but because session status # groups in the response are actually composed of multiple groups in storage, # we need to make sure we get them all. For this, use conditional aggregates: def get_column_for_status(function_name: str, prefix: str, status: str) -> Function: return Function( f"{function_name}If", [ Column("value"), Function( "equals", [Column(tag_key_session_status), indexer.resolve(status)], ), ], alias=f"{prefix}_{status}", ) if "count_unique(user)" == raw_field: metric_id = indexer.resolve(MetricKey.USER.value) if metric_id is not None: if group_by_status: data.extend( _get_snuba_query_data( org_id, query, EntityKey.MetricsSets, MetricKey.USER, metric_id, [ # The order of these columns is important, because # the first column might get used in order by get_column_for_status("uniq", "users", "init"), get_column_for_status("uniq", "users", "abnormal"), get_column_for_status("uniq", "users", "crashed"), get_column_for_status("uniq", "users", "errored"), ], limit_state, )) else: data.extend( _get_snuba_query_data( org_id, query, EntityKey.MetricsSets, MetricKey.USER, metric_id, [Function("uniq", [Column("value")], "value")], limit_state, )) metric_to_output_field[(MetricKey.USER, "value")] = _UserField() if raw_field in _DURATION_FIELDS: metric_id = indexer.resolve(MetricKey.SESSION_DURATION.value) if metric_id is not None: def get_virtual_column( field: SessionsQueryFunction) -> _VirtualColumnName: return cast(_VirtualColumnName, field[:3]) # Filter down # to healthy sessions, because that's what sessions_v2 exposes: healthy = indexer.resolve("exited") if healthy is None: # There are no healthy sessions, return return [], {} column_condition = Function( "equals", (Column(tag_key_session_status), healthy)) snuba_column = _to_column(raw_field, column_condition) if snuba_column not in columns_fetched: data.extend( _get_snuba_query_data( org_id, query, EntityKey.MetricsDistributions, MetricKey.SESSION_DURATION, metric_id, [snuba_column], limit_state, )) columns_fetched.add(snuba_column) col = get_virtual_column(raw_field) metric_to_output_field[(MetricKey.SESSION_DURATION, col)] = _SessionDurationField( raw_field, col, group_by_status) if "sum(session)" == raw_field: metric_id = indexer.resolve(MetricKey.SESSION.value) if metric_id is not None: if group_by_status: # We need session counters grouped by status, as well as the number of errored sessions # 1 session counters data.extend( _get_snuba_query_data( org_id, query, EntityKey.MetricsCounters, MetricKey.SESSION, metric_id, [ # The order of these columns is important, because # the first column might get used in order by get_column_for_status("sum", "sessions", "init"), get_column_for_status("sum", "sessions", "abnormal"), get_column_for_status("sum", "sessions", "crashed"), get_column_for_status("sum", "sessions", "errored_preaggr"), ], limit_state, )) # 2: session.error error_metric_id = indexer.resolve( MetricKey.SESSION_ERROR.value) if error_metric_id is not None: # Should not limit session.error to session.status=X, # because that tag does not exist for this metric limit_state.skip_columns.add( Column(tag_key_session_status)) data.extend( _get_snuba_query_data( org_id, query, EntityKey.MetricsSets, MetricKey.SESSION_ERROR, error_metric_id, [Function("uniq", [Column("value")], "value")], limit_state, )) # Remove skip_column again: limit_state.skip_columns.remove( Column(tag_key_session_status)) else: # Simply count the number of started sessions: init = indexer.resolve("init") if tag_key_session_status is not None and init is not None: extra_conditions = [ Condition(Column(tag_key_session_status), Op.EQ, init) ] data.extend( _get_snuba_query_data( org_id, query, EntityKey.MetricsCounters, MetricKey.SESSION, metric_id, [Function("sum", [Column("value")], "value")], limit_state, extra_conditions, )) metric_to_output_field[(MetricKey.SESSION, "value")] = _SumSessionField() return data, metric_to_output_field
def to_int(string): try: return indexer.resolve(string) except KeyError: return None
def tag_value(org_id: int, name: str) -> int: index = indexer.resolve(org_id, name) # type: ignore if index is None: raise MetricIndexNotFound(name) return index # type: ignore
def to_int(string_type, string): try: return indexer.resolve(self._project.id, string_type, string) except KeyError: return None
def get_tag_values( projects: Sequence[Project], tag_name: str, metric_names: Optional[Sequence[str]]) -> Sequence[TagValue]: """Get all known values for a specific tag""" assert projects tag_id = indexer.resolve(tag_name) if tag_id is None: raise InvalidParams try: metric_ids = _get_metrics_filter_ids(metric_names) except MetricDoesNotExistInIndexer: return [] else: where = [Condition(Column("metric_id"), Op.IN, list(metric_ids)) ] if metric_ids else [] tag_values = defaultdict(list) # This dictionary is required as a mapping from an entity to the ids available in it to # validate that constituent metrics of a SingleEntityDerivedMetric actually span a single # entity by validating that the ids of the constituent metrics all lie in the same entity supported_metric_ids_in_entities = {} column_name = f"tags[{tag_id}]" for metric_type in ("counter", "set", "distribution"): supported_metric_ids_in_entities.setdefault(metric_type, []) entity_key = METRIC_TYPE_TO_ENTITY[metric_type] rows = run_metrics_query( entity_key=entity_key, select=[Column("metric_id"), Column(column_name)], where=where, groupby=[Column("metric_id"), Column(column_name)], referrer="snuba.metrics.meta.get_tag_values", projects=projects, org_id=projects[0].organization_id, ) for row in rows: value_id = row[column_name] supported_metric_ids_in_entities[metric_type].append( row["metric_id"]) if value_id > 0: metric_id = row["metric_id"] tag_values[metric_id].append(value_id) # If we are trying to find the tag values for only one metric name, then no need to query # other entities once we find data for that metric_name in one of the entities if metric_names and len(metric_names) == 1 and rows: break value_id_lists = tag_values.values() if metric_names is not None: if metric_ids != set(tag_values.keys()): return [] # At this point, we are sure that every metric_name/metric_id that was requested is # present in the dataset, and now we need to check that all derived metrics requested are # setup correctly _validate_requested_derived_metrics( metric_names=metric_names, supported_metric_ids_in_entities=supported_metric_ids_in_entities, ) # Only return tags that occur in all metrics value_ids = set.intersection(*[set(ids) for ids in value_id_lists]) else: value_ids = {value_id for ids in value_id_lists for value_id in ids} tags = [{ "key": tag_name, "value": reverse_resolve(value_id) } for value_id in value_ids] tags.sort(key=lambda tag: (tag["key"], tag["value"])) return tags
def try_get_string_index(org_id: int, name: str) -> Optional[int]: return indexer.resolve(org_id, name) # type: ignore
def _resolve(name: str) -> Optional[int]: """Wrapper for typing""" return indexer.resolve(name) # type: ignore