Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(metrics): Unify datetime format #2409

Merged
merged 15 commits into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 14 additions & 10 deletions sentry_sdk/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@
import random
import time
import zlib
from datetime import datetime
from functools import wraps, partial
from threading import Event, Lock, Thread

import sentry_sdk
from sentry_sdk._compat import text_type
from sentry_sdk.hub import Hub
from sentry_sdk.utils import now, nanosecond_time
from sentry_sdk.utils import now, nanosecond_time, to_timestamp
from sentry_sdk.envelope import Envelope, Item
from sentry_sdk.tracing import (
TRANSACTION_SOURCE_ROUTE,
Expand All @@ -27,6 +28,7 @@
from typing import Callable
from typing import Optional
from typing import Tuple
from typing import Union

Check warning on line 31 in sentry_sdk/metrics.py

View check run for this annotation

Codecov / codecov/patch

sentry_sdk/metrics.py#L31

Added line #L31 was not covered by tests

from sentry_sdk._types import BucketKey
from sentry_sdk._types import DurationUnit
Expand Down Expand Up @@ -382,7 +384,7 @@
value, # type: MetricValue
unit, # type: MeasurementUnit
tags, # type: Optional[MetricTags]
timestamp=None, # type: Optional[float]
timestamp=None, # type: Optional[Union[float, datetime]]
):
# type: (...) -> None
self._ensure_thread()
Expand All @@ -392,6 +394,8 @@

if timestamp is None:
timestamp = time.time()
elif isinstance(timestamp, datetime):
timestamp = to_timestamp(timestamp)

Check warning on line 398 in sentry_sdk/metrics.py

View check run for this annotation

Codecov / codecov/patch

sentry_sdk/metrics.py#L398

Added line #L398 was not covered by tests

bucket_timestamp = int(
(timestamp // self.ROLLUP_IN_SECONDS) * self.ROLLUP_IN_SECONDS
Expand Down Expand Up @@ -477,7 +481,7 @@
def _get_aggregator_and_update_tags(key, tags):
# type: (str, Optional[MetricTags]) -> Tuple[Optional[MetricsAggregator], Optional[MetricTags]]
"""Returns the current metrics aggregator if there is one."""
hub = Hub.current
hub = sentry_sdk.Hub.current

Check warning on line 484 in sentry_sdk/metrics.py

View check run for this annotation

Codecov / codecov/patch

sentry_sdk/metrics.py#L484

Added line #L484 was not covered by tests
client = hub.client
if client is None or client.metrics_aggregator is None:
return None, tags
Expand Down Expand Up @@ -506,7 +510,7 @@
value=1.0, # type: float
unit="none", # type: MeasurementUnit
tags=None, # type: Optional[MetricTags]
timestamp=None, # type: Optional[float]
timestamp=None, # type: Optional[Union[float, datetime]]
):
# type: (...) -> None
"""Increments a counter."""
Expand All @@ -520,7 +524,7 @@
self,
key, # type: str
tags, # type: Optional[MetricTags]
timestamp, # type: Optional[float]
timestamp, # type: Optional[Union[float, datetime]]
value, # type: Optional[float]
unit, # type: DurationUnit
):
Expand Down Expand Up @@ -572,7 +576,7 @@
value=None, # type: Optional[float]
unit="second", # type: DurationUnit
tags=None, # type: Optional[MetricTags]
timestamp=None, # type: Optional[float]
timestamp=None, # type: Optional[Union[float, datetime]]
):
# type: (...) -> _Timing
"""Emits a distribution with the time it takes to run the given code block.
Expand All @@ -595,7 +599,7 @@
value, # type: float
unit="none", # type: MeasurementUnit
tags=None, # type: Optional[MetricTags]
timestamp=None, # type: Optional[float]
timestamp=None, # type: Optional[Union[float, datetime]]
):
# type: (...) -> None
"""Emits a distribution."""
Expand All @@ -609,7 +613,7 @@
value, # type: MetricValue
unit="none", # type: MeasurementUnit
tags=None, # type: Optional[MetricTags]
timestamp=None, # type: Optional[float]
timestamp=None, # type: Optional[Union[float, datetime]]
):
# type: (...) -> None
"""Emits a set."""
Expand All @@ -623,7 +627,7 @@
value, # type: float
unit="none", # type: MetricValue
tags=None, # type: Optional[MetricTags]
timestamp=None, # type: Optional[float]
timestamp=None, # type: Optional[Union[float, datetime]]
):
# type: (...) -> None
"""Emits a gauge."""
Expand Down
28 changes: 26 additions & 2 deletions sentry_sdk/tracing.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from typing import List
from typing import Optional
from typing import Tuple
from typing import Union

Check warning on line 23 in sentry_sdk/tracing.py

View check run for this annotation

Codecov / codecov/patch

sentry_sdk/tracing.py#L23

Added line #L23 was not covered by tests

import sentry_sdk.profiler
from sentry_sdk._types import Event, MeasurementUnit, SamplingContext
Expand Down Expand Up @@ -101,6 +102,8 @@
"hub",
"_context_manager_state",
"_containing_transaction",
"emit_metric",
"metric_name",
)

def __new__(cls, **kwargs):
Expand Down Expand Up @@ -130,7 +133,9 @@
status=None, # type: Optional[str]
transaction=None, # type: Optional[str] # deprecated
containing_transaction=None, # type: Optional[Transaction]
start_timestamp=None, # type: Optional[datetime]
start_timestamp=None, # type: Optional[Union[datetime, float]]
emit_metric=False, # type: bool
metric_name=None, # type: Optional[str]
):
# type: (...) -> None
self.trace_id = trace_id or uuid.uuid4().hex
Expand All @@ -145,7 +150,11 @@
self._tags = {} # type: Dict[str, str]
self._data = {} # type: Dict[str, Any]
self._containing_transaction = containing_transaction
self.start_timestamp = start_timestamp or datetime.utcnow()
if start_timestamp is None:
start_timestamp = datetime.utcnow()
elif isinstance(start_timestamp, float):
start_timestamp = datetime.utcfromtimestamp(start_timestamp)

Check warning on line 156 in sentry_sdk/tracing.py

View check run for this annotation

Codecov / codecov/patch

sentry_sdk/tracing.py#L156

Added line #L156 was not covered by tests
self.start_timestamp = start_timestamp
try:
# profiling depends on this value and requires that
# it is measured in nanoseconds
Expand All @@ -156,6 +165,9 @@
#: End timestamp of span
self.timestamp = None # type: Optional[datetime]

self.emit_metric = emit_metric
self.metric_name = metric_name

self._span_recorder = None # type: Optional[_SpanRecorder]

# TODO this should really live on the Transaction class rather than the Span
Expand Down Expand Up @@ -471,6 +483,17 @@
except AttributeError:
self.timestamp = datetime.utcnow()

# If we were asked to emit a metric, do so now. We always record
# that timing in seconds.
if self.emit_metric:
metrics.timing(

Check warning on line 489 in sentry_sdk/tracing.py

View check run for this annotation

Codecov / codecov/patch

sentry_sdk/tracing.py#L489

Added line #L489 was not covered by tests
self.metric_name or ("span." + (self.op or "generic")),
timestamp=self.start_timestamp,
value=(self.timestamp - self.start_timestamp).total_seconds(),
unit="second",
tags=self._tags,
)

maybe_create_breadcrumbs_from_span(hub, self)
return None

Expand Down Expand Up @@ -995,3 +1018,4 @@
has_tracing_enabled,
maybe_create_breadcrumbs_from_span,
)
from sentry_sdk import metrics
36 changes: 35 additions & 1 deletion tests/test_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import time

from sentry_sdk import Hub, metrics, push_scope
from sentry_sdk import Hub, metrics, push_scope, start_span


def parse_metrics(bytes):
Expand Down Expand Up @@ -181,6 +181,40 @@ def test_timing_basic(sentry_init, capture_envelopes):
}


def test_timing_via_span(sentry_init, capture_envelopes):
sentry_init(
release="fun-release@1.0.0",
environment="not-fun-env",
_experiments={"enable_metrics": True},
)
ts = time.time()
envelopes = capture_envelopes()

with start_span(
op="whatever", start_timestamp=ts, emit_metric=True
) as span:
span.set_tag("blub", "blah")
time.sleep(0.1)
Hub.current.flush()

(envelope,) = envelopes

assert len(envelope.items) == 1
assert envelope.items[0].headers["type"] == "statsd"
m = parse_metrics(envelope.items[0].payload.get_bytes())

assert len(m) == 1
assert m[0][1] == "span.whatever@second"
assert m[0][2] == "d"
assert len(m[0][3]) == 1
assert float(m[0][3][0]) >= 0.1
assert m[0][4] == {
"blub": "blah",
"release": "fun-release@1.0.0",
"environment": "not-fun-env",
}


def test_distribution(sentry_init, capture_envelopes):
sentry_init(
release="fun-release@1.0.0",
Expand Down