scout-apm 3.1.0__cp39-cp39-macosx_10_9_x86_64.whl → 3.3.0__cp39-cp39-macosx_10_9_x86_64.whl
Sign up to get free protection for your applications and to get access to all the features.
- scout_apm/api/__init__.py +1 -0
- scout_apm/async_/starlette.py +6 -5
- scout_apm/bottle.py +3 -3
- scout_apm/celery.py +6 -2
- scout_apm/core/_objtrace.cpython-39-darwin.so +0 -0
- scout_apm/core/agent/commands.py +20 -7
- scout_apm/core/config.py +122 -29
- scout_apm/core/metadata.py +3 -3
- scout_apm/core/queue_time.py +1 -1
- scout_apm/core/sampler.py +149 -0
- scout_apm/core/samplers/cpu.py +2 -2
- scout_apm/core/samplers/thread.py +1 -1
- scout_apm/core/tracked_request.py +23 -7
- scout_apm/core/web_requests.py +1 -1
- scout_apm/django/middleware.py +1 -0
- scout_apm/dramatiq.py +3 -1
- scout_apm/falcon.py +1 -0
- scout_apm/flask/__init__.py +1 -0
- scout_apm/huey.py +1 -0
- scout_apm/rq.py +12 -3
- {scout_apm-3.1.0.dist-info → scout_apm-3.3.0.dist-info}/METADATA +19 -7
- {scout_apm-3.1.0.dist-info → scout_apm-3.3.0.dist-info}/RECORD +26 -25
- {scout_apm-3.1.0.dist-info → scout_apm-3.3.0.dist-info}/WHEEL +1 -1
- {scout_apm-3.1.0.dist-info → scout_apm-3.3.0.dist-info}/LICENSE +0 -0
- {scout_apm-3.1.0.dist-info → scout_apm-3.3.0.dist-info}/entry_points.txt +0 -0
- {scout_apm-3.1.0.dist-info → scout_apm-3.3.0.dist-info}/top_level.txt +0 -0
scout_apm/api/__init__.py
CHANGED
@@ -97,6 +97,7 @@ class Transaction(AsyncDecoratorMixin, ContextDecorator):
|
|
97
97
|
operation = text(kind) + "/" + text(name)
|
98
98
|
|
99
99
|
tracked_request = TrackedRequest.instance()
|
100
|
+
tracked_request.operation = operation
|
100
101
|
tracked_request.is_real_request = True
|
101
102
|
span = tracked_request.start_span(
|
102
103
|
operation=operation, should_capture_backtrace=False
|
scout_apm/async_/starlette.py
CHANGED
@@ -40,6 +40,7 @@ class ScoutMiddleware:
|
|
40
40
|
endpoint.__module__,
|
41
41
|
endpoint.__qualname__,
|
42
42
|
)
|
43
|
+
tracked_request.operation = controller_span.operation
|
43
44
|
else:
|
44
45
|
# Mark the request as not real
|
45
46
|
tracked_request.is_real_request = False
|
@@ -90,11 +91,11 @@ def install_background_instrumentation():
|
|
90
91
|
tracked_request = TrackedRequest.instance()
|
91
92
|
tracked_request.is_real_request = True
|
92
93
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
):
|
94
|
+
operation = "Job/{}.{}".format(
|
95
|
+
instance.func.__module__, instance.func.__qualname__
|
96
|
+
)
|
97
|
+
tracked_request.operation = operation
|
98
|
+
with tracked_request.span(operation=operation):
|
98
99
|
return await wrapped(*args, **kwargs)
|
99
100
|
|
100
101
|
BackgroundTask.__call__ = wrapped_background_call(BackgroundTask.__call__)
|
scout_apm/bottle.py
CHANGED
@@ -71,10 +71,10 @@ def wrap_callback(wrapped, instance, args, kwargs):
|
|
71
71
|
"x-request-start", ""
|
72
72
|
)
|
73
73
|
track_request_queue_time(queue_time, tracked_request)
|
74
|
+
operation = "Controller{}".format(controller_name)
|
74
75
|
|
75
|
-
with tracked_request.span(
|
76
|
-
operation=
|
77
|
-
):
|
76
|
+
with tracked_request.span(operation=operation):
|
77
|
+
tracked_request.operation = operation
|
78
78
|
try:
|
79
79
|
value = wrapped(*args, **kwargs)
|
80
80
|
except Exception:
|
scout_apm/celery.py
CHANGED
@@ -29,7 +29,9 @@ logger = logging.getLogger(__name__)
|
|
29
29
|
|
30
30
|
def before_task_publish_callback(headers=None, properties=None, **kwargs):
|
31
31
|
if "scout_task_start" not in headers:
|
32
|
-
headers["scout_task_start"] = datetime_to_timestamp(
|
32
|
+
headers["scout_task_start"] = datetime_to_timestamp(
|
33
|
+
dt.datetime.now(dt.timezone.utc)
|
34
|
+
)
|
33
35
|
|
34
36
|
|
35
37
|
def task_prerun_callback(task=None, **kwargs):
|
@@ -54,7 +56,9 @@ def task_prerun_callback(task=None, **kwargs):
|
|
54
56
|
tracked_request.tag("routing_key", delivery_info.get("routing_key", "unknown"))
|
55
57
|
tracked_request.tag("queue", delivery_info.get("queue", "unknown"))
|
56
58
|
|
57
|
-
|
59
|
+
operation = "Job/" + task.name
|
60
|
+
tracked_request.start_span(operation=operation)
|
61
|
+
tracked_request.operation = operation
|
58
62
|
|
59
63
|
|
60
64
|
def task_postrun_callback(task=None, **kwargs):
|
Binary file
|
scout_apm/core/agent/commands.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# coding=utf-8
|
2
2
|
|
3
|
+
import datetime as dt
|
3
4
|
import logging
|
4
5
|
import re
|
5
6
|
|
@@ -10,6 +11,18 @@ logger = logging.getLogger(__name__)
|
|
10
11
|
key_regex = re.compile(r"^[a-zA-Z0-9]{20}$")
|
11
12
|
|
12
13
|
|
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
|
+
|
13
26
|
class Register(object):
|
14
27
|
__slots__ = ("app", "key", "hostname")
|
15
28
|
|
@@ -49,7 +62,7 @@ class StartSpan(object):
|
|
49
62
|
def message(self):
|
50
63
|
return {
|
51
64
|
"StartSpan": {
|
52
|
-
"timestamp": self.timestamp
|
65
|
+
"timestamp": format_dt_for_core_agent(self.timestamp),
|
53
66
|
"request_id": self.request_id,
|
54
67
|
"span_id": self.span_id,
|
55
68
|
"parent_id": self.parent,
|
@@ -69,7 +82,7 @@ class StopSpan(object):
|
|
69
82
|
def message(self):
|
70
83
|
return {
|
71
84
|
"StopSpan": {
|
72
|
-
"timestamp": self.timestamp
|
85
|
+
"timestamp": format_dt_for_core_agent(self.timestamp),
|
73
86
|
"request_id": self.request_id,
|
74
87
|
"span_id": self.span_id,
|
75
88
|
}
|
@@ -86,7 +99,7 @@ class StartRequest(object):
|
|
86
99
|
def message(self):
|
87
100
|
return {
|
88
101
|
"StartRequest": {
|
89
|
-
"timestamp": self.timestamp
|
102
|
+
"timestamp": format_dt_for_core_agent(self.timestamp),
|
90
103
|
"request_id": self.request_id,
|
91
104
|
}
|
92
105
|
}
|
@@ -102,7 +115,7 @@ class FinishRequest(object):
|
|
102
115
|
def message(self):
|
103
116
|
return {
|
104
117
|
"FinishRequest": {
|
105
|
-
"timestamp": self.timestamp
|
118
|
+
"timestamp": format_dt_for_core_agent(self.timestamp),
|
106
119
|
"request_id": self.request_id,
|
107
120
|
}
|
108
121
|
}
|
@@ -121,7 +134,7 @@ class TagSpan(object):
|
|
121
134
|
def message(self):
|
122
135
|
return {
|
123
136
|
"TagSpan": {
|
124
|
-
"timestamp": self.timestamp
|
137
|
+
"timestamp": format_dt_for_core_agent(self.timestamp),
|
125
138
|
"request_id": self.request_id,
|
126
139
|
"span_id": self.span_id,
|
127
140
|
"tag": self.tag,
|
@@ -142,7 +155,7 @@ class TagRequest(object):
|
|
142
155
|
def message(self):
|
143
156
|
return {
|
144
157
|
"TagRequest": {
|
145
|
-
"timestamp": self.timestamp
|
158
|
+
"timestamp": format_dt_for_core_agent(self.timestamp),
|
146
159
|
"request_id": self.request_id,
|
147
160
|
"tag": self.tag,
|
148
161
|
"value": self.value,
|
@@ -162,7 +175,7 @@ class ApplicationEvent(object):
|
|
162
175
|
def message(self):
|
163
176
|
return {
|
164
177
|
"ApplicationEvent": {
|
165
|
-
"timestamp": self.timestamp
|
178
|
+
"timestamp": format_dt_for_core_agent(self.timestamp),
|
166
179
|
"event_type": self.event_type,
|
167
180
|
"event_value": self.event_value,
|
168
181
|
"source": self.source,
|
scout_apm/core/config.py
CHANGED
@@ -4,6 +4,7 @@ import logging
|
|
4
4
|
import os
|
5
5
|
import re
|
6
6
|
import warnings
|
7
|
+
from typing import Any, Dict, List, Optional, Union
|
7
8
|
|
8
9
|
from scout_apm.core import platform_detection
|
9
10
|
|
@@ -30,13 +31,13 @@ class ScoutConfig(object):
|
|
30
31
|
Null(),
|
31
32
|
]
|
32
33
|
|
33
|
-
def value(self, key):
|
34
|
+
def value(self, key: str) -> Any:
|
34
35
|
value = self.locate_layer_for_key(key).value(key)
|
35
36
|
if key in CONVERSIONS:
|
36
37
|
return CONVERSIONS[key](value)
|
37
38
|
return value
|
38
39
|
|
39
|
-
def locate_layer_for_key(self, key):
|
40
|
+
def locate_layer_for_key(self, key: str) -> Any:
|
40
41
|
for layer in self.layers:
|
41
42
|
if layer.has_config(key):
|
42
43
|
return layer
|
@@ -44,7 +45,7 @@ class ScoutConfig(object):
|
|
44
45
|
# Should be unreachable because Null returns None for all keys.
|
45
46
|
raise ValueError("key {!r} not found in any layer".format(key))
|
46
47
|
|
47
|
-
def log(self):
|
48
|
+
def log(self) -> None:
|
48
49
|
logger.debug("Configuration Loaded:")
|
49
50
|
for key in self.known_keys:
|
50
51
|
if key in self.secret_keys:
|
@@ -76,13 +77,20 @@ class ScoutConfig(object):
|
|
76
77
|
"framework",
|
77
78
|
"framework_version",
|
78
79
|
"hostname",
|
79
|
-
"ignore",
|
80
|
+
"ignore", # Deprecated in favor of ignore_endpoints
|
81
|
+
"ignore_endpoints",
|
82
|
+
"ignore_jobs",
|
80
83
|
"key",
|
81
84
|
"log_level",
|
82
85
|
"log_payload_content",
|
83
86
|
"monitor",
|
84
87
|
"name",
|
85
88
|
"revision_sha",
|
89
|
+
"sample_rate",
|
90
|
+
"endpoint_sample_rate",
|
91
|
+
"sample_endpoints",
|
92
|
+
"sample_jobs",
|
93
|
+
"job_sample_rate",
|
86
94
|
"scm_subdirectory",
|
87
95
|
"shutdown_message_enabled",
|
88
96
|
"shutdown_timeout_seconds",
|
@@ -90,7 +98,7 @@ class ScoutConfig(object):
|
|
90
98
|
|
91
99
|
secret_keys = {"key"}
|
92
100
|
|
93
|
-
def core_agent_permissions(self):
|
101
|
+
def core_agent_permissions(self) -> int:
|
94
102
|
try:
|
95
103
|
return int(str(self.value("core_agent_permissions")), 8)
|
96
104
|
except ValueError:
|
@@ -100,7 +108,7 @@ class ScoutConfig(object):
|
|
100
108
|
return 0o700
|
101
109
|
|
102
110
|
@classmethod
|
103
|
-
def set(cls, **kwargs):
|
111
|
+
def set(cls, **kwargs: Any) -> None:
|
104
112
|
"""
|
105
113
|
Sets a configuration value for the Scout agent. Values set here will
|
106
114
|
not override values set in ENV.
|
@@ -109,7 +117,7 @@ class ScoutConfig(object):
|
|
109
117
|
SCOUT_PYTHON_VALUES[key] = value
|
110
118
|
|
111
119
|
@classmethod
|
112
|
-
def unset(cls, *keys):
|
120
|
+
def unset(cls, *keys: str) -> None:
|
113
121
|
"""
|
114
122
|
Removes a configuration value for the Scout agent.
|
115
123
|
"""
|
@@ -117,7 +125,7 @@ class ScoutConfig(object):
|
|
117
125
|
SCOUT_PYTHON_VALUES.pop(key, None)
|
118
126
|
|
119
127
|
@classmethod
|
120
|
-
def reset_all(cls):
|
128
|
+
def reset_all(cls) -> None:
|
121
129
|
"""
|
122
130
|
Remove all configuration settings set via `ScoutConfig.set(...)`.
|
123
131
|
|
@@ -135,10 +143,10 @@ class Python(object):
|
|
135
143
|
A configuration overlay that lets other parts of python set values.
|
136
144
|
"""
|
137
145
|
|
138
|
-
def has_config(self, key):
|
146
|
+
def has_config(self, key: str) -> bool:
|
139
147
|
return key in SCOUT_PYTHON_VALUES
|
140
148
|
|
141
|
-
def value(self, key):
|
149
|
+
def value(self, key: str) -> Any:
|
142
150
|
return SCOUT_PYTHON_VALUES[key]
|
143
151
|
|
144
152
|
|
@@ -151,15 +159,15 @@ class Env(object):
|
|
151
159
|
environment variable
|
152
160
|
"""
|
153
161
|
|
154
|
-
def has_config(self, key):
|
162
|
+
def has_config(self, key: str) -> bool:
|
155
163
|
env_key = self.modify_key(key)
|
156
164
|
return env_key in os.environ
|
157
165
|
|
158
|
-
def value(self, key):
|
166
|
+
def value(self, key: str) -> Any:
|
159
167
|
env_key = self.modify_key(key)
|
160
168
|
return os.environ[env_key]
|
161
169
|
|
162
|
-
def modify_key(self, key):
|
170
|
+
def modify_key(self, key: str) -> str:
|
163
171
|
env_key = ("SCOUT_" + key).upper()
|
164
172
|
return env_key
|
165
173
|
|
@@ -169,27 +177,27 @@ class Derived(object):
|
|
169
177
|
A configuration overlay that calculates from other values.
|
170
178
|
"""
|
171
179
|
|
172
|
-
def __init__(self, config):
|
180
|
+
def __init__(self, config: ScoutConfig):
|
173
181
|
"""
|
174
182
|
config argument is the overall ScoutConfig var, so we can lookup the
|
175
183
|
components of the derived info.
|
176
184
|
"""
|
177
185
|
self.config = config
|
178
186
|
|
179
|
-
def has_config(self, key):
|
187
|
+
def has_config(self, key: str) -> bool:
|
180
188
|
return self.lookup_func(key) is not None
|
181
189
|
|
182
|
-
def value(self, key):
|
190
|
+
def value(self, key: str) -> Any:
|
183
191
|
return self.lookup_func(key)()
|
184
192
|
|
185
|
-
def lookup_func(self, key):
|
193
|
+
def lookup_func(self, key: str) -> Optional[Any]:
|
186
194
|
"""
|
187
195
|
Returns the derive_#{key} function, or None if it isn't defined
|
188
196
|
"""
|
189
197
|
func_name = "derive_" + key
|
190
198
|
return getattr(self, func_name, None)
|
191
199
|
|
192
|
-
def derive_core_agent_full_name(self):
|
200
|
+
def derive_core_agent_full_name(self) -> str:
|
193
201
|
triple = self.config.value("core_agent_triple")
|
194
202
|
if not platform_detection.is_valid_triple(triple):
|
195
203
|
warnings.warn(
|
@@ -201,7 +209,7 @@ class Derived(object):
|
|
201
209
|
triple=triple,
|
202
210
|
)
|
203
211
|
|
204
|
-
def derive_core_agent_triple(self):
|
212
|
+
def derive_core_agent_triple(self) -> str:
|
205
213
|
return platform_detection.get_triple()
|
206
214
|
|
207
215
|
|
@@ -223,7 +231,10 @@ class Defaults(object):
|
|
223
231
|
"core_agent_socket_path": "tcp://127.0.0.1:6590",
|
224
232
|
"core_agent_version": "v1.5.0", # can be an exact tag name, or 'latest'
|
225
233
|
"disabled_instruments": [],
|
226
|
-
"download_url":
|
234
|
+
"download_url": (
|
235
|
+
"https://s3-us-west-1.amazonaws.com/scout-public-downloads/"
|
236
|
+
"apm_core_agent/release"
|
237
|
+
), # noqa: B950
|
227
238
|
"errors_batch_size": 5,
|
228
239
|
"errors_enabled": True,
|
229
240
|
"errors_ignored_exceptions": (),
|
@@ -231,26 +242,34 @@ class Defaults(object):
|
|
231
242
|
"framework": "",
|
232
243
|
"framework_version": "",
|
233
244
|
"hostname": None,
|
245
|
+
"ignore": [],
|
246
|
+
"ignore_endpoints": [],
|
247
|
+
"ignore_jobs": [],
|
234
248
|
"key": "",
|
235
249
|
"log_payload_content": False,
|
236
250
|
"monitor": False,
|
237
251
|
"name": "Python App",
|
238
252
|
"revision_sha": self._git_revision_sha(),
|
253
|
+
"sample_rate": 100,
|
254
|
+
"sample_endpoints": [],
|
255
|
+
"endpoint_sample_rate": None,
|
256
|
+
"sample_jobs": [],
|
257
|
+
"job_sample_rate": None,
|
239
258
|
"scm_subdirectory": "",
|
240
259
|
"shutdown_message_enabled": True,
|
241
260
|
"shutdown_timeout_seconds": 2.0,
|
242
261
|
"uri_reporting": "filtered_params",
|
243
262
|
}
|
244
263
|
|
245
|
-
def _git_revision_sha(self):
|
264
|
+
def _git_revision_sha(self) -> str:
|
246
265
|
# N.B. The environment variable SCOUT_REVISION_SHA may also be used,
|
247
266
|
# but that will be picked up by Env
|
248
267
|
return os.environ.get("HEROKU_SLUG_COMMIT", "")
|
249
268
|
|
250
|
-
def has_config(self, key):
|
269
|
+
def has_config(self, key: str) -> bool:
|
251
270
|
return key in self.defaults
|
252
271
|
|
253
|
-
def value(self, key):
|
272
|
+
def value(self, key: str) -> Any:
|
254
273
|
return self.defaults[key]
|
255
274
|
|
256
275
|
|
@@ -261,14 +280,18 @@ class Null(object):
|
|
261
280
|
Used as the last step of the layered configuration.
|
262
281
|
"""
|
263
282
|
|
264
|
-
def has_config(self, key):
|
283
|
+
def has_config(self, key: str) -> bool:
|
265
284
|
return True
|
266
285
|
|
267
|
-
def value(self, key):
|
286
|
+
def value(self, key: str) -> None:
|
268
287
|
return None
|
269
288
|
|
270
289
|
|
271
|
-
def
|
290
|
+
def _strip_leading_slash(path: str) -> str:
|
291
|
+
return path.lstrip(" /").strip()
|
292
|
+
|
293
|
+
|
294
|
+
def convert_to_bool(value: Any) -> bool:
|
272
295
|
if isinstance(value, bool):
|
273
296
|
return value
|
274
297
|
if isinstance(value, str):
|
@@ -277,14 +300,38 @@ def convert_to_bool(value):
|
|
277
300
|
return False
|
278
301
|
|
279
302
|
|
280
|
-
def convert_to_float(value):
|
303
|
+
def convert_to_float(value: Any) -> float:
|
281
304
|
try:
|
282
305
|
return float(value)
|
283
306
|
except ValueError:
|
284
307
|
return 0.0
|
285
308
|
|
286
309
|
|
287
|
-
def
|
310
|
+
def convert_sample_rate(value: Any) -> Optional[int]:
|
311
|
+
"""
|
312
|
+
Converts sample rate to integer, ensuring it's between 0 and 100.
|
313
|
+
Allows None as a valid value.
|
314
|
+
"""
|
315
|
+
if value is None:
|
316
|
+
return None
|
317
|
+
try:
|
318
|
+
rate = int(value)
|
319
|
+
if not (0 <= rate <= 100):
|
320
|
+
logger.warning(
|
321
|
+
f"Invalid sample rate {rate}. Must be between 0 and 100. "
|
322
|
+
"Defaulting to 100."
|
323
|
+
)
|
324
|
+
return 100
|
325
|
+
return rate
|
326
|
+
except (TypeError, ValueError):
|
327
|
+
logger.warning(
|
328
|
+
f"Invalid sample rate {value}. Must be a number between 0 and 100. "
|
329
|
+
"Defaulting to 100."
|
330
|
+
)
|
331
|
+
return 100
|
332
|
+
|
333
|
+
|
334
|
+
def convert_to_list(value: Any) -> List[Any]:
|
288
335
|
if isinstance(value, list):
|
289
336
|
return value
|
290
337
|
if isinstance(value, tuple):
|
@@ -296,13 +343,59 @@ def convert_to_list(value):
|
|
296
343
|
return []
|
297
344
|
|
298
345
|
|
346
|
+
def convert_ignore_paths(value: Any) -> List[str]:
|
347
|
+
"""
|
348
|
+
Removes leading slashes from paths and returns a list of strings.
|
349
|
+
"""
|
350
|
+
raw_paths = convert_to_list(value)
|
351
|
+
return [_strip_leading_slash(path) for path in raw_paths]
|
352
|
+
|
353
|
+
|
354
|
+
def convert_endpoint_sampling(value: Union[str, Dict[str, Any]]) -> Dict[str, int]:
|
355
|
+
"""
|
356
|
+
Converts endpoint sampling configuration from string or dict format
|
357
|
+
to a normalized dict.
|
358
|
+
Example: '/endpoint:40,/test:0' -> {'/endpoint': 40, '/test': 0}
|
359
|
+
"""
|
360
|
+
if isinstance(value, dict):
|
361
|
+
return {_strip_leading_slash(k): int(v) for k, v in value.items()}
|
362
|
+
if isinstance(value, str):
|
363
|
+
if not value.strip():
|
364
|
+
return {}
|
365
|
+
result = {}
|
366
|
+
pairs = [pair.strip() for pair in value.split(",")]
|
367
|
+
for pair in pairs:
|
368
|
+
try:
|
369
|
+
endpoint, rate = pair.split(":")
|
370
|
+
rate_int = int(rate)
|
371
|
+
if not (0 <= rate_int <= 100):
|
372
|
+
logger.warning(
|
373
|
+
f"Invalid sampling rate {rate} for endpoint {endpoint}. "
|
374
|
+
"Must be between 0 and 100."
|
375
|
+
)
|
376
|
+
continue
|
377
|
+
result[_strip_leading_slash(endpoint)] = rate_int
|
378
|
+
except ValueError:
|
379
|
+
logger.warning(f"Invalid sampling configuration: {pair}")
|
380
|
+
continue
|
381
|
+
return result
|
382
|
+
return {}
|
383
|
+
|
384
|
+
|
299
385
|
CONVERSIONS = {
|
300
386
|
"collect_remote_ip": convert_to_bool,
|
301
387
|
"core_agent_download": convert_to_bool,
|
302
388
|
"core_agent_launch": convert_to_bool,
|
303
389
|
"disabled_instruments": convert_to_list,
|
304
|
-
"ignore":
|
390
|
+
"ignore": convert_ignore_paths,
|
391
|
+
"ignore_endpoints": convert_ignore_paths,
|
392
|
+
"ignore_jobs": convert_ignore_paths,
|
305
393
|
"monitor": convert_to_bool,
|
394
|
+
"sample_rate": convert_sample_rate,
|
395
|
+
"sample_endpoints": convert_endpoint_sampling,
|
396
|
+
"endpoint_sample_rate": convert_sample_rate,
|
397
|
+
"sample_jobs": convert_endpoint_sampling,
|
398
|
+
"job_sample_rate": convert_sample_rate,
|
306
399
|
"shutdown_message_enabled": convert_to_bool,
|
307
400
|
"shutdown_timeout_seconds": convert_to_float,
|
308
401
|
}
|
scout_apm/core/metadata.py
CHANGED
@@ -4,7 +4,7 @@ import datetime as dt
|
|
4
4
|
import sys
|
5
5
|
from os import getpid
|
6
6
|
|
7
|
-
from scout_apm.core.agent.commands import ApplicationEvent
|
7
|
+
from scout_apm.core.agent.commands import ApplicationEvent, format_dt_for_core_agent
|
8
8
|
from scout_apm.core.agent.socket import CoreAgentSocketThread
|
9
9
|
from scout_apm.core.config import scout_config
|
10
10
|
|
@@ -15,7 +15,7 @@ def report_app_metadata():
|
|
15
15
|
event_type="scout.metadata",
|
16
16
|
event_value=get_metadata(),
|
17
17
|
source="Pid: " + str(getpid()),
|
18
|
-
timestamp=dt.datetime.
|
18
|
+
timestamp=dt.datetime.now(dt.timezone.utc),
|
19
19
|
)
|
20
20
|
)
|
21
21
|
|
@@ -24,7 +24,7 @@ def get_metadata():
|
|
24
24
|
data = {
|
25
25
|
"language": "python",
|
26
26
|
"language_version": "{}.{}.{}".format(*sys.version_info[:3]),
|
27
|
-
"server_time": dt.datetime.
|
27
|
+
"server_time": format_dt_for_core_agent(dt.datetime.now(dt.timezone.utc)),
|
28
28
|
"framework": scout_config.value("framework"),
|
29
29
|
"framework_version": scout_config.value("framework_version"),
|
30
30
|
"environment": "",
|
scout_apm/core/queue_time.py
CHANGED
@@ -86,7 +86,7 @@ def track_job_queue_time(
|
|
86
86
|
bool: Whether we succeeded in marking queue time for the job. Used for testing.
|
87
87
|
"""
|
88
88
|
if header_value is not None:
|
89
|
-
now = datetime_to_timestamp(dt.datetime.
|
89
|
+
now = datetime_to_timestamp(dt.datetime.now(dt.timezone.utc)) * 1e9
|
90
90
|
try:
|
91
91
|
ambiguous_float_start = typing.cast(float, header_value)
|
92
92
|
start = _convert_ambiguous_timestamp_to_ns(ambiguous_float_start)
|
@@ -0,0 +1,149 @@
|
|
1
|
+
# coding=utf-8
|
2
|
+
|
3
|
+
import random
|
4
|
+
from typing import Dict, Optional, Tuple
|
5
|
+
|
6
|
+
|
7
|
+
class Sampler:
|
8
|
+
"""
|
9
|
+
Handles sampling decision logic for Scout APM.
|
10
|
+
|
11
|
+
This class encapsulates all sampling-related functionality including:
|
12
|
+
- Loading and managing sampling configuration
|
13
|
+
- Pattern matching for operations (endpoints and jobs)
|
14
|
+
- Making sampling decisions based on operation type and patterns
|
15
|
+
"""
|
16
|
+
|
17
|
+
# Constants for operation type detection
|
18
|
+
CONTROLLER_PREFIX = "Controller/"
|
19
|
+
JOB_PREFIX = "Job/"
|
20
|
+
|
21
|
+
def __init__(self, config):
|
22
|
+
"""
|
23
|
+
Initialize sampler with Scout configuration.
|
24
|
+
|
25
|
+
Args:
|
26
|
+
config: ScoutConfig instance containing sampling configuration
|
27
|
+
"""
|
28
|
+
self.config = config
|
29
|
+
self.sample_rate = config.value("sample_rate")
|
30
|
+
self.sample_endpoints = config.value("sample_endpoints")
|
31
|
+
self.sample_jobs = config.value("sample_jobs")
|
32
|
+
self.ignore_endpoints = set(
|
33
|
+
config.value("ignore_endpoints") + config.value("ignore")
|
34
|
+
)
|
35
|
+
self.ignore_jobs = set(config.value("ignore_jobs"))
|
36
|
+
self.endpoint_sample_rate = config.value("endpoint_sample_rate")
|
37
|
+
self.job_sample_rate = config.value("job_sample_rate")
|
38
|
+
|
39
|
+
def _any_sampling(self):
|
40
|
+
"""
|
41
|
+
Check if any sampling is enabled.
|
42
|
+
|
43
|
+
Returns:
|
44
|
+
Boolean indicating if any sampling is enabled
|
45
|
+
"""
|
46
|
+
return (
|
47
|
+
self.sample_rate < 100
|
48
|
+
or self.sample_endpoints
|
49
|
+
or self.sample_jobs
|
50
|
+
or self.ignore_endpoints
|
51
|
+
or self.ignore_jobs
|
52
|
+
or self.endpoint_sample_rate is not None
|
53
|
+
or self.job_sample_rate is not None
|
54
|
+
)
|
55
|
+
|
56
|
+
def _find_matching_rate(
|
57
|
+
self, name: str, patterns: Dict[str, float]
|
58
|
+
) -> Optional[str]:
|
59
|
+
"""
|
60
|
+
Finds the matching sample rate for a given operation name.
|
61
|
+
|
62
|
+
Args:
|
63
|
+
name: The operation name to match
|
64
|
+
patterns: Dictionary of pattern to sample rate mappings
|
65
|
+
|
66
|
+
Returns:
|
67
|
+
The sample rate for the matching pattern or None if no match found
|
68
|
+
"""
|
69
|
+
|
70
|
+
for pattern, rate in patterns.items():
|
71
|
+
if name.startswith(pattern):
|
72
|
+
return rate
|
73
|
+
return None
|
74
|
+
|
75
|
+
def _get_operation_type_and_name(
|
76
|
+
self, operation: str
|
77
|
+
) -> Tuple[Optional[str], Optional[str]]:
|
78
|
+
"""
|
79
|
+
Determines if an operation is an endpoint or job and extracts its name.
|
80
|
+
|
81
|
+
Args:
|
82
|
+
operation: The full operation string (e.g. "Controller/users/show")
|
83
|
+
|
84
|
+
Returns:
|
85
|
+
Tuple of (type, name) where type is either 'endpoint' or 'job',
|
86
|
+
and name is the operation name without the prefix
|
87
|
+
"""
|
88
|
+
if operation.startswith(self.CONTROLLER_PREFIX):
|
89
|
+
return "endpoint", operation[len(self.CONTROLLER_PREFIX) :]
|
90
|
+
elif operation.startswith(self.JOB_PREFIX):
|
91
|
+
return "job", operation[len(self.JOB_PREFIX) :]
|
92
|
+
else:
|
93
|
+
return None, None
|
94
|
+
|
95
|
+
def get_effective_sample_rate(self, operation: str, is_ignored: bool) -> int:
|
96
|
+
"""
|
97
|
+
Determines the effective sample rate for a given operation.
|
98
|
+
|
99
|
+
Prioritization:
|
100
|
+
1. Sampling rate for specific endpoint or job
|
101
|
+
2. Specified ignore pattern or flag for operation
|
102
|
+
3. Global endpoint or job sample rate
|
103
|
+
4. Global sample rate
|
104
|
+
|
105
|
+
Args:
|
106
|
+
operation: The operation string (e.g. "Controller/users/show")
|
107
|
+
is_ignored: boolean for if the specific transaction is ignored
|
108
|
+
|
109
|
+
Returns:
|
110
|
+
Integer between 0 and 100 representing sample rate
|
111
|
+
"""
|
112
|
+
op_type, name = self._get_operation_type_and_name(operation)
|
113
|
+
patterns = self.sample_endpoints if op_type == "endpoint" else self.sample_jobs
|
114
|
+
ignores = self.ignore_endpoints if op_type == "endpoint" else self.ignore_jobs
|
115
|
+
default_operation_rate = (
|
116
|
+
self.endpoint_sample_rate if op_type == "endpoint" else self.job_sample_rate
|
117
|
+
)
|
118
|
+
|
119
|
+
if not op_type or not name:
|
120
|
+
return self.sample_rate
|
121
|
+
matching_rate = self._find_matching_rate(name, patterns)
|
122
|
+
if matching_rate is not None:
|
123
|
+
return matching_rate
|
124
|
+
for prefix in ignores:
|
125
|
+
if name.startswith(prefix) or is_ignored:
|
126
|
+
return 0
|
127
|
+
if default_operation_rate is not None:
|
128
|
+
return default_operation_rate
|
129
|
+
|
130
|
+
# Fall back to global sample rate
|
131
|
+
return self.sample_rate
|
132
|
+
|
133
|
+
def should_sample(self, operation: str, is_ignored: bool) -> bool:
|
134
|
+
"""
|
135
|
+
Determines if an operation should be sampled.
|
136
|
+
If no sampling is enabled, always return True.
|
137
|
+
|
138
|
+
Args:
|
139
|
+
operation: The operation string (e.g. "Controller/users/show"
|
140
|
+
or "Job/mailer")
|
141
|
+
|
142
|
+
Returns:
|
143
|
+
Boolean indicating whether to sample this operation
|
144
|
+
"""
|
145
|
+
if not self._any_sampling():
|
146
|
+
return True
|
147
|
+
return random.randint(1, 100) <= self.get_effective_sample_rate(
|
148
|
+
operation, is_ignored
|
149
|
+
)
|
scout_apm/core/samplers/cpu.py
CHANGED
@@ -14,7 +14,7 @@ class Cpu(object):
|
|
14
14
|
human_name = "Process CPU"
|
15
15
|
|
16
16
|
def __init__(self):
|
17
|
-
self.last_run = dt.datetime.
|
17
|
+
self.last_run = dt.datetime.now(dt.timezone.utc)
|
18
18
|
self.last_cpu_times = psutil.Process().cpu_times()
|
19
19
|
self.num_processors = psutil.cpu_count()
|
20
20
|
if self.num_processors is None:
|
@@ -22,7 +22,7 @@ class Cpu(object):
|
|
22
22
|
self.num_processors = 1
|
23
23
|
|
24
24
|
def run(self):
|
25
|
-
now = dt.datetime.
|
25
|
+
now = dt.datetime.now(dt.timezone.utc)
|
26
26
|
process = psutil.Process() # get a handle on the current process
|
27
27
|
cpu_times = process.cpu_times()
|
28
28
|
|
@@ -30,7 +30,7 @@ class SamplersThread(SingletonThread):
|
|
30
30
|
event = ApplicationEvent(
|
31
31
|
event_value=event_value,
|
32
32
|
event_type=event_type,
|
33
|
-
timestamp=dt.datetime.
|
33
|
+
timestamp=dt.datetime.now(dt.timezone.utc),
|
34
34
|
source="Pid: " + str(os.getpid()),
|
35
35
|
)
|
36
36
|
CoreAgentSocketThread.send(event)
|
@@ -10,6 +10,7 @@ from scout_apm.core.agent.commands import BatchCommand
|
|
10
10
|
from scout_apm.core.agent.socket import CoreAgentSocketThread
|
11
11
|
from scout_apm.core.config import scout_config
|
12
12
|
from scout_apm.core.n_plus_one_tracker import NPlusOneTracker
|
13
|
+
from scout_apm.core.sampler import Sampler
|
13
14
|
from scout_apm.core.samplers.memory import get_rss_in_mb
|
14
15
|
from scout_apm.core.samplers.thread import SamplersThread
|
15
16
|
|
@@ -23,7 +24,16 @@ class TrackedRequest(object):
|
|
23
24
|
their keyname
|
24
25
|
"""
|
25
26
|
|
27
|
+
_sampler = None
|
28
|
+
|
29
|
+
@classmethod
|
30
|
+
def get_sampler(cls):
|
31
|
+
if cls._sampler is None:
|
32
|
+
cls._sampler = Sampler(scout_config)
|
33
|
+
return cls._sampler
|
34
|
+
|
26
35
|
__slots__ = (
|
36
|
+
"sampler",
|
27
37
|
"request_id",
|
28
38
|
"start_time",
|
29
39
|
"end_time",
|
@@ -35,6 +45,7 @@ class TrackedRequest(object):
|
|
35
45
|
"n_plus_one_tracker",
|
36
46
|
"hit_max",
|
37
47
|
"sent",
|
48
|
+
"operation",
|
38
49
|
)
|
39
50
|
|
40
51
|
# Stop adding new spans at this point, to avoid exhausting memory
|
@@ -48,7 +59,7 @@ class TrackedRequest(object):
|
|
48
59
|
|
49
60
|
def __init__(self):
|
50
61
|
self.request_id = "req-" + str(uuid4())
|
51
|
-
self.start_time = dt.datetime.
|
62
|
+
self.start_time = dt.datetime.now(dt.timezone.utc)
|
52
63
|
self.end_time = None
|
53
64
|
self.active_spans = []
|
54
65
|
self.complete_spans = []
|
@@ -58,6 +69,7 @@ class TrackedRequest(object):
|
|
58
69
|
self.n_plus_one_tracker = NPlusOneTracker()
|
59
70
|
self.hit_max = False
|
60
71
|
self.sent = False
|
72
|
+
self.operation = None
|
61
73
|
logger.debug("Starting request: %s", self.request_id)
|
62
74
|
|
63
75
|
def __repr__(self):
|
@@ -145,11 +157,13 @@ class TrackedRequest(object):
|
|
145
157
|
|
146
158
|
logger.debug("Stopping request: %s", self.request_id)
|
147
159
|
if self.end_time is None:
|
148
|
-
self.end_time = dt.datetime.
|
160
|
+
self.end_time = dt.datetime.now(dt.timezone.utc)
|
149
161
|
|
150
162
|
if self.is_real_request:
|
151
|
-
self.
|
152
|
-
|
163
|
+
if not self.sent and self.get_sampler().should_sample(
|
164
|
+
self.operation, self.is_ignored()
|
165
|
+
):
|
166
|
+
self.tag("mem_delta", self._get_mem_delta())
|
153
167
|
self.sent = True
|
154
168
|
batch_command = BatchCommand.from_tracked_request(self)
|
155
169
|
if scout_config.value("log_payload_content"):
|
@@ -217,7 +231,7 @@ class Span(object):
|
|
217
231
|
should_capture_backtrace=True,
|
218
232
|
):
|
219
233
|
self.span_id = "span-" + str(uuid4())
|
220
|
-
self.start_time = dt.datetime.
|
234
|
+
self.start_time = dt.datetime.now(dt.timezone.utc)
|
221
235
|
self.end_time = None
|
222
236
|
self.request_id = request_id
|
223
237
|
self.operation = operation
|
@@ -236,7 +250,7 @@ class Span(object):
|
|
236
250
|
)
|
237
251
|
|
238
252
|
def stop(self):
|
239
|
-
self.end_time = dt.datetime.
|
253
|
+
self.end_time = dt.datetime.now(dt.timezone.utc)
|
240
254
|
self.end_objtrace_counts = objtrace.get_counts()
|
241
255
|
|
242
256
|
def tag(self, key, value):
|
@@ -252,7 +266,9 @@ class Span(object):
|
|
252
266
|
return (self.end_time - self.start_time).total_seconds()
|
253
267
|
else:
|
254
268
|
# Current, running duration
|
255
|
-
return (
|
269
|
+
return (
|
270
|
+
dt.datetime.now(tz=dt.timezone.utc) - self.start_time
|
271
|
+
).total_seconds()
|
256
272
|
|
257
273
|
# Add any interesting annotations to the span. Assumes that we are in the
|
258
274
|
# process of stopping this span.
|
scout_apm/core/web_requests.py
CHANGED
scout_apm/django/middleware.py
CHANGED
@@ -122,6 +122,7 @@ class ViewTimingMiddleware(object):
|
|
122
122
|
span = tracked_request.current_span()
|
123
123
|
if span is not None:
|
124
124
|
span.operation = get_controller_name(request)
|
125
|
+
tracked_request.operation = span.operation
|
125
126
|
|
126
127
|
def process_exception(self, request, exception):
|
127
128
|
"""
|
scout_apm/dramatiq.py
CHANGED
@@ -17,7 +17,9 @@ class ScoutMiddleware(dramatiq.Middleware):
|
|
17
17
|
tracked_request = TrackedRequest.instance()
|
18
18
|
tracked_request.tag("queue", message.queue_name)
|
19
19
|
tracked_request.tag("message_id", message.message_id)
|
20
|
-
|
20
|
+
operation = "Job/" + message.actor_name
|
21
|
+
tracked_request.start_span(operation=operation)
|
22
|
+
tracked_request.operation = operation
|
21
23
|
|
22
24
|
def after_process_message(self, broker, message, result=None, exception=None):
|
23
25
|
if self._do_nothing:
|
scout_apm/falcon.py
CHANGED
@@ -106,6 +106,7 @@ class ScoutMiddleware(object):
|
|
106
106
|
span = tracked_request.start_span(
|
107
107
|
operation=operation, should_capture_backtrace=False
|
108
108
|
)
|
109
|
+
tracked_request.operation = operation
|
109
110
|
req.context.scout_resource_span = span
|
110
111
|
|
111
112
|
def _name_operation(self, req, responder, resource):
|
scout_apm/flask/__init__.py
CHANGED
@@ -47,6 +47,7 @@ class ScoutApm(object):
|
|
47
47
|
|
48
48
|
tracked_request = TrackedRequest.instance()
|
49
49
|
tracked_request.is_real_request = True
|
50
|
+
tracked_request.operation = operation
|
50
51
|
request._scout_tracked_request = tracked_request
|
51
52
|
|
52
53
|
werkzeug_track_request_data(request, tracked_request)
|
scout_apm/huey.py
CHANGED
@@ -30,6 +30,7 @@ def scout_on_pre_execute(task):
|
|
30
30
|
|
31
31
|
operation = "Job/{}.{}".format(task.__module__, task.__class__.__name__)
|
32
32
|
tracked_request.start_span(operation=operation)
|
33
|
+
tracked_request.operation = operation
|
33
34
|
|
34
35
|
|
35
36
|
def scout_on_post_execute(task, task_value, exception):
|
scout_apm/rq.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# coding=utf-8
|
2
2
|
|
3
3
|
import datetime as dt
|
4
|
+
import logging
|
4
5
|
|
5
6
|
import wrapt
|
6
7
|
from rq import SimpleWorker as RqSimpleWorker
|
@@ -14,6 +15,8 @@ from scout_apm.core.tracked_request import TrackedRequest
|
|
14
15
|
install_attempted = False
|
15
16
|
installed = None
|
16
17
|
|
18
|
+
logger = logging.getLogger(__name__)
|
19
|
+
|
17
20
|
|
18
21
|
def ensure_scout_installed():
|
19
22
|
global install_attempted, installed
|
@@ -65,10 +68,16 @@ def wrap_perform(wrapped, instance, args, kwargs):
|
|
65
68
|
tracked_request.is_real_request = True
|
66
69
|
tracked_request.tag("task_id", instance.get_id())
|
67
70
|
tracked_request.tag("queue", instance.origin)
|
68
|
-
|
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()
|
69
77
|
tracked_request.tag("queue_time", queue_time)
|
70
|
-
|
71
|
-
|
78
|
+
operation = "Job/{}".format(instance.func_name)
|
79
|
+
tracked_request.operation = operation
|
80
|
+
with tracked_request.span(operation=operation):
|
72
81
|
try:
|
73
82
|
return wrapped(*args, **kwargs)
|
74
83
|
except Exception:
|
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
2
|
-
Name:
|
3
|
-
Version: 3.
|
1
|
+
Metadata-Version: 2.2
|
2
|
+
Name: scout_apm
|
3
|
+
Version: 3.3.0
|
4
4
|
Summary: Scout Application Performance Monitoring Agent
|
5
5
|
Home-page: https://github.com/scoutapp/scout_apm_python
|
6
6
|
Author: Scout
|
@@ -32,10 +32,22 @@ Requires-Python: >=3.8, <4
|
|
32
32
|
Description-Content-Type: text/markdown
|
33
33
|
License-File: LICENSE
|
34
34
|
Requires-Dist: asgiref
|
35
|
-
Requires-Dist: psutil
|
36
|
-
Requires-Dist: urllib3
|
35
|
+
Requires-Dist: psutil>=5
|
36
|
+
Requires-Dist: urllib3~=2.2.0
|
37
37
|
Requires-Dist: certifi
|
38
|
-
Requires-Dist: wrapt
|
38
|
+
Requires-Dist: wrapt<2.0,>=1.10
|
39
|
+
Dynamic: author
|
40
|
+
Dynamic: author-email
|
41
|
+
Dynamic: classifier
|
42
|
+
Dynamic: description
|
43
|
+
Dynamic: description-content-type
|
44
|
+
Dynamic: home-page
|
45
|
+
Dynamic: keywords
|
46
|
+
Dynamic: license
|
47
|
+
Dynamic: project-url
|
48
|
+
Dynamic: requires-dist
|
49
|
+
Dynamic: requires-python
|
50
|
+
Dynamic: summary
|
39
51
|
|
40
52
|
# Scout Python APM Agent
|
41
53
|
|
@@ -74,7 +86,7 @@ To use Scout, you'll need to
|
|
74
86
|
|
75
87
|
For full installation instructions, including information on configuring Scout
|
76
88
|
via environment variables and troubleshooting, see our
|
77
|
-
[Python docs](https://
|
89
|
+
[Python docs](https://scoutapm.com/docs/python).
|
78
90
|
|
79
91
|
## Support
|
80
92
|
|
@@ -1,46 +1,41 @@
|
|
1
|
-
scout_apm
|
2
|
-
scout_apm-3.1.0.dist-info/LICENSE,sha256=IL2YQsmIcNnRK09t7_ELMSBMdyrMWIJpBOCAhZ9IMCU,1084
|
3
|
-
scout_apm-3.1.0.dist-info/WHEEL,sha256=iG8A0NTh07TTUrjuItt5jQWSaCWls4bkT_lqUuVXNGc,109
|
4
|
-
scout_apm-3.1.0.dist-info/entry_points.txt,sha256=eiVubJRHQCFcJ1fqH_2myVIOlt9xx32sKTRtWs9xWjk,82
|
5
|
-
scout_apm-3.1.0.dist-info/top_level.txt,sha256=tXGCTyC-E-TraDQng0CvkawiUZU-h4kkhe-5avNfnTw,10
|
6
|
-
scout_apm-3.1.0.dist-info/METADATA,sha256=1G2oC--ICt8gostTiHr4bKN-dcVddXC7ICS9ZDqhspA,3138
|
7
|
-
scout_apm/huey.py,sha256=fluE_JbNlkZv_Gko5R8jtdGWD8eAlvbOw_mnf6aEGCk,1710
|
1
|
+
scout_apm/huey.py,sha256=XjQNVfO8Z14iu1t9qqv-ZRn_3HqfuQv5t1ZYhlRZ17Y,1752
|
8
2
|
scout_apm/compat.py,sha256=kTG20OAM8SkbVQZS_-bd_bn4F0BjpsfGoxfCk7kyYCI,2496
|
9
|
-
scout_apm/dramatiq.py,sha256=
|
3
|
+
scout_apm/dramatiq.py,sha256=N0VxvprOeXSAmmvE7nmZ5ul1rMuJQC-eEzerwGTC7yc,1424
|
10
4
|
scout_apm/hug.py,sha256=tSo8r6oFNFoqF3T7lVXi1CxmrRkOX8DiDJrks58dHyw,1387
|
11
|
-
scout_apm/rq.py,sha256=
|
5
|
+
scout_apm/rq.py,sha256=afjakKsiNLXgwPMrBB-o8brMPptEuaIfQZ5Cu70ak1w,2249
|
12
6
|
scout_apm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
13
7
|
scout_apm/sqlalchemy.py,sha256=MKhRJwiJvJDRR4hQA2W8FG2YMueDFXNOmXbP9sQGVqM,1358
|
14
|
-
scout_apm/celery.py,sha256=
|
15
|
-
scout_apm/bottle.py,sha256=
|
16
|
-
scout_apm/falcon.py,sha256=
|
17
|
-
scout_apm/flask/__init__.py,sha256=
|
8
|
+
scout_apm/celery.py,sha256=zzM5wn9ScaFk-7YizntSOG8I2WEzEc-crSYZ7_AJjxA,4729
|
9
|
+
scout_apm/bottle.py,sha256=jWBUXgW4-sOiUbr9HYimO5Ryd9dvNVZa5Rb611jxxNI,2934
|
10
|
+
scout_apm/falcon.py,sha256=5Ll4RQRz8v_nB_A3cx65I1UQ2hxgWaU1uCsAtkbg4F0,5357
|
11
|
+
scout_apm/flask/__init__.py,sha256=Rt5D7c_NifqIG4cGGh7oo4XmekXacUlZda1WidECodU,4306
|
18
12
|
scout_apm/flask/sqlalchemy.py,sha256=HwoYvN0c4LZ9_spCkvVE8gcBJyHFRJCgndFVFKwCzao,757
|
19
|
-
scout_apm/core/tracked_request.py,sha256=
|
20
|
-
scout_apm/core/queue_time.py,sha256=
|
21
|
-
scout_apm/core/config.py,sha256=
|
22
|
-
scout_apm/core/metadata.py,sha256=
|
13
|
+
scout_apm/core/tracked_request.py,sha256=zCtv5hWoZ-Cy3yPM0UUaFQkVEyPHTt_BfLXC4FzBv7s,10394
|
14
|
+
scout_apm/core/queue_time.py,sha256=KA1tsQ8K4kNYSXTfWS-RulTQ41Bhs2-HmqJwIkVxgcc,3199
|
15
|
+
scout_apm/core/config.py,sha256=TuKWfz3T1rYhRLyJm9ewVBZcRlz3YjkFRBX1YJlVHg8,11868
|
16
|
+
scout_apm/core/metadata.py,sha256=huqY2c5vPrQSMZykxQzrSyYYiw8s9E_OIm_ABW9ZzzM,2284
|
23
17
|
scout_apm/core/error.py,sha256=EQ1e1wV9K2vkHXXRzoZxIPW89CF-ce7c8wYTI2b3ajM,3237
|
24
18
|
scout_apm/core/n_plus_one_tracker.py,sha256=hE3YEVEe45Oygq8lR_6ftPRdCab6WYjnN8HeY8wLPL8,978
|
25
19
|
scout_apm/core/__init__.py,sha256=SnXENrNGgE8_ontzysrZdrARLTsBDz7hXD50zludFbM,2944
|
26
20
|
scout_apm/core/threading.py,sha256=i_e3Zbqcq-yIDkipcTKCGJwmqGzpiYffl6IK88ylC-g,1624
|
27
21
|
scout_apm/core/context.py,sha256=9qpFGKAGIqyer1NqAhBmU8DuVTf-4_doUFSC55vfDm4,4220
|
28
22
|
scout_apm/core/objtrace.py,sha256=F7L0V2QBzXYUEFfqon3adXkrA9UQJgmWCFprJo-l01I,463
|
29
|
-
scout_apm/core/web_requests.py,sha256=
|
23
|
+
scout_apm/core/web_requests.py,sha256=DD6xDdDZOfxCmpOTvXEJY3JLQ0n4Zw9SmlSfnN4xC3w,5680
|
30
24
|
scout_apm/core/platform_detection.py,sha256=gWgZNfcnjy97HzDTz-Q2F6xzrch7eVQtTGp2KqcdMEk,1777
|
31
|
-
scout_apm/core/_objtrace.cpython-39-darwin.so,sha256=
|
25
|
+
scout_apm/core/_objtrace.cpython-39-darwin.so,sha256=eBWmr_SV9gLPCBJnxHRW3lsObhINaf7f_Y639QvYoOo,35288
|
32
26
|
scout_apm/core/stacktracer.py,sha256=loNFpOwFtTvf6XsCxari4iFJ8Pe4rZrLmoU691ZuV1M,900
|
27
|
+
scout_apm/core/sampler.py,sha256=NZKX2RAnvOfsHY6EN47qaRmKOCoL04slNG1k91n8n_I,5105
|
33
28
|
scout_apm/core/backtrace.py,sha256=x2owyERxWdomJBz4leN3wHsf_P393864UUXW4uFrQtc,3495
|
34
29
|
scout_apm/core/error_service.py,sha256=QKyFGgjc_Ihm8ONv7j0hkPgtBu30U3poyfAD40BJ7jQ,5359
|
35
30
|
scout_apm/core/agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
36
31
|
scout_apm/core/agent/socket.py,sha256=GR0tfElZNxiCRCwtvHiqLU_37GIcz_ABQ3B7Jh-XeP8,6777
|
37
|
-
scout_apm/core/agent/commands.py,sha256=
|
32
|
+
scout_apm/core/agent/commands.py,sha256=Ze4CKBHstpSk5OOzr1sNT84XFY2U9hLUGqfpDBSqhtY,7147
|
38
33
|
scout_apm/core/agent/manager.py,sha256=Vm6JfjRJW9YGQabWeZ5VXuWC7g8P_qBok1NjP6Oqoh4,10385
|
39
34
|
scout_apm/core/cli/core_agent_manager.py,sha256=iOZhpXCnKZriJznCc2LD7eDXKqmc98zsYtDIrj3tXlU,813
|
40
35
|
scout_apm/core/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
41
|
-
scout_apm/core/samplers/thread.py,sha256=
|
36
|
+
scout_apm/core/samplers/thread.py,sha256=P0s7T99EpYbbkekZnlCJQ787G8g1NnNbZjn8CLtDdUg,1342
|
42
37
|
scout_apm/core/samplers/memory.py,sha256=D1py5gmf5GISq6_5HNnwI3HU2EunXaQ2HzqVKRwp5no,444
|
43
|
-
scout_apm/core/samplers/cpu.py,sha256=
|
38
|
+
scout_apm/core/samplers/cpu.py,sha256=X0bpFks18cR61pbf69ptyKE0CtnUmBJUJXSRQ_S2IdM,2522
|
44
39
|
scout_apm/core/samplers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
45
40
|
scout_apm/instruments/elasticsearch.py,sha256=xkkV1wHCd6clAFX7sMMrU75mfxXkKwN0i43aWPZg3is,8272
|
46
41
|
scout_apm/instruments/urllib3.py,sha256=riwzKX-sgj9kCbN8E5gvtBlyWPAae8bMvi-JgMuGO_w,2219
|
@@ -48,8 +43,8 @@ scout_apm/instruments/jinja2.py,sha256=a2-u9klcZQdkCJaylc55a4KGi3tHaNlbMXQr1PW3z
|
|
48
43
|
scout_apm/instruments/__init__.py,sha256=X76KWdu2jgDdeJtg5DOC6rYPaq0ZXkhjiftxcsem3hI,631
|
49
44
|
scout_apm/instruments/pymongo.py,sha256=wVqA59ciH6LvOH3VRmMZG067U6qypoA6QzLtWp0IdAM,2538
|
50
45
|
scout_apm/instruments/redis.py,sha256=nrkt35bNI172yDzHmpcXFc972QDxLdOkdvh2gWkcz08,2190
|
51
|
-
scout_apm/api/__init__.py,sha256=
|
52
|
-
scout_apm/async_/starlette.py,sha256=
|
46
|
+
scout_apm/api/__init__.py,sha256=dx6qEJ-xMsWc4E4bvgoxSo5k7vrCELLApJvOoyLWSpA,5660
|
47
|
+
scout_apm/async_/starlette.py,sha256=aFnc1XTXC4ug9llYX9dDeiElXQIb6QgA8gq3N_g9-Ew,3812
|
53
48
|
scout_apm/async_/__init__.py,sha256=eoZ6GfifbqhMLNzjlqRDVil-yyBkOmVN9ujSgJWNBlY,15
|
54
49
|
scout_apm/async_/api.py,sha256=K8Sh5IiVO-PnJNRzErAQulNxuFcQoMR8-a2-P0IMAZo,1116
|
55
50
|
scout_apm/async_/instruments/jinja2.py,sha256=pYrLigxeSgEAWmzWxqh5zCa86TkZQ9A1S7W8uTXE3C8,374
|
@@ -57,8 +52,14 @@ scout_apm/async_/instruments/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NM
|
|
57
52
|
scout_apm/django/request.py,sha256=bswpkpJIKO5oFm3OiBRwgXhtZFKx6pbgzGNCLrF8qz0,4853
|
58
53
|
scout_apm/django/__init__.py,sha256=RQdhc6VLBlJsiWLVb8yggtaY2pMla1j6YP4yrKPAYgk,207
|
59
54
|
scout_apm/django/apps.py,sha256=aUQlZG8qQFCi0-vFeU0fmnpI60n3nvpXRWBAjdgG5GA,5260
|
60
|
-
scout_apm/django/middleware.py,sha256=
|
55
|
+
scout_apm/django/middleware.py,sha256=fE-bon42PigKZ5SXVxDBHOyPwBmWzZNmzqILKZqM72I,7761
|
61
56
|
scout_apm/django/instruments/huey.py,sha256=tRvBg5wr76W_aGpLfVpaDnLcTpR8jdhGMFOz3Xl580s,653
|
62
57
|
scout_apm/django/instruments/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
63
58
|
scout_apm/django/instruments/template.py,sha256=zr_xlfgEbHoIItAFLbMEc0I4jj4sU8BW5IXMPNMqOI0,963
|
64
59
|
scout_apm/django/instruments/sql.py,sha256=8_n7oUHtrAc7miWrk_k8CLhc9KzS6W0iUbmZJFN-S6g,4227
|
60
|
+
scout_apm-3.3.0.dist-info/RECORD,,
|
61
|
+
scout_apm-3.3.0.dist-info/LICENSE,sha256=IL2YQsmIcNnRK09t7_ELMSBMdyrMWIJpBOCAhZ9IMCU,1084
|
62
|
+
scout_apm-3.3.0.dist-info/WHEEL,sha256=0WiweP_1WiDsz1Eg0WaDZErWbFKfA6G6v7dDq6Kgtvg,108
|
63
|
+
scout_apm-3.3.0.dist-info/entry_points.txt,sha256=eiVubJRHQCFcJ1fqH_2myVIOlt9xx32sKTRtWs9xWjk,82
|
64
|
+
scout_apm-3.3.0.dist-info/top_level.txt,sha256=tXGCTyC-E-TraDQng0CvkawiUZU-h4kkhe-5avNfnTw,10
|
65
|
+
scout_apm-3.3.0.dist-info/METADATA,sha256=us-cfey-lzP4bYukYOSb9VuI88IgE8pla3szPXPyYY0,3386
|
File without changes
|
File without changes
|
File without changes
|