Skip to content

Commit bed747d

Browse files
Rewrite utcnow usage (#801)
* Remove use of datetime.utcnow() * Fix test * Centralize datetime formatting for agent * Use isoformat after all * Test fixes * Possible fix * Update CHANGELOG --------- Co-authored-by: Adam Johnson <me@adamj.eu>
1 parent db6a1f0 commit bed747d

20 files changed

Lines changed: 76 additions & 45 deletions

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
## Pending
44

5+
- Change to tz-aware dates internally (Issue #799)
6+
- psutil dependency un-pin (#790)
7+
58
## [3.2.0] 2024-09-12
69
### Added
710
- "Operation" attribute added to TrackedRequest class to better support development of [scout_apm_python_logging](https://github.com/scoutapp/scout_apm_python_logging)

DEVELOPMENT.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ Running the test app
8585
--------------------
8686

8787
Note: this has not been tested in a while. Instead, the
88-
[scout-test-apps repo](https://github.com/tim-schilling/scout-test-apps) has
88+
[scout-test-apps repo](https://github.com/scoutapp/scout-test-apps) has
8989
been used with many individual scout apps.
9090

9191
Add the following env variables:
@@ -133,7 +133,6 @@ architectures. Its documentation is excellent.
133133
Documentation
134134
-------------
135135

136-
The user documentation is stored in the [slate_apm_help
137-
repo](https://github.com/scoutapp/slate_apm_help) in the
138-
`source/_includes/python.md` file. Make relevant changes there when developing
139-
features.
136+
The user documentation is stored in the [scout-documentation](https://github.com/scoutapp/scout-documentation) repo.
137+
It is private to the Scout Monitoring team; if you want to submit a feature feel free to
138+
send us some Markdown and we will get it added.

src/scout_apm/celery.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ def get_safe_settings():
2929

3030
def before_task_publish_callback(headers=None, properties=None, **kwargs):
3131
if "scout_task_start" not in headers:
32-
headers["scout_task_start"] = datetime_to_timestamp(dt.datetime.utcnow())
32+
headers["scout_task_start"] = datetime_to_timestamp(
33+
dt.datetime.now(dt.timezone.utc)
34+
)
3335

3436

3537
def task_prerun_callback(task=None, **kwargs):

src/scout_apm/core/agent/commands.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# coding=utf-8
22

3+
import datetime as dt
34
import logging
45
import re
56

@@ -10,6 +11,18 @@
1011
key_regex = re.compile(r"^[a-zA-Z0-9]{20}$")
1112

1213

14+
def format_dt_for_core_agent(event_time: dt.datetime) -> str:
15+
"""
16+
Returns expected format for Core Agent compatibility.
17+
Coerce any tz-aware datetime to UTC just in case.
18+
"""
19+
# if we somehow got a naive datetime, convert it to UTC
20+
if event_time.tzinfo is None:
21+
logger.warning("Naive datetime passed to format_dt_for_core_agent")
22+
event_time = event_time.astimezone(dt.timezone.utc)
23+
return event_time.isoformat()
24+
25+
1326
class Register(object):
1427
__slots__ = ("app", "key", "hostname")
1528

@@ -49,7 +62,7 @@ def __init__(self, timestamp, request_id, span_id, parent, operation):
4962
def message(self):
5063
return {
5164
"StartSpan": {
52-
"timestamp": self.timestamp.isoformat() + "Z",
65+
"timestamp": format_dt_for_core_agent(self.timestamp),
5366
"request_id": self.request_id,
5467
"span_id": self.span_id,
5568
"parent_id": self.parent,
@@ -69,7 +82,7 @@ def __init__(self, timestamp, request_id, span_id):
6982
def message(self):
7083
return {
7184
"StopSpan": {
72-
"timestamp": self.timestamp.isoformat() + "Z",
85+
"timestamp": format_dt_for_core_agent(self.timestamp),
7386
"request_id": self.request_id,
7487
"span_id": self.span_id,
7588
}
@@ -86,7 +99,7 @@ def __init__(self, timestamp, request_id):
8699
def message(self):
87100
return {
88101
"StartRequest": {
89-
"timestamp": self.timestamp.isoformat() + "Z",
102+
"timestamp": format_dt_for_core_agent(self.timestamp),
90103
"request_id": self.request_id,
91104
}
92105
}
@@ -102,7 +115,7 @@ def __init__(self, timestamp, request_id):
102115
def message(self):
103116
return {
104117
"FinishRequest": {
105-
"timestamp": self.timestamp.isoformat() + "Z",
118+
"timestamp": format_dt_for_core_agent(self.timestamp),
106119
"request_id": self.request_id,
107120
}
108121
}
@@ -121,7 +134,7 @@ def __init__(self, timestamp, request_id, span_id, tag, value):
121134
def message(self):
122135
return {
123136
"TagSpan": {
124-
"timestamp": self.timestamp.isoformat() + "Z",
137+
"timestamp": format_dt_for_core_agent(self.timestamp),
125138
"request_id": self.request_id,
126139
"span_id": self.span_id,
127140
"tag": self.tag,
@@ -142,7 +155,7 @@ def __init__(self, timestamp, request_id, tag, value):
142155
def message(self):
143156
return {
144157
"TagRequest": {
145-
"timestamp": self.timestamp.isoformat() + "Z",
158+
"timestamp": format_dt_for_core_agent(self.timestamp),
146159
"request_id": self.request_id,
147160
"tag": self.tag,
148161
"value": self.value,
@@ -162,7 +175,7 @@ def __init__(self, event_type, event_value, source, timestamp):
162175
def message(self):
163176
return {
164177
"ApplicationEvent": {
165-
"timestamp": self.timestamp.isoformat() + "Z",
178+
"timestamp": format_dt_for_core_agent(self.timestamp),
166179
"event_type": self.event_type,
167180
"event_value": self.event_value,
168181
"source": self.source,

src/scout_apm/core/metadata.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import sys
55
from os import getpid
66

7-
from scout_apm.core.agent.commands import ApplicationEvent
7+
from scout_apm.core.agent.commands import ApplicationEvent, format_dt_for_core_agent
88
from scout_apm.core.agent.socket import CoreAgentSocketThread
99
from scout_apm.core.config import scout_config
1010

@@ -15,7 +15,7 @@ def report_app_metadata():
1515
event_type="scout.metadata",
1616
event_value=get_metadata(),
1717
source="Pid: " + str(getpid()),
18-
timestamp=dt.datetime.utcnow(),
18+
timestamp=dt.datetime.now(dt.timezone.utc),
1919
)
2020
)
2121

@@ -24,7 +24,7 @@ def get_metadata():
2424
data = {
2525
"language": "python",
2626
"language_version": "{}.{}.{}".format(*sys.version_info[:3]),
27-
"server_time": dt.datetime.utcnow().isoformat() + "Z",
27+
"server_time": format_dt_for_core_agent(dt.datetime.now(dt.timezone.utc)),
2828
"framework": scout_config.value("framework"),
2929
"framework_version": scout_config.value("framework_version"),
3030
"environment": "",

src/scout_apm/core/queue_time.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ def track_job_queue_time(
8686
bool: Whether we succeeded in marking queue time for the job. Used for testing.
8787
"""
8888
if header_value is not None:
89-
now = datetime_to_timestamp(dt.datetime.utcnow()) * 1e9
89+
now = datetime_to_timestamp(dt.datetime.now(dt.timezone.utc)) * 1e9
9090
try:
9191
ambiguous_float_start = typing.cast(float, header_value)
9292
start = _convert_ambiguous_timestamp_to_ns(ambiguous_float_start)

src/scout_apm/core/samplers/cpu.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@ class Cpu(object):
1414
human_name = "Process CPU"
1515

1616
def __init__(self):
17-
self.last_run = dt.datetime.utcnow()
17+
self.last_run = dt.datetime.now(dt.timezone.utc)
1818
self.last_cpu_times = psutil.Process().cpu_times()
1919
self.num_processors = psutil.cpu_count()
2020
if self.num_processors is None:
2121
logger.debug("Could not determine CPU count - assuming there is one.")
2222
self.num_processors = 1
2323

2424
def run(self):
25-
now = dt.datetime.utcnow()
25+
now = dt.datetime.now(dt.timezone.utc)
2626
process = psutil.Process() # get a handle on the current process
2727
cpu_times = process.cpu_times()
2828

src/scout_apm/core/samplers/thread.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def run(self):
3030
event = ApplicationEvent(
3131
event_value=event_value,
3232
event_type=event_type,
33-
timestamp=dt.datetime.utcnow(),
33+
timestamp=dt.datetime.now(dt.timezone.utc),
3434
source="Pid: " + str(os.getpid()),
3535
)
3636
CoreAgentSocketThread.send(event)

src/scout_apm/core/tracked_request.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def instance(cls):
4949

5050
def __init__(self):
5151
self.request_id = "req-" + str(uuid4())
52-
self.start_time = dt.datetime.utcnow()
52+
self.start_time = dt.datetime.now(dt.timezone.utc)
5353
self.end_time = None
5454
self.active_spans = []
5555
self.complete_spans = []
@@ -147,7 +147,7 @@ def finish(self):
147147

148148
logger.debug("Stopping request: %s", self.request_id)
149149
if self.end_time is None:
150-
self.end_time = dt.datetime.utcnow()
150+
self.end_time = dt.datetime.now(dt.timezone.utc)
151151

152152
if self.is_real_request:
153153
self.tag("mem_delta", self._get_mem_delta())
@@ -219,7 +219,7 @@ def __init__(
219219
should_capture_backtrace=True,
220220
):
221221
self.span_id = "span-" + str(uuid4())
222-
self.start_time = dt.datetime.utcnow()
222+
self.start_time = dt.datetime.now(dt.timezone.utc)
223223
self.end_time = None
224224
self.request_id = request_id
225225
self.operation = operation
@@ -238,7 +238,7 @@ def __repr__(self):
238238
)
239239

240240
def stop(self):
241-
self.end_time = dt.datetime.utcnow()
241+
self.end_time = dt.datetime.now(dt.timezone.utc)
242242
self.end_objtrace_counts = objtrace.get_counts()
243243

244244
def tag(self, key, value):
@@ -254,7 +254,9 @@ def duration(self):
254254
return (self.end_time - self.start_time).total_seconds()
255255
else:
256256
# Current, running duration
257-
return (dt.datetime.utcnow() - self.start_time).total_seconds()
257+
return (
258+
dt.datetime.now(tz=dt.timezone.utc) - self.start_time
259+
).total_seconds()
258260

259261
# Add any interesting annotations to the span. Assumes that we are in the
260262
# process of stopping this span.

src/scout_apm/rq.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# coding=utf-8
22

33
import datetime as dt
4+
import logging
45

56
import wrapt
67
from rq import SimpleWorker as RqSimpleWorker
@@ -14,6 +15,8 @@
1415
install_attempted = False
1516
installed = None
1617

18+
logger = logging.getLogger(__name__)
19+
1720

1821
def ensure_scout_installed():
1922
global install_attempted, installed
@@ -65,7 +68,12 @@ def wrap_perform(wrapped, instance, args, kwargs):
6568
tracked_request.is_real_request = True
6669
tracked_request.tag("task_id", instance.get_id())
6770
tracked_request.tag("queue", instance.origin)
68-
queue_time = (dt.datetime.utcnow() - instance.enqueued_at).total_seconds()
71+
# rq strips tzinfo from enqueued_at during serde in at least some cases
72+
# internally everything uses UTC naive datetimes, so we operate on that
73+
# assumption here.
74+
if instance.enqueued_at.tzinfo is None:
75+
queued_at = instance.enqueued_at.replace(tzinfo=dt.timezone.utc)
76+
queue_time = (dt.datetime.now(dt.timezone.utc) - queued_at).total_seconds()
6977
tracked_request.tag("queue_time", queue_time)
7078
operation = "Job/{}".format(instance.func_name)
7179
tracked_request.operation = operation

0 commit comments

Comments
 (0)