scout-apm 3.3.0__cp38-cp38-musllinux_1_2_i686.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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-38-i386-linux-gnu.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 +82 -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)()
|