Skip to content

Commit 716ecaf

Browse files
authored
fix(insights): total tokens used column shows as decimal (#92319)
Due to extrapolation, it's possible for integer attribute types to come back as decimals when doing a sum aggregation on them. This cause decimal values when querying for `sum(ai.total_tokens.used)` which is incorrect. Ideally this is fixed on the rpc side, but we can still handle it properly on our end too. This Pr 1. Sets the meta of `ai.total_tokens.used` to `integer` which signals the frontend to format this value as an integer 2. Allows `integer` types to be used in `sum`, percentile, and other aggregates.
1 parent f206cf7 commit 716ecaf

File tree

4 files changed

+53
-8
lines changed

4 files changed

+53
-8
lines changed

src/sentry/search/eap/spans/aggregates.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,7 @@ def resolve_bounded_sample(args: ResolvedArguments) -> tuple[AttributeKey, Trace
372372
attribute_types={
373373
"duration",
374374
"number",
375+
"integer",
375376
*constants.SIZE_TYPE,
376377
*constants.DURATION_TYPE,
377378
},
@@ -388,6 +389,7 @@ def resolve_bounded_sample(args: ResolvedArguments) -> tuple[AttributeKey, Trace
388389
"duration",
389390
"number",
390391
"percentage",
392+
"integer",
391393
*constants.SIZE_TYPE,
392394
*constants.DURATION_TYPE,
393395
},
@@ -403,6 +405,7 @@ def resolve_bounded_sample(args: ResolvedArguments) -> tuple[AttributeKey, Trace
403405
attribute_types={
404406
"duration",
405407
"number",
408+
"integer",
406409
"percentage",
407410
*constants.SIZE_TYPE,
408411
*constants.DURATION_TYPE,
@@ -422,6 +425,7 @@ def resolve_bounded_sample(args: ResolvedArguments) -> tuple[AttributeKey, Trace
422425
attribute_types={
423426
"duration",
424427
"number",
428+
"integer",
425429
"percentage",
426430
*constants.SIZE_TYPE,
427431
*constants.DURATION_TYPE,
@@ -440,6 +444,7 @@ def resolve_bounded_sample(args: ResolvedArguments) -> tuple[AttributeKey, Trace
440444
attribute_types={
441445
"duration",
442446
"number",
447+
"integer",
443448
*constants.SIZE_TYPE,
444449
*constants.DURATION_TYPE,
445450
},
@@ -456,6 +461,7 @@ def resolve_bounded_sample(args: ResolvedArguments) -> tuple[AttributeKey, Trace
456461
attribute_types={
457462
"duration",
458463
"number",
464+
"integer",
459465
"percentage",
460466
*constants.SIZE_TYPE,
461467
*constants.DURATION_TYPE,
@@ -472,6 +478,7 @@ def resolve_bounded_sample(args: ResolvedArguments) -> tuple[AttributeKey, Trace
472478
attribute_types={
473479
"duration",
474480
"number",
481+
"integer",
475482
*constants.SIZE_TYPE,
476483
*constants.DURATION_TYPE,
477484
},
@@ -488,6 +495,7 @@ def resolve_bounded_sample(args: ResolvedArguments) -> tuple[AttributeKey, Trace
488495
attribute_types={
489496
"duration",
490497
"number",
498+
"integer",
491499
"percentage",
492500
*constants.SIZE_TYPE,
493501
*constants.DURATION_TYPE,
@@ -504,6 +512,7 @@ def resolve_bounded_sample(args: ResolvedArguments) -> tuple[AttributeKey, Trace
504512
attribute_types={
505513
"duration",
506514
"number",
515+
"integer",
507516
"percentage",
508517
*constants.SIZE_TYPE,
509518
*constants.DURATION_TYPE,
@@ -520,6 +529,7 @@ def resolve_bounded_sample(args: ResolvedArguments) -> tuple[AttributeKey, Trace
520529
attribute_types={
521530
"duration",
522531
"number",
532+
"integer",
523533
"percentage",
524534
*constants.SIZE_TYPE,
525535
*constants.DURATION_TYPE,
@@ -536,6 +546,7 @@ def resolve_bounded_sample(args: ResolvedArguments) -> tuple[AttributeKey, Trace
536546
attribute_types={
537547
"duration",
538548
"number",
549+
"integer",
539550
"percentage",
540551
*constants.SIZE_TYPE,
541552
*constants.DURATION_TYPE,
@@ -552,6 +563,7 @@ def resolve_bounded_sample(args: ResolvedArguments) -> tuple[AttributeKey, Trace
552563
attribute_types={
553564
"duration",
554565
"number",
566+
"integer",
555567
"percentage",
556568
*constants.SIZE_TYPE,
557569
*constants.DURATION_TYPE,
@@ -568,6 +580,7 @@ def resolve_bounded_sample(args: ResolvedArguments) -> tuple[AttributeKey, Trace
568580
attribute_types={
569581
"duration",
570582
"number",
583+
"integer",
571584
"percentage",
572585
*constants.SIZE_TYPE,
573586
*constants.DURATION_TYPE,
@@ -584,6 +597,7 @@ def resolve_bounded_sample(args: ResolvedArguments) -> tuple[AttributeKey, Trace
584597
attribute_types={
585598
"duration",
586599
"number",
600+
"integer",
587601
"percentage",
588602
*constants.SIZE_TYPE,
589603
*constants.DURATION_TYPE,

src/sentry/search/eap/spans/attributes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@
172172
ResolvedAttribute(
173173
public_alias="ai.total_tokens.used",
174174
internal_name="ai_total_tokens_used",
175-
search_type="number",
175+
search_type="integer",
176176
),
177177
ResolvedAttribute(
178178
public_alias="ai.total_cost",

tests/sentry/search/eap/test_spans.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
AttributeAggregation,
1010
AttributeKey,
1111
AttributeValue,
12-
DoubleArray,
1312
ExtrapolationMode,
1413
Function,
14+
IntArray,
1515
StrArray,
1616
VirtualColumnContext,
1717
)
@@ -63,9 +63,9 @@ def test_numeric_query(self):
6363
where, having, _ = self.resolver.resolve_query("ai.total_tokens.used:123")
6464
assert where == TraceItemFilter(
6565
comparison_filter=ComparisonFilter(
66-
key=AttributeKey(name="ai_total_tokens_used", type=AttributeKey.Type.TYPE_DOUBLE),
66+
key=AttributeKey(name="ai_total_tokens_used", type=AttributeKey.Type.TYPE_INT),
6767
op=ComparisonFilter.OP_EQUALS,
68-
value=AttributeValue(val_double=123),
68+
value=AttributeValue(val_int=123),
6969
)
7070
)
7171
assert having is None
@@ -111,9 +111,9 @@ def test_in_numeric_filter(self):
111111
where, having, _ = self.resolver.resolve_query("ai.total_tokens.used:[123,456,789]")
112112
assert where == TraceItemFilter(
113113
comparison_filter=ComparisonFilter(
114-
key=AttributeKey(name="ai_total_tokens_used", type=AttributeKey.Type.TYPE_DOUBLE),
114+
key=AttributeKey(name="ai_total_tokens_used", type=AttributeKey.Type.TYPE_INT),
115115
op=ComparisonFilter.OP_IN,
116-
value=AttributeValue(val_double_array=DoubleArray(values=[123, 456, 789])),
116+
value=AttributeValue(val_int_array=IntArray(values=[123, 456, 789])),
117117
)
118118
)
119119
assert having is None
@@ -122,9 +122,9 @@ def test_greater_than_numeric_filter(self):
122122
where, having, _ = self.resolver.resolve_query("ai.total_tokens.used:>123")
123123
assert where == TraceItemFilter(
124124
comparison_filter=ComparisonFilter(
125-
key=AttributeKey(name="ai_total_tokens_used", type=AttributeKey.Type.TYPE_DOUBLE),
125+
key=AttributeKey(name="ai_total_tokens_used", type=AttributeKey.Type.TYPE_INT),
126126
op=ComparisonFilter.OP_GREATER_THAN,
127-
value=AttributeValue(val_double=123),
127+
value=AttributeValue(val_int=123),
128128
)
129129
)
130130
assert having is None

tests/snuba/api/endpoints/test_organization_events_span_indexed.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4695,3 +4695,34 @@ def test_special_characters(self):
46954695
f"tag{c}": c,
46964696
}
46974697
]
4698+
4699+
def test_ai_total_tokens_returns_integer_meta(self):
4700+
self.store_spans(
4701+
[
4702+
self.create_span(
4703+
{"data": {"ai_total_tokens_used": 100}},
4704+
start_ts=self.ten_mins_ago,
4705+
),
4706+
self.create_span(
4707+
{"data": {"ai_total_tokens_used": 50}},
4708+
start_ts=self.ten_mins_ago,
4709+
),
4710+
],
4711+
is_eap=self.is_eap,
4712+
)
4713+
4714+
response = self.do_request(
4715+
{
4716+
"field": ["sum(ai.total_tokens.used)"],
4717+
"query": "",
4718+
"project": self.project.id,
4719+
"dataset": self.dataset,
4720+
}
4721+
)
4722+
assert response.status_code == 200, response.content
4723+
4724+
data, meta = response.data["data"], response.data["meta"]
4725+
assert len(data) == 1
4726+
assert data[0]["sum(ai.total_tokens.used)"] == 150
4727+
assert meta["dataset"] == self.dataset
4728+
assert meta["fields"]["sum(ai.total_tokens.used)"] == "integer"

0 commit comments

Comments
 (0)