scout-apm 3.3.0__cp312-cp312-musllinux_1_2_i686.whl
Sign up to get free protection for your applications and to get access to all the features.
- scout_apm/__init__.py +0 -0
- scout_apm/api/__init__.py +197 -0
- scout_apm/async_/__init__.py +1 -0
- scout_apm/async_/api.py +41 -0
- scout_apm/async_/instruments/__init__.py +0 -0
- scout_apm/async_/instruments/jinja2.py +13 -0
- scout_apm/async_/starlette.py +101 -0
- scout_apm/bottle.py +86 -0
- scout_apm/celery.py +153 -0
- scout_apm/compat.py +104 -0
- scout_apm/core/__init__.py +99 -0
- scout_apm/core/_objtrace.cpython-312-i386-linux-musl.so +0 -0
- scout_apm/core/agent/__init__.py +0 -0
- scout_apm/core/agent/commands.py +250 -0
- scout_apm/core/agent/manager.py +319 -0
- scout_apm/core/agent/socket.py +211 -0
- scout_apm/core/backtrace.py +116 -0
- scout_apm/core/cli/__init__.py +0 -0
- scout_apm/core/cli/core_agent_manager.py +32 -0
- scout_apm/core/config.py +404 -0
- scout_apm/core/context.py +140 -0
- scout_apm/core/error.py +95 -0
- scout_apm/core/error_service.py +167 -0
- scout_apm/core/metadata.py +66 -0
- scout_apm/core/n_plus_one_tracker.py +41 -0
- scout_apm/core/objtrace.py +24 -0
- scout_apm/core/platform_detection.py +66 -0
- scout_apm/core/queue_time.py +99 -0
- scout_apm/core/sampler.py +149 -0
- scout_apm/core/samplers/__init__.py +0 -0
- scout_apm/core/samplers/cpu.py +76 -0
- scout_apm/core/samplers/memory.py +23 -0
- scout_apm/core/samplers/thread.py +41 -0
- scout_apm/core/stacktracer.py +30 -0
- scout_apm/core/threading.py +56 -0
- scout_apm/core/tracked_request.py +328 -0
- scout_apm/core/web_requests.py +167 -0
- scout_apm/django/__init__.py +7 -0
- scout_apm/django/apps.py +137 -0
- scout_apm/django/instruments/__init__.py +0 -0
- scout_apm/django/instruments/huey.py +30 -0
- scout_apm/django/instruments/sql.py +140 -0
- scout_apm/django/instruments/template.py +35 -0
- scout_apm/django/middleware.py +211 -0
- scout_apm/django/request.py +144 -0
- scout_apm/dramatiq.py +42 -0
- scout_apm/falcon.py +142 -0
- scout_apm/flask/__init__.py +118 -0
- scout_apm/flask/sqlalchemy.py +28 -0
- scout_apm/huey.py +54 -0
- scout_apm/hug.py +40 -0
- scout_apm/instruments/__init__.py +21 -0
- scout_apm/instruments/elasticsearch.py +263 -0
- scout_apm/instruments/jinja2.py +127 -0
- scout_apm/instruments/pymongo.py +105 -0
- scout_apm/instruments/redis.py +77 -0
- scout_apm/instruments/urllib3.py +80 -0
- scout_apm/rq.py +85 -0
- scout_apm/sqlalchemy.py +38 -0
- scout_apm-3.3.0.dist-info/LICENSE +21 -0
- scout_apm-3.3.0.dist-info/METADATA +94 -0
- scout_apm-3.3.0.dist-info/RECORD +65 -0
- scout_apm-3.3.0.dist-info/WHEEL +5 -0
- scout_apm-3.3.0.dist-info/entry_points.txt +2 -0
- scout_apm-3.3.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,211 @@
|
|
1
|
+
# coding=utf-8
|
2
|
+
|
3
|
+
import json
|
4
|
+
import logging
|
5
|
+
import os
|
6
|
+
import socket
|
7
|
+
import struct
|
8
|
+
import threading
|
9
|
+
import time
|
10
|
+
|
11
|
+
from scout_apm.compat import queue
|
12
|
+
from scout_apm.core.agent.commands import Register
|
13
|
+
from scout_apm.core.agent.manager import get_socket_path
|
14
|
+
from scout_apm.core.config import scout_config
|
15
|
+
from scout_apm.core.threading import SingletonThread
|
16
|
+
|
17
|
+
# Time unit - monkey-patched in tests to make them run faster
|
18
|
+
SECOND = 1
|
19
|
+
|
20
|
+
logger = logging.getLogger(__name__)
|
21
|
+
|
22
|
+
|
23
|
+
class CoreAgentSocketThread(SingletonThread):
|
24
|
+
_instance_lock = threading.Lock()
|
25
|
+
_stop_event = threading.Event()
|
26
|
+
_command_queue = queue.Queue(maxsize=500)
|
27
|
+
|
28
|
+
@classmethod
|
29
|
+
def _on_stop(cls):
|
30
|
+
super(CoreAgentSocketThread, cls)._on_stop()
|
31
|
+
# Unblock _command_queue.get()
|
32
|
+
try:
|
33
|
+
cls._command_queue.put(None, False)
|
34
|
+
except queue.Full:
|
35
|
+
pass
|
36
|
+
|
37
|
+
@classmethod
|
38
|
+
def send(cls, command):
|
39
|
+
try:
|
40
|
+
cls._command_queue.put(command, False)
|
41
|
+
except queue.Full as exc:
|
42
|
+
# TODO mark the command as not queued?
|
43
|
+
logger.debug("CoreAgentSocketThread error on send: %r", exc, exc_info=exc)
|
44
|
+
|
45
|
+
cls.ensure_started()
|
46
|
+
|
47
|
+
@classmethod
|
48
|
+
def wait_until_drained(cls, timeout_seconds=2.0, callback=None):
|
49
|
+
interval_seconds = min(timeout_seconds, 0.05)
|
50
|
+
start = time.time()
|
51
|
+
while True:
|
52
|
+
queue_size = cls._command_queue.qsize()
|
53
|
+
queue_empty = queue_size == 0
|
54
|
+
elapsed = time.time() - start
|
55
|
+
if queue_empty or elapsed >= timeout_seconds:
|
56
|
+
break
|
57
|
+
|
58
|
+
if callback is not None:
|
59
|
+
callback(queue_size)
|
60
|
+
callback = None
|
61
|
+
|
62
|
+
cls.ensure_started()
|
63
|
+
|
64
|
+
time.sleep(interval_seconds)
|
65
|
+
return queue_empty
|
66
|
+
|
67
|
+
def run(self):
|
68
|
+
self.socket_path = get_socket_path()
|
69
|
+
self.socket = self.make_socket()
|
70
|
+
|
71
|
+
try:
|
72
|
+
self._connect()
|
73
|
+
self._register()
|
74
|
+
while True:
|
75
|
+
try:
|
76
|
+
body = self._command_queue.get(block=True, timeout=1 * SECOND)
|
77
|
+
except queue.Empty:
|
78
|
+
body = None
|
79
|
+
|
80
|
+
if body is not None:
|
81
|
+
result = self._send(body)
|
82
|
+
if result:
|
83
|
+
self._command_queue.task_done()
|
84
|
+
else:
|
85
|
+
# Something was wrong with the socket.
|
86
|
+
self._disconnect()
|
87
|
+
self._connect()
|
88
|
+
self._register()
|
89
|
+
|
90
|
+
# Check for stop event after each read. This allows opening,
|
91
|
+
# sending, and then immediately stopping. We do this for
|
92
|
+
# the metadata event at application start time.
|
93
|
+
if self._stop_event.is_set():
|
94
|
+
logger.debug("CoreAgentSocketThread stopping.")
|
95
|
+
break
|
96
|
+
except Exception as exc:
|
97
|
+
logger.debug("CoreAgentSocketThread exception: %r", exc, exc_info=exc)
|
98
|
+
finally:
|
99
|
+
self.socket.close()
|
100
|
+
logger.debug("CoreAgentSocketThread stopped.")
|
101
|
+
|
102
|
+
def _send(self, command):
|
103
|
+
msg = command.message()
|
104
|
+
|
105
|
+
try:
|
106
|
+
data = json.dumps(msg)
|
107
|
+
except (ValueError, TypeError) as exc:
|
108
|
+
logger.debug(
|
109
|
+
"Exception when serializing command message: %r", exc, exc_info=exc
|
110
|
+
)
|
111
|
+
return False
|
112
|
+
|
113
|
+
full_data = struct.pack(">I", len(data)) + data.encode("utf-8")
|
114
|
+
try:
|
115
|
+
self.socket.sendall(full_data)
|
116
|
+
except OSError as exc:
|
117
|
+
logger.debug(
|
118
|
+
(
|
119
|
+
"CoreAgentSocketThread exception on _send:"
|
120
|
+
+ " %r on PID: %s on thread: %s"
|
121
|
+
),
|
122
|
+
exc,
|
123
|
+
os.getpid(),
|
124
|
+
threading.current_thread(),
|
125
|
+
exc_info=exc,
|
126
|
+
)
|
127
|
+
return False
|
128
|
+
|
129
|
+
# TODO do something with the response sent back in reply to command
|
130
|
+
self._read_response()
|
131
|
+
|
132
|
+
return True
|
133
|
+
|
134
|
+
def _read_response(self):
|
135
|
+
try:
|
136
|
+
raw_size = self.socket.recv(4)
|
137
|
+
if len(raw_size) != 4:
|
138
|
+
# Ignore invalid responses
|
139
|
+
return None
|
140
|
+
size = struct.unpack(">I", raw_size)[0]
|
141
|
+
message = bytearray(0)
|
142
|
+
|
143
|
+
while len(message) < size:
|
144
|
+
recv = self.socket.recv(size)
|
145
|
+
message += recv
|
146
|
+
|
147
|
+
return message
|
148
|
+
except OSError as exc:
|
149
|
+
logger.debug(
|
150
|
+
"CoreAgentSocketThread error on read response: %r", exc, exc_info=exc
|
151
|
+
)
|
152
|
+
return None
|
153
|
+
|
154
|
+
def _register(self):
|
155
|
+
self._send(
|
156
|
+
Register(
|
157
|
+
app=scout_config.value("name"),
|
158
|
+
key=scout_config.value("key"),
|
159
|
+
hostname=scout_config.value("hostname"),
|
160
|
+
)
|
161
|
+
)
|
162
|
+
|
163
|
+
def _connect(self, connect_attempts=5, retry_wait_secs=1):
|
164
|
+
for attempt in range(1, connect_attempts + 1):
|
165
|
+
logger.debug(
|
166
|
+
(
|
167
|
+
"CoreAgentSocketThread attempt %d, connecting to %s, "
|
168
|
+
+ "PID: %s, Thread: %s"
|
169
|
+
),
|
170
|
+
attempt,
|
171
|
+
self.socket_path,
|
172
|
+
os.getpid(),
|
173
|
+
threading.current_thread(),
|
174
|
+
)
|
175
|
+
try:
|
176
|
+
self.socket.connect(self.get_socket_address())
|
177
|
+
self.socket.settimeout(3 * SECOND)
|
178
|
+
logger.debug("CoreAgentSocketThread connected")
|
179
|
+
return
|
180
|
+
except socket.error as exc:
|
181
|
+
logger.debug(
|
182
|
+
"CoreAgentSocketThread connection error: %r", exc, exc_info=exc
|
183
|
+
)
|
184
|
+
# Return without waiting when reaching the maximum number of attempts.
|
185
|
+
if attempt == connect_attempts:
|
186
|
+
raise
|
187
|
+
time.sleep(retry_wait_secs * SECOND)
|
188
|
+
|
189
|
+
def _disconnect(self):
|
190
|
+
logger.debug("CoreAgentSocketThread disconnecting from %s", self.socket_path)
|
191
|
+
try:
|
192
|
+
self.socket.close()
|
193
|
+
except socket.error as exc:
|
194
|
+
logger.debug(
|
195
|
+
"CoreAgentSocketThread exception on disconnect: %r", exc, exc_info=exc
|
196
|
+
)
|
197
|
+
finally:
|
198
|
+
self.socket = self.make_socket()
|
199
|
+
|
200
|
+
def make_socket(self):
|
201
|
+
if self.socket_path.is_tcp:
|
202
|
+
family = socket.AF_INET
|
203
|
+
else:
|
204
|
+
family = socket.AF_UNIX
|
205
|
+
return socket.socket(family, socket.SOCK_STREAM)
|
206
|
+
|
207
|
+
def get_socket_address(self):
|
208
|
+
if self.socket_path.is_tcp:
|
209
|
+
host, _, port = self.socket_path.tcp_address.partition(":")
|
210
|
+
return host, int(port)
|
211
|
+
return self.socket_path
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# coding=utf-8
|
2
|
+
|
3
|
+
import itertools
|
4
|
+
import os
|
5
|
+
import sys
|
6
|
+
import sysconfig
|
7
|
+
import traceback
|
8
|
+
import warnings
|
9
|
+
from logging import getLogger
|
10
|
+
|
11
|
+
logger = getLogger(__name__)
|
12
|
+
|
13
|
+
# Maximum non-Scout frames to target retrieving
|
14
|
+
LIMIT = 50
|
15
|
+
# How many upper frames from inside Scout to ignore
|
16
|
+
IGNORED = 1
|
17
|
+
|
18
|
+
|
19
|
+
def filter_frames(frames):
|
20
|
+
"""Filter the stack trace frames down to non-library code."""
|
21
|
+
paths = sysconfig.get_paths()
|
22
|
+
library_paths = {paths["purelib"], paths["platlib"]}
|
23
|
+
for frame in frames:
|
24
|
+
if not any(frame["file"].startswith(exclusion) for exclusion in library_paths):
|
25
|
+
yield frame
|
26
|
+
|
27
|
+
|
28
|
+
def module_filepath(module, filepath):
|
29
|
+
"""Get the filepath relative to the base module."""
|
30
|
+
root_module_name = module.split(".", 1)[0]
|
31
|
+
if root_module_name == module:
|
32
|
+
return os.path.basename(filepath)
|
33
|
+
|
34
|
+
module_dir = None
|
35
|
+
root_module = sys.modules[root_module_name]
|
36
|
+
try:
|
37
|
+
if root_module.__file__:
|
38
|
+
module_dir = root_module.__file__.rsplit(os.sep, 2)[0]
|
39
|
+
elif root_module.__path__ and isinstance(root_module.__path__, (list, tuple)):
|
40
|
+
# Default to using the first path specified for the module.
|
41
|
+
module_dir = root_module.__path__[0].rsplit(os.sep, 1)[0]
|
42
|
+
if len(root_module.__path__) > 1:
|
43
|
+
logger.debug(
|
44
|
+
"{} has {} paths. Use the first and ignore the rest.".format(
|
45
|
+
root_module, len(root_module.__path__)
|
46
|
+
)
|
47
|
+
)
|
48
|
+
except Exception as exc:
|
49
|
+
logger.debug(
|
50
|
+
"Error processing module {} and filepath {}".format(root_module, filepath),
|
51
|
+
exc_info=exc,
|
52
|
+
)
|
53
|
+
|
54
|
+
return filepath.split(module_dir, 1)[-1].lstrip(os.sep) if module_dir else filepath
|
55
|
+
|
56
|
+
|
57
|
+
def filepaths(frame):
|
58
|
+
"""Get the filepath for frame."""
|
59
|
+
module = frame.f_globals.get("__name__", None)
|
60
|
+
filepath = frame.f_code.co_filename
|
61
|
+
|
62
|
+
if filepath.endswith(".pyc"):
|
63
|
+
filepath = filepath[:-1]
|
64
|
+
|
65
|
+
return filepath, (module_filepath(module, filepath) if module else filepath)
|
66
|
+
|
67
|
+
|
68
|
+
def stacktrace_walker(tb):
|
69
|
+
"""Iterate over each frame of the stack downards for exceptions."""
|
70
|
+
for frame, lineno in traceback.walk_tb(tb):
|
71
|
+
name = frame.f_code.co_name
|
72
|
+
full_path, relative_path = filepaths(frame)
|
73
|
+
yield {
|
74
|
+
"file": relative_path,
|
75
|
+
"full_path": full_path,
|
76
|
+
"line": lineno,
|
77
|
+
"function": name,
|
78
|
+
}
|
79
|
+
|
80
|
+
|
81
|
+
def backtrace_walker():
|
82
|
+
"""Iterate over each frame of the stack upwards.
|
83
|
+
|
84
|
+
Taken from python3/traceback.ExtractSummary.extract to support
|
85
|
+
iterating over the entire stack, but without creating a large
|
86
|
+
data structure.
|
87
|
+
"""
|
88
|
+
start_frame = sys._getframe().f_back
|
89
|
+
for frame, lineno in traceback.walk_stack(start_frame):
|
90
|
+
name = frame.f_code.co_name
|
91
|
+
full_path, relative_path = filepaths(frame)
|
92
|
+
yield {
|
93
|
+
"file": relative_path,
|
94
|
+
"full_path": full_path,
|
95
|
+
"line": lineno,
|
96
|
+
"function": name,
|
97
|
+
}
|
98
|
+
|
99
|
+
|
100
|
+
def capture_backtrace():
|
101
|
+
walker = filter_frames(backtrace_walker())
|
102
|
+
return list(itertools.islice(walker, LIMIT))
|
103
|
+
|
104
|
+
|
105
|
+
def capture_stacktrace(tb):
|
106
|
+
walker = stacktrace_walker(tb)
|
107
|
+
return list(reversed(list(itertools.islice(walker, LIMIT))))
|
108
|
+
|
109
|
+
|
110
|
+
def capture():
|
111
|
+
warnings.warn(
|
112
|
+
"capture is deprecated, instead use capture_backtrace instead.",
|
113
|
+
DeprecationWarning,
|
114
|
+
stacklevel=2,
|
115
|
+
)
|
116
|
+
return capture_backtrace()
|
File without changes
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding=utf-8
|
2
|
+
|
3
|
+
import argparse
|
4
|
+
import logging
|
5
|
+
|
6
|
+
from scout_apm.core import CoreAgentManager
|
7
|
+
|
8
|
+
logger = logging.getLogger(__name__)
|
9
|
+
|
10
|
+
|
11
|
+
def main(argv=None):
|
12
|
+
parser = argparse.ArgumentParser()
|
13
|
+
parser.add_argument(
|
14
|
+
"-v", "--verbose", help="increase output verbosity", action="count"
|
15
|
+
)
|
16
|
+
|
17
|
+
subparsers = parser.add_subparsers(
|
18
|
+
title="subcommands", description="valid subcommands", dest="subparser"
|
19
|
+
)
|
20
|
+
subparsers.add_parser("download")
|
21
|
+
subparsers.add_parser("launch")
|
22
|
+
|
23
|
+
args = parser.parse_args(argv)
|
24
|
+
|
25
|
+
if args.verbose is not None:
|
26
|
+
if args.verbose >= 2:
|
27
|
+
logging.basicConfig(level=logging.DEBUG)
|
28
|
+
else:
|
29
|
+
logging.basicConfig(level=logging.INFO)
|
30
|
+
|
31
|
+
core_agent_manager = CoreAgentManager()
|
32
|
+
getattr(core_agent_manager, args.subparser)()
|