Skip to content

Commit

Permalink
feat: Code locations for metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
mitsuhiko authored and jan-auer committed Nov 23, 2023
1 parent ea55387 commit 9a86bd0
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 7 deletions.
44 changes: 37 additions & 7 deletions sentry_sdk/metrics.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
import io
import re
import sys

Check warning on line 4 in sentry_sdk/metrics.py

View check run for this annotation

Codecov / codecov/patch

sentry_sdk/metrics.py#L4

Added line #L4 was not covered by tests
import threading
import random
import time
Expand Down Expand Up @@ -57,6 +58,19 @@
)


def get_code_location(stacklevel):
try:
frm = sys._getframe(stacklevel + 3)
except Exception:
return None
return {

Check warning on line 66 in sentry_sdk/metrics.py

View check run for this annotation

Codecov / codecov/patch

sentry_sdk/metrics.py#L61-L66

Added lines #L61 - L66 were not covered by tests
"line": frm.f_lineno,
"module": frm.f_globals.get("__name__"),
"filename": frm.f_code.co_filename,
"function": frm.f_code.co_name,
}


@contextmanager
def recursion_protection():
# type: () -> Generator[bool, None, None]
Expand Down Expand Up @@ -314,6 +328,7 @@ def __init__(
):
# type: (...) -> None
self.buckets = {} # type: Dict[int, Any]
self.code_locations = {} # type: Dict[BucketKey, Any]

Check warning on line 331 in sentry_sdk/metrics.py

View check run for this annotation

Codecov / codecov/patch

sentry_sdk/metrics.py#L331

Added line #L331 was not covered by tests
self._buckets_total_weight = 0
self._capture_func = capture_func
self._lock = Lock()
Expand Down Expand Up @@ -409,6 +424,7 @@ def add(
unit, # type: MeasurementUnit
tags, # type: Optional[MetricTags]
timestamp=None, # type: Optional[Union[float, datetime]]
stacklevel=1, # type: int
):
# type: (...) -> None
if not self._ensure_thread() or self._flusher is None:
Expand Down Expand Up @@ -441,6 +457,12 @@ def add(

self._buckets_total_weight += metric.weight - previous_weight

# Store code location once per bucket
if bucket_key not in self.code_locations:
loc = get_code_location(stacklevel)

Check warning on line 462 in sentry_sdk/metrics.py

View check run for this annotation

Codecov / codecov/patch

sentry_sdk/metrics.py#L462

Added line #L462 was not covered by tests
if loc is not None:
self.code_locations[bucket_key] = loc

Check warning on line 464 in sentry_sdk/metrics.py

View check run for this annotation

Codecov / codecov/patch

sentry_sdk/metrics.py#L464

Added line #L464 was not covered by tests

# Given the new weight we consider whether we want to force flush.
self._consider_force_flush()

Expand Down Expand Up @@ -536,6 +558,7 @@ def incr(
unit="none", # type: MeasurementUnit
tags=None, # type: Optional[MetricTags]
timestamp=None, # type: Optional[Union[float, datetime]]
stacklevel=1, # type: int
):
# type: (...) -> None
"""Increments a counter."""
Expand All @@ -552,6 +575,7 @@ def __init__(
timestamp, # type: Optional[Union[float, datetime]]
value, # type: Optional[float]
unit, # type: DurationUnit
stacklevel # type: int
):
# type: (...) -> None
self.key = key
Expand All @@ -560,6 +584,7 @@ def __init__(
self.value = value
self.unit = unit
self.entered = None # type: Optional[float]
self.stacklevel = stacklevel

Check warning on line 587 in sentry_sdk/metrics.py

View check run for this annotation

Codecov / codecov/patch

sentry_sdk/metrics.py#L587

Added line #L587 was not covered by tests

def _validate_invocation(self, context):
# type: (str) -> None
Expand All @@ -579,7 +604,7 @@ def __exit__(self, exc_type, exc_value, tb):
aggregator, tags = _get_aggregator_and_update_tags(self.key, self.tags)
if aggregator is not None:
elapsed = TIMING_FUNCTIONS[self.unit]() - self.entered # type: ignore
aggregator.add("d", self.key, elapsed, self.unit, tags, self.timestamp)
aggregator.add("d", self.key, elapsed, self.unit, tags, self.timestamp, self.stacklevel)

Check warning on line 607 in sentry_sdk/metrics.py

View check run for this annotation

Codecov / codecov/patch

sentry_sdk/metrics.py#L607

Added line #L607 was not covered by tests

def __call__(self, f):
# type: (Any) -> Any
Expand All @@ -589,7 +614,8 @@ def __call__(self, f):
def timed_func(*args, **kwargs):
# type: (*Any, **Any) -> Any
with timing(
key=self.key, tags=self.tags, timestamp=self.timestamp, unit=self.unit
key=self.key, tags=self.tags, timestamp=self.timestamp, unit=self.unit,
stacklevel=self.stacklevel + 1
):
return f(*args, **kwargs)

Expand All @@ -602,6 +628,7 @@ def timing(
unit="second", # type: DurationUnit
tags=None, # type: Optional[MetricTags]
timestamp=None, # type: Optional[Union[float, datetime]]
stacklevel=1, # type: int
):
# type: (...) -> _Timing
"""Emits a distribution with the time it takes to run the given code block.
Expand All @@ -615,8 +642,8 @@ def timing(
if value is not None:
aggregator, tags = _get_aggregator_and_update_tags(key, tags)
if aggregator is not None:
aggregator.add("d", key, value, unit, tags, timestamp)
return _Timing(key, tags, timestamp, value, unit)
aggregator.add("d", key, value, unit, tags, timestamp, stacklevel)
return _Timing(key, tags, timestamp, value, unit, stacklevel)

Check warning on line 646 in sentry_sdk/metrics.py

View check run for this annotation

Codecov / codecov/patch

sentry_sdk/metrics.py#L645-L646

Added lines #L645 - L646 were not covered by tests


def distribution(
Expand All @@ -625,12 +652,13 @@ def distribution(
unit="none", # type: MeasurementUnit
tags=None, # type: Optional[MetricTags]
timestamp=None, # type: Optional[Union[float, datetime]]
stacklevel=1, # type: int
):
# type: (...) -> None
"""Emits a distribution."""
aggregator, tags = _get_aggregator_and_update_tags(key, tags)
if aggregator is not None:
aggregator.add("d", key, value, unit, tags, timestamp)
aggregator.add("d", key, value, unit, tags, timestamp, stacklevel)

Check warning on line 661 in sentry_sdk/metrics.py

View check run for this annotation

Codecov / codecov/patch

sentry_sdk/metrics.py#L661

Added line #L661 was not covered by tests


def set(
Expand All @@ -639,12 +667,13 @@ def set(
unit="none", # type: MeasurementUnit
tags=None, # type: Optional[MetricTags]
timestamp=None, # type: Optional[Union[float, datetime]]
stacklevel=1, # type: int
):
# type: (...) -> None
"""Emits a set."""
aggregator, tags = _get_aggregator_and_update_tags(key, tags)
if aggregator is not None:
aggregator.add("s", key, value, unit, tags, timestamp)
aggregator.add("s", key, value, unit, tags, timestamp, stacklevel)

Check warning on line 676 in sentry_sdk/metrics.py

View check run for this annotation

Codecov / codecov/patch

sentry_sdk/metrics.py#L676

Added line #L676 was not covered by tests


def gauge(
Expand All @@ -653,9 +682,10 @@ def gauge(
unit="none", # type: MetricValue
tags=None, # type: Optional[MetricTags]
timestamp=None, # type: Optional[Union[float, datetime]]
stacklevel=1, # type: int
):
# type: (...) -> None
"""Emits a gauge."""
aggregator, tags = _get_aggregator_and_update_tags(key, tags)
if aggregator is not None:
aggregator.add("g", key, value, unit, tags, timestamp)
aggregator.add("g", key, value, unit, tags, timestamp, stacklevel)

Check warning on line 691 in sentry_sdk/metrics.py

View check run for this annotation

Codecov / codecov/patch

sentry_sdk/metrics.py#L691

Added line #L691 was not covered by tests
33 changes: 33 additions & 0 deletions tests/test_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,13 @@ def test_timing(sentry_init, capture_envelopes):
"environment": "not-fun-env",
}

loc = Hub.current.client.metrics_aggregator.code_locations
first_loc, = loc.values()
assert first_loc["filename"] == __file__
assert first_loc["line"] > 0
assert first_loc["module"] == "tests.test_metrics"
assert first_loc["function"] == "test_timing"


def test_timing_decorator(sentry_init, capture_envelopes):
sentry_init(
Expand Down Expand Up @@ -147,6 +154,18 @@ def amazing_nano():
"environment": "not-fun-env",
}

loc = Hub.current.client.metrics_aggregator.code_locations
assert len(loc) == 2
first_loc, second_loc = loc.values()
assert first_loc["filename"] == __file__
assert first_loc["line"] > 0
assert first_loc["module"] == "tests.test_metrics"
assert first_loc["function"] == "test_timing_decorator"
assert second_loc["filename"] == __file__
assert second_loc["line"] > 0
assert second_loc["module"] == "tests.test_metrics"
assert second_loc["function"] == "test_timing_decorator"


def test_timing_basic(sentry_init, capture_envelopes):
sentry_init(
Expand Down Expand Up @@ -180,6 +199,13 @@ def test_timing_basic(sentry_init, capture_envelopes):
"environment": "not-fun-env",
}

loc = Hub.current.client.metrics_aggregator.code_locations
first_loc, = loc.values()
assert first_loc["filename"] == __file__
assert first_loc["line"] > 0
assert first_loc["module"] == "tests.test_metrics"
assert first_loc["function"] == "test_timing_basic"


def test_distribution(sentry_init, capture_envelopes):
sentry_init(
Expand Down Expand Up @@ -504,6 +530,13 @@ def test_tag_serialization(sentry_init, capture_envelopes):
"environment": "not-fun-env",
}

loc = Hub.current.client.metrics_aggregator.code_locations
first_loc = next(iter(loc.values()))
assert first_loc["filename"] == __file__
assert first_loc["line"] > 0
assert first_loc["module"] == "tests.test_metrics"
assert first_loc["function"] == "test_tag_serialization"


def test_flush_recursion_protection(sentry_init, capture_envelopes, monkeypatch):
sentry_init(
Expand Down

0 comments on commit 9a86bd0

Please sign in to comment.