Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions proto/sentry_protos/snuba/v1/trace_item.proto
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,17 @@ message KeyValue {
AnyValue value = 2;
}

message CategoryCount {
// DataCategory that defined in Relay
uint32 data_category = 1;
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should be an enum taken from the list of categories in the Relay package.

@Dav1dde should we extract this from Relay?

Copy link
Member Author

Choose a reason for hiding this comment

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

@phacops I was making the assumption we would validate the data category in the consumer itself

Copy link
Collaborator

Choose a reason for hiding this comment

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

It's fine. I guess we also don't have a list of DataCategory in protobuf so what I'd want to do is not really possible yet. We'd have to make the list in protobuf first then replace it everywhere it's used instead of using the Python class to generate it or something.

Copy link
Member Author

Choose a reason for hiding this comment

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

@Dav1dde can we get a rust crate for the relay consts? so we can import the consts like we do in other places in snuba

Copy link
Member

Choose a reason for hiding this comment

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

In Relay, for outcomes, the data category is actually typed as u8 which leaves us with about 220 more data categories we can invent.

Currently the source of truth for data categories lives in Sentry, but also all of Sentry (and Relay for that matter) is built in a way that it needs to be able to deal with unknown datacategories (arbitrary numbers), so we could follow this approach also here and just use a number.

I think setting up code generation in Relay, to import into sentry-protos creates a weird dependency graph where sentry-protos depends on some Relay artifcat but Relay also depends on sentry-protos to produce data.

Is changing from u8/uint32 to an enum in protobuf a breaking change? If not we can start out as a number, then think about moving the source of truth for a data category into sentry-protos or some other schema repo.

Copy link
Member

@Dav1dde Dav1dde Feb 11, 2026

Choose a reason for hiding this comment

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

Maybe I misunderstood the question, @MeredithAnya would you like to use the rust artifact/list of datacateories just in the consumer to have some typing or also here in the protos?

If it's just about the consumer, the consumer needs to be able to deal with unknown data categories, as we don't want to have to update the consumer before introducing a new data category.

Copy link
Member Author

@MeredithAnya MeredithAnya Feb 11, 2026

Choose a reason for hiding this comment

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

Maybe I misunderstood the question, @MeredithAnya would you like to use the rust artifact/list of datacateories just in the consumer to have some typing or also here in the protos?

@Dav1dde I was talking about the consumers. Right now I want to enforce that the data categories we are creating accepted outcomes for are in the right subset, in getsentry outcomes consumer they are using it like

from sentry.constants import DataCategory

MIN_CATEGORY = min(DataCategory).value
MAX_CATEGORY = max(DataCategory).value
IGNORED_CATEGORIES = {DataCategory.SESSION}

where in sentry's constants.py the DataCategory is

DataCategory = sentry_relay.consts.DataCategory

Obviously I'd be doing this differently but right now snuba only has the ability to do this in the python consumers/ api logic with https://pypi.org/project/sentry-relay/

uint64 quantity = 2;
}

message Outcomes {
repeated CategoryCount category_count = 1;
uint64 key_id = 2;
}

message TraceItem {
uint64 organization_id = 1;
uint64 project_id = 2;
Expand All @@ -47,4 +58,5 @@ message TraceItem {
uint32 retention_days = 100;
google.protobuf.Timestamp received = 101;
uint32 downsampled_retention_days = 102;
Outcomes outcomes = 110;
}
169 changes: 167 additions & 2 deletions rust/src/sentry_protos.snuba.v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,76 @@ pub struct ExistsFilter {
#[prost(message, optional, tag = "1")]
pub key: ::core::option::Option<AttributeKey>,
}
/// Filter that matches trace items where ANY attribute matches the given value.
/// Use this for searching across all attributes without specifying a key.
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct AnyAttributeFilter {
#[prost(enumeration = "any_attribute_filter::Op", tag = "1")]
pub op: i32,
#[prost(message, optional, tag = "2")]
pub value: ::core::option::Option<AttributeValue>,
#[prost(bool, tag = "3")]
pub ignore_case: bool,
/// Optional: Restrict search to specific attribute types.
/// If empty, searches all string-type attributes by default.
#[prost(enumeration = "attribute_key::Type", repeated, tag = "4")]
pub attribute_types: ::prost::alloc::vec::Vec<i32>,
}
/// Nested message and enum types in `AnyAttributeFilter`.
pub mod any_attribute_filter {
/// Restricted set of operations that make sense for all-attribute search
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
::prost::Enumeration
)]
#[repr(i32)]
pub enum Op {
Unspecified = 0,
Equals = 1,
NotEquals = 2,
Like = 3,
NotLike = 4,
In = 5,
NotIn = 6,
}
impl Op {
/// String value of the enum field names used in the ProtoBuf definition.
///
/// The values are not transformed in any way and thus are considered stable
/// (if the ProtoBuf definition does not change) and safe for programmatic use.
pub fn as_str_name(&self) -> &'static str {
match self {
Self::Unspecified => "OP_UNSPECIFIED",
Self::Equals => "OP_EQUALS",
Self::NotEquals => "OP_NOT_EQUALS",
Self::Like => "OP_LIKE",
Self::NotLike => "OP_NOT_LIKE",
Self::In => "OP_IN",
Self::NotIn => "OP_NOT_IN",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
match value {
"OP_UNSPECIFIED" => Some(Self::Unspecified),
"OP_EQUALS" => Some(Self::Equals),
"OP_NOT_EQUALS" => Some(Self::NotEquals),
"OP_LIKE" => Some(Self::Like),
"OP_NOT_LIKE" => Some(Self::NotLike),
"OP_IN" => Some(Self::In),
"OP_NOT_IN" => Some(Self::NotIn),
_ => None,
}
}
}
}
/// a condition used to filter for matching "trace items"
///
/// ex: "exists span.duration" would mean
Expand All @@ -499,7 +569,7 @@ pub struct ExistsFilter {
/// trace items contain attributes like 'span.duration' )
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct TraceItemFilter {
#[prost(oneof = "trace_item_filter::Value", tags = "1, 2, 3, 4, 5")]
#[prost(oneof = "trace_item_filter::Value", tags = "1, 2, 3, 4, 5, 6")]
pub value: ::core::option::Option<trace_item_filter::Value>,
}
/// Nested message and enum types in `TraceItemFilter`.
Expand All @@ -516,6 +586,8 @@ pub mod trace_item_filter {
ComparisonFilter(super::ComparisonFilter),
#[prost(message, tag = "5")]
ExistsFilter(super::ExistsFilter),
#[prost(message, tag = "6")]
AnyAttributeFilter(super::AnyAttributeFilter),
}
}
#[derive(Clone, PartialEq, ::prost::Message)]
Expand Down Expand Up @@ -1975,7 +2047,7 @@ pub mod aggregation_filter {
pub struct Column {
#[prost(string, tag = "3")]
pub label: ::prost::alloc::string::String,
#[prost(oneof = "column::Column", tags = "1, 2, 5, 4, 6")]
#[prost(oneof = "column::Column", tags = "1, 2, 5, 4, 6, 7")]
pub column: ::core::option::Option<column::Column>,
}
/// Nested message and enum types in `Column`.
Expand Down Expand Up @@ -2050,6 +2122,80 @@ pub mod column {
DefaultValueInt64(i64),
}
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct FormulaCondition {
#[prost(message, optional, boxed, tag = "1")]
pub left: ::core::option::Option<::prost::alloc::boxed::Box<super::Column>>,
#[prost(enumeration = "formula_condition::Op", tag = "2")]
pub op: i32,
#[prost(message, optional, boxed, tag = "3")]
pub right: ::core::option::Option<::prost::alloc::boxed::Box<super::Column>>,
}
/// Nested message and enum types in `FormulaCondition`.
pub mod formula_condition {
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
::prost::Enumeration
)]
#[repr(i32)]
pub enum Op {
Unspecified = 0,
LessThan = 1,
GreaterThan = 2,
LessThanOrEquals = 3,
GreaterThanOrEquals = 4,
Equals = 5,
NotEquals = 6,
}
impl Op {
/// String value of the enum field names used in the ProtoBuf definition.
///
/// The values are not transformed in any way and thus are considered stable
/// (if the ProtoBuf definition does not change) and safe for programmatic use.
pub fn as_str_name(&self) -> &'static str {
match self {
Self::Unspecified => "OP_UNSPECIFIED",
Self::LessThan => "OP_LESS_THAN",
Self::GreaterThan => "OP_GREATER_THAN",
Self::LessThanOrEquals => "OP_LESS_THAN_OR_EQUALS",
Self::GreaterThanOrEquals => "OP_GREATER_THAN_OR_EQUALS",
Self::Equals => "OP_EQUALS",
Self::NotEquals => "OP_NOT_EQUALS",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
match value {
"OP_UNSPECIFIED" => Some(Self::Unspecified),
"OP_LESS_THAN" => Some(Self::LessThan),
"OP_GREATER_THAN" => Some(Self::GreaterThan),
"OP_LESS_THAN_OR_EQUALS" => Some(Self::LessThanOrEquals),
"OP_GREATER_THAN_OR_EQUALS" => Some(Self::GreaterThanOrEquals),
"OP_EQUALS" => Some(Self::Equals),
"OP_NOT_EQUALS" => Some(Self::NotEquals),
_ => None,
}
}
}
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ConditionalFormula {
#[prost(message, optional, boxed, tag = "1")]
pub condition: ::core::option::Option<
::prost::alloc::boxed::Box<FormulaCondition>,
>,
#[prost(message, optional, boxed, tag = "2")]
pub r#match: ::core::option::Option<::prost::alloc::boxed::Box<super::Column>>,
#[prost(message, optional, boxed, tag = "3")]
pub default: ::core::option::Option<::prost::alloc::boxed::Box<super::Column>>,
}
#[derive(Clone, PartialEq, ::prost::Oneof)]
pub enum Column {
#[prost(message, tag = "1")]
Expand All @@ -2062,6 +2208,8 @@ pub mod column {
Formula(::prost::alloc::boxed::Box<BinaryFormula>),
#[prost(message, tag = "6")]
Literal(super::Literal),
#[prost(message, tag = "7")]
ConditionalFormula(::prost::alloc::boxed::Box<ConditionalFormula>),
}
}
#[derive(Clone, PartialEq, ::prost::Message)]
Expand Down Expand Up @@ -2145,6 +2293,21 @@ pub struct KeyValue {
#[prost(message, optional, tag = "2")]
pub value: ::core::option::Option<AnyValue>,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
pub struct CategoryCount {
/// DataCategory that defined in Relay
#[prost(uint32, tag = "1")]
pub data_category: u32,
#[prost(uint64, tag = "2")]
pub quantity: u64,
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Outcomes {
#[prost(message, repeated, tag = "1")]
pub category_count: ::prost::alloc::vec::Vec<CategoryCount>,
#[prost(uint64, tag = "2")]
pub key_id: u64,
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct TraceItem {
#[prost(uint64, tag = "1")]
Expand Down Expand Up @@ -2176,6 +2339,8 @@ pub struct TraceItem {
pub received: ::core::option::Option<::prost_types::Timestamp>,
#[prost(uint32, tag = "102")]
pub downsampled_retention_days: u32,
#[prost(message, optional, tag = "110")]
pub outcomes: ::core::option::Option<Outcomes>,
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ExportTraceItemsRequest {
Expand Down
Loading