cavisson-pythonagent 0.0.1__py3-none-any.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.
Files changed (180) hide show
  1. cavisson_pythonagent-0.0.1.dist-info/METADATA +32 -0
  2. cavisson_pythonagent-0.0.1.dist-info/RECORD +180 -0
  3. cavisson_pythonagent-0.0.1.dist-info/WHEEL +5 -0
  4. cavisson_pythonagent-0.0.1.dist-info/licenses/LICENSE +19 -0
  5. cavisson_pythonagent-0.0.1.dist-info/top_level.txt +1 -0
  6. pythonagent/__init__.py +22 -0
  7. pythonagent/agent/__init__.py +219 -0
  8. pythonagent/agent/internal/__init__.py +1 -0
  9. pythonagent/agent/internal/agent.py +1651 -0
  10. pythonagent/agent/internal/framesinfo.py +152 -0
  11. pythonagent/agent/internal/heap_dump.py +39 -0
  12. pythonagent/agent/internal/intercept_module.py +70 -0
  13. pythonagent/agent/internal/logs.py +122 -0
  14. pythonagent/agent/internal/metadata/__init__.py +0 -0
  15. pythonagent/agent/internal/metadata/agent_meta_data.py +276 -0
  16. pythonagent/agent/internal/proc_compat.py +75 -0
  17. pythonagent/agent/internal/profile.py +47 -0
  18. pythonagent/agent/internal/provider.py +83 -0
  19. pythonagent/agent/internal/thread_dump.py +85 -0
  20. pythonagent/agent/internal/udp.py +245 -0
  21. pythonagent/agent/internal/udp_message.py +800 -0
  22. pythonagent/agent/probes/Instrumentation/__init__.py +340 -0
  23. pythonagent/agent/probes/Instrumentation/find.py +243 -0
  24. pythonagent/agent/probes/Instrumentation/module_version_resolver.py +155 -0
  25. pythonagent/agent/probes/Instrumentation/new_parser.py +64 -0
  26. pythonagent/agent/probes/Instrumentation/parser.py +129 -0
  27. pythonagent/agent/probes/__init__.py +219 -0
  28. pythonagent/agent/probes/base.py +303 -0
  29. pythonagent/agent/probes/cache/__init__.py +51 -0
  30. pythonagent/agent/probes/cache/redis.py +119 -0
  31. pythonagent/agent/probes/cache/redis_asyncio.py +83 -0
  32. pythonagent/agent/probes/coroutines/__init__.py +1 -0
  33. pythonagent/agent/probes/coroutines/asyncio.py +63 -0
  34. pythonagent/agent/probes/elasticdb/__init__.py +7 -0
  35. pythonagent/agent/probes/elasticdb/aelastic.py +54 -0
  36. pythonagent/agent/probes/frameworks/__init__.py +27 -0
  37. pythonagent/agent/probes/frameworks/agentprofiler.py +105 -0
  38. pythonagent/agent/probes/frameworks/aiohttp_web.py +155 -0
  39. pythonagent/agent/probes/frameworks/aisess.py +151 -0
  40. pythonagent/agent/probes/frameworks/asgi.py +340 -0
  41. pythonagent/agent/probes/frameworks/bottle.py +27 -0
  42. pythonagent/agent/probes/frameworks/cherry.py +25 -0
  43. pythonagent/agent/probes/frameworks/django.py +128 -0
  44. pythonagent/agent/probes/frameworks/falcon.py +21 -0
  45. pythonagent/agent/probes/frameworks/fastapi.py +35 -0
  46. pythonagent/agent/probes/frameworks/flask.py +30 -0
  47. pythonagent/agent/probes/frameworks/pyramid.py +56 -0
  48. pythonagent/agent/probes/frameworks/test.py +108 -0
  49. pythonagent/agent/probes/frameworks/tornado_async_web.py +117 -0
  50. pythonagent/agent/probes/frameworks/tornado_web.py +133 -0
  51. pythonagent/agent/probes/frameworks/wsgi.py +353 -0
  52. pythonagent/agent/probes/grpc/__init__.py +76 -0
  53. pythonagent/agent/probes/grpc/client_interceptor.py +132 -0
  54. pythonagent/agent/probes/grpc/server_interceptor.py +129 -0
  55. pythonagent/agent/probes/havoc/__init__.py +0 -0
  56. pythonagent/agent/probes/havoc/custom_memory_stress.py +186 -0
  57. pythonagent/agent/probes/havoc/custom_thread_stress.py +187 -0
  58. pythonagent/agent/probes/havoc/havoc_constants.py +218 -0
  59. pythonagent/agent/probes/havoc/havoc_manager.py +981 -0
  60. pythonagent/agent/probes/http/__init__.py +49 -0
  61. pythonagent/agent/probes/http/aiohttp_client.py +59 -0
  62. pythonagent/agent/probes/http/boto.py +12 -0
  63. pythonagent/agent/probes/http/httplib.py +110 -0
  64. pythonagent/agent/probes/http/httpx_client.py +116 -0
  65. pythonagent/agent/probes/http/requests.py +15 -0
  66. pythonagent/agent/probes/http/tornado_httpclient.py +85 -0
  67. pythonagent/agent/probes/http/urllib3.py +16 -0
  68. pythonagent/agent/probes/langchain/__init__.py +21 -0
  69. pythonagent/agent/probes/langchain/base_tool.py +95 -0
  70. pythonagent/agent/probes/langchain/langchain_community.py +136 -0
  71. pythonagent/agent/probes/langchain/langchain_core.py +32 -0
  72. pythonagent/agent/probes/langchain/langchain_openai.py +110 -0
  73. pythonagent/agent/probes/logging/__init__.py +106 -0
  74. pythonagent/agent/probes/message_brokers/__init__.py +4 -0
  75. pythonagent/agent/probes/message_brokers/pika.py +126 -0
  76. pythonagent/agent/probes/mongodb/__init__.py +6 -0
  77. pythonagent/agent/probes/mongodb/pymongo.py +286 -0
  78. pythonagent/agent/probes/openai/__init__.py +3 -0
  79. pythonagent/agent/probes/openai/openai.py +797 -0
  80. pythonagent/agent/probes/span.py +101 -0
  81. pythonagent/agent/probes/sql/__init__.py +13 -0
  82. pythonagent/agent/probes/sql/botocores3.py +51 -0
  83. pythonagent/agent/probes/sql/dbapi.py +285 -0
  84. pythonagent/agent/probes/sql/dynamodb.py +90 -0
  85. pythonagent/agent/probes/sql/mysql_connector.py +24 -0
  86. pythonagent/agent/probes/sql/mysql_connector_cext.py +24 -0
  87. pythonagent/agent/probes/sql/mysqldb.py +43 -0
  88. pythonagent/agent/probes/sql/psycopg2.py +174 -0
  89. pythonagent/agent/probes/sql/pymysql.py +25 -0
  90. pythonagent/bootstrap/__init__.py +0 -0
  91. pythonagent/bootstrap/cav_gunicorn.py +26 -0
  92. pythonagent/bootstrap/cavagent_lambda_wrapper.py +291 -0
  93. pythonagent/bootstrap/run.py +47 -0
  94. pythonagent/bootstrap/sitecustomize.py +287 -0
  95. pythonagent/cavisson/netdiagnostics/CavAgent/instrumentationprofile.json +26 -0
  96. pythonagent/cavisson/netdiagnostics/CavAgent/interceptor_points.txt +29 -0
  97. pythonagent/cavisson/netdiagnostics/python/CavAgent/instrumentationprofile.json +42 -0
  98. pythonagent/cavisson/netdiagnostics/python/CavAgent/interceptor_points.txt +29 -0
  99. pythonagent/cavisson/netdiagnostics/python/config/ndsettings.conf +6 -0
  100. pythonagent/config.py +279 -0
  101. pythonagent/find.py +72 -0
  102. pythonagent/find_mod_cls_name.py +54 -0
  103. pythonagent/lang.py +131 -0
  104. pythonagent/lib.py +91 -0
  105. pythonagent/main/__init__.py +0 -0
  106. pythonagent/main/pytrace/__init__.py +79 -0
  107. pythonagent/main/pytrace/commands/__init__.py +0 -0
  108. pythonagent/main/pytrace/commands/auto_discovery.py +25 -0
  109. pythonagent/main/pytrace/commands/run.py +401 -0
  110. pythonagent/main/pytrace/pytrace.py +133 -0
  111. pythonagent/main/wsgi.py +6 -0
  112. pythonagent/main.py +27 -0
  113. pythonagent/run.py +46 -0
  114. pythonagent/sqins.py +8 -0
  115. pythonagent/test.py +73 -0
  116. pythonagent/utils.py +168 -0
  117. pythonagent/vendor/__init__.py +0 -0
  118. pythonagent/vendor/pympler/__init__.py +1 -0
  119. pythonagent/vendor/pympler/asizeof.py +2810 -0
  120. pythonagent/vendor/pympler/charts.py +62 -0
  121. pythonagent/vendor/pympler/classtracker.py +590 -0
  122. pythonagent/vendor/pympler/classtracker_stats.py +780 -0
  123. pythonagent/vendor/pympler/garbagegraph.py +80 -0
  124. pythonagent/vendor/pympler/mprofile.py +97 -0
  125. pythonagent/vendor/pympler/muppy.py +275 -0
  126. pythonagent/vendor/pympler/panels.py +115 -0
  127. pythonagent/vendor/pympler/process.py +238 -0
  128. pythonagent/vendor/pympler/py.typed +0 -0
  129. pythonagent/vendor/pympler/refbrowser.py +451 -0
  130. pythonagent/vendor/pympler/refgraph.py +350 -0
  131. pythonagent/vendor/pympler/summary.py +321 -0
  132. pythonagent/vendor/pympler/tracker.py +267 -0
  133. pythonagent/vendor/pympler/util/__init__.py +0 -0
  134. pythonagent/vendor/pympler/util/bottle.py +3809 -0
  135. pythonagent/vendor/pympler/util/compat.py +23 -0
  136. pythonagent/vendor/pympler/util/stringutils.py +77 -0
  137. pythonagent/vendor/pympler/web.py +346 -0
  138. pythonagent/vendor/werkzeug/__init__.py +20 -0
  139. pythonagent/vendor/werkzeug/_compat.py +228 -0
  140. pythonagent/vendor/werkzeug/_internal.py +473 -0
  141. pythonagent/vendor/werkzeug/_reloader.py +341 -0
  142. pythonagent/vendor/werkzeug/datastructures.py +3120 -0
  143. pythonagent/vendor/werkzeug/debug/__init__.py +498 -0
  144. pythonagent/vendor/werkzeug/debug/console.py +218 -0
  145. pythonagent/vendor/werkzeug/debug/repr.py +297 -0
  146. pythonagent/vendor/werkzeug/debug/tbtools.py +628 -0
  147. pythonagent/vendor/werkzeug/exceptions.py +829 -0
  148. pythonagent/vendor/werkzeug/filesystem.py +64 -0
  149. pythonagent/vendor/werkzeug/formparser.py +584 -0
  150. pythonagent/vendor/werkzeug/http.py +1307 -0
  151. pythonagent/vendor/werkzeug/local.py +420 -0
  152. pythonagent/vendor/werkzeug/middleware/__init__.py +25 -0
  153. pythonagent/vendor/werkzeug/middleware/dispatcher.py +66 -0
  154. pythonagent/vendor/werkzeug/middleware/http_proxy.py +219 -0
  155. pythonagent/vendor/werkzeug/middleware/lint.py +408 -0
  156. pythonagent/vendor/werkzeug/middleware/profiler.py +132 -0
  157. pythonagent/vendor/werkzeug/middleware/proxy_fix.py +169 -0
  158. pythonagent/vendor/werkzeug/middleware/shared_data.py +293 -0
  159. pythonagent/vendor/werkzeug/posixemulation.py +117 -0
  160. pythonagent/vendor/werkzeug/routing.py +2210 -0
  161. pythonagent/vendor/werkzeug/security.py +249 -0
  162. pythonagent/vendor/werkzeug/serving.py +1117 -0
  163. pythonagent/vendor/werkzeug/test.py +1123 -0
  164. pythonagent/vendor/werkzeug/testapp.py +241 -0
  165. pythonagent/vendor/werkzeug/urls.py +1138 -0
  166. pythonagent/vendor/werkzeug/useragents.py +202 -0
  167. pythonagent/vendor/werkzeug/utils.py +778 -0
  168. pythonagent/vendor/werkzeug/wrappers/__init__.py +36 -0
  169. pythonagent/vendor/werkzeug/wrappers/accept.py +50 -0
  170. pythonagent/vendor/werkzeug/wrappers/auth.py +33 -0
  171. pythonagent/vendor/werkzeug/wrappers/base_request.py +673 -0
  172. pythonagent/vendor/werkzeug/wrappers/base_response.py +700 -0
  173. pythonagent/vendor/werkzeug/wrappers/common_descriptors.py +341 -0
  174. pythonagent/vendor/werkzeug/wrappers/cors.py +100 -0
  175. pythonagent/vendor/werkzeug/wrappers/etag.py +304 -0
  176. pythonagent/vendor/werkzeug/wrappers/json.py +145 -0
  177. pythonagent/vendor/werkzeug/wrappers/request.py +49 -0
  178. pythonagent/vendor/werkzeug/wrappers/response.py +84 -0
  179. pythonagent/vendor/werkzeug/wrappers/user_agent.py +14 -0
  180. pythonagent/vendor/werkzeug/wsgi.py +1000 -0
@@ -0,0 +1,80 @@
1
+
2
+ from .refgraph import ReferenceGraph
3
+ from .util.stringutils import trunc, pp
4
+
5
+ import sys
6
+ import gc
7
+
8
+ __all__ = ['GarbageGraph', 'start_debug_garbage', 'end_debug_garbage']
9
+
10
+
11
+ class GarbageGraph(ReferenceGraph):
12
+ """
13
+ The ``GarbageGraph`` is a ``ReferenceGraph`` that illustrates the objects
14
+ building reference cycles. The garbage collector is switched to debug mode
15
+ (all identified garbage is stored in `gc.garbage`) and the garbage
16
+ collector is invoked. The collected objects are then illustrated in a
17
+ directed graph.
18
+
19
+ Large graphs can be reduced to the actual cycles by passing ``reduce=True``
20
+ to the constructor.
21
+
22
+ It is recommended to disable the garbage collector when using the
23
+ ``GarbageGraph``.
24
+
25
+ >>> from pympler.garbagegraph import GarbageGraph, start_debug_garbage
26
+ >>> start_debug_garbage()
27
+ >>> l = []
28
+ >>> l.append(l)
29
+ >>> del l
30
+ >>> gb = GarbageGraph()
31
+ >>> gb.render('garbage.eps')
32
+ True
33
+ """
34
+ def __init__(self, reduce=False, collectable=True):
35
+ """
36
+ Initialize the GarbageGraph with the objects identified by the garbage
37
+ collector. If `collectable` is true, every reference cycle is recorded.
38
+ Otherwise only uncollectable objects are reported.
39
+ """
40
+ if collectable:
41
+ gc.set_debug(gc.DEBUG_SAVEALL)
42
+ else:
43
+ gc.set_debug(0)
44
+ gc.collect()
45
+
46
+ ReferenceGraph.__init__(self, gc.garbage, reduce)
47
+
48
+ def print_stats(self, stream=None):
49
+ """
50
+ Log annotated garbage objects to console or file.
51
+
52
+ :param stream: open file, uses sys.stdout if not given
53
+ """
54
+ if not stream: # pragma: no cover
55
+ stream = sys.stdout
56
+ self.metadata.sort(key=lambda x: -x.size)
57
+ stream.write('%-10s %8s %-12s %-46s\n' % ('id', 'size', 'type',
58
+ 'representation'))
59
+ for g in self.metadata:
60
+ stream.write('0x%08x %8d %-12s %-46s\n' % (g.id, g.size,
61
+ trunc(g.type, 12),
62
+ trunc(g.str, 46)))
63
+ stream.write('Garbage: %8d collected objects (%s in cycles): %12s\n' %
64
+ (self.count, self.num_in_cycles, pp(self.total_size)))
65
+
66
+
67
+ def start_debug_garbage():
68
+ """
69
+ Turn off garbage collector to analyze *collectable* reference cycles.
70
+ """
71
+ gc.collect()
72
+ gc.disable()
73
+
74
+
75
+ def end_debug_garbage():
76
+ """
77
+ Turn garbage collection on and disable debug output.
78
+ """
79
+ gc.set_debug(0)
80
+ gc.enable()
@@ -0,0 +1,97 @@
1
+ """
2
+ Memory usage profiler for Python.
3
+
4
+ """
5
+ import inspect
6
+ import sys
7
+
8
+ from . import muppy
9
+
10
+
11
+ class MProfiler(object):
12
+ """A memory usage profiler class.
13
+
14
+ Memory data for each function is stored as a 3-element list in the
15
+ dictionary self.memories. The index is always a codepoint (see below).
16
+ The following are the definitions of the members:
17
+
18
+ [0] = The number of times this function was called
19
+ [1] = Minimum memory consumption when this function was measured.
20
+ [2] = Maximum memory consumption when this function was measured.
21
+
22
+ A codepoint is a list of 3-tuple of the type
23
+ (filename, functionname, linenumber). You can omit either element, which
24
+ will cause the profiling to be triggered if any of the other criteria
25
+ match. E.g.
26
+ - (None, foo, None), will profile any foo function,
27
+ - (bar, foo, None) will profile only the foo function from the bar file,
28
+ - (bar, foo, 17) will profile only line 17 of the foo function defined
29
+ in the file bar.
30
+
31
+ Additionally, you can define on what events you want the profiling be
32
+ triggered. Possible events are defined in
33
+ http://docs.python.org/lib/debugger-hooks.html.
34
+
35
+ If you do not define either codepoints or events, the profiler will
36
+ record the memory usage in at every codepoint and event.
37
+
38
+ """
39
+
40
+ def __init__(self, codepoints=None, events=None):
41
+ """
42
+ keyword arguments:
43
+ codepoints -- a list of points in code to monitor (defaults to all
44
+ codepoints)
45
+ events -- a list of events to monitor (defaults to all events)
46
+ """
47
+ self.memories = {}
48
+ self.codepoints = codepoints
49
+ self.events = events
50
+
51
+ def codepoint_included(self, codepoint):
52
+ """Check if codepoint matches any of the defined codepoints."""
53
+ if self.codepoints is None:
54
+ return True
55
+ for cp in self.codepoints:
56
+ mismatch = False
57
+ for i in range(len(cp)):
58
+ if (cp[i] is not None) and (cp[i] != codepoint[i]):
59
+ mismatch = True
60
+ break
61
+ if not mismatch:
62
+ return True
63
+ return False
64
+
65
+ def profile(self, frame, event, arg): # arg req to match signature
66
+ """Profiling method used to profile matching codepoints and events."""
67
+ if (self.events is None) or (event in self.events):
68
+ frame_info = inspect.getframeinfo(frame)
69
+ cp = (frame_info[0], frame_info[2], frame_info[1])
70
+ if self.codepoint_included(cp):
71
+ objects = muppy.get_objects()
72
+ size = muppy.get_size(objects)
73
+ if cp not in self.memories:
74
+ self.memories[cp] = [0, 0, 0, 0]
75
+ self.memories[cp][0] = 1
76
+ self.memories[cp][1] = size
77
+ self.memories[cp][2] = size
78
+ else:
79
+ self.memories[cp][0] += 1
80
+ if self.memories[cp][1] > size:
81
+ self.memories[cp][1] = size
82
+ if self.memories[cp][2] < size:
83
+ self.memories[cp][2] = size
84
+
85
+ def run(self, cmd):
86
+ sys.setprofile(self.profile)
87
+ try:
88
+ exec(cmd)
89
+ finally:
90
+ sys.setprofile(None)
91
+ return self
92
+
93
+
94
+ if __name__ == "__main__":
95
+ p = MProfiler()
96
+ p.run("print('hello')")
97
+ print(p.memories)
@@ -0,0 +1,275 @@
1
+ from typing import Any, Callable, Dict, List, Optional, Set, Tuple
2
+
3
+ import gc
4
+
5
+ from . import summary
6
+ from .util import compat
7
+
8
+ from inspect import isframe, stack
9
+
10
+ from sys import getsizeof
11
+
12
+ from .asizeof import _Py_TPFLAGS_HAVE_GC
13
+
14
+
15
+ def ignore_object(obj: Any) -> bool:
16
+ try:
17
+ return isframe(obj)
18
+ except ReferenceError:
19
+ return True
20
+
21
+
22
+ def get_objects(remove_dups: bool = True, include_frames: bool = False
23
+ ) -> List[Any]:
24
+ """Return a list of all known objects excluding frame objects.
25
+
26
+ If (outer) frame objects shall be included, pass `include_frames=True`. In
27
+ order to prevent building reference cycles, the current frame object (of
28
+ the caller of get_objects) is ignored. This will not prevent creating
29
+ reference cycles if the object list is passed up the call-stack. Therefore,
30
+ frame objects are not included by default.
31
+
32
+ Keyword arguments:
33
+ remove_dups -- if True, all duplicate objects will be removed.
34
+ include_frames -- if True, includes frame objects.
35
+ """
36
+ gc.collect()
37
+
38
+ # Do not initialize local variables before calling gc.get_objects or those
39
+ # will be included in the list. Furthermore, ignore frame objects to
40
+ # prevent reference cycles.
41
+ tmp = gc.get_objects()
42
+ tmp = [o for o in tmp if not ignore_object(o)]
43
+
44
+ res = []
45
+ for o in tmp:
46
+ # gc.get_objects returns only container objects, but we also want
47
+ # the objects referenced by them
48
+ refs = get_referents(o)
49
+ for ref in refs:
50
+ if not gc.is_tracked(ref):
51
+ # we already got the container objects, now we only add
52
+ # non-container objects
53
+ res.append(ref)
54
+ res.extend(tmp)
55
+ if remove_dups:
56
+ res = _remove_duplicates(res)
57
+
58
+ if include_frames:
59
+ for sf in stack()[2:]:
60
+ res.append(sf[0])
61
+ return res
62
+
63
+
64
+ def get_size(objects: List[Any]) -> int:
65
+ """Compute the total size of all elements in objects."""
66
+ res = 0
67
+ for o in objects:
68
+ try:
69
+ res += getsizeof(o)
70
+ except AttributeError:
71
+ print("IGNORING: type=%s; o=%s" % (str(type(o)), str(o)))
72
+ return res
73
+
74
+
75
+ def get_diff(left: List[Any], right: List[Any]) -> Dict[str, List[Any]]:
76
+ """Get the difference of both lists.
77
+
78
+ The result will be a dict with this form {'+': [], '-': []}.
79
+ Items listed in '+' exist only in the right list,
80
+ items listed in '-' exist only in the left list.
81
+
82
+ """
83
+ res = {'+': [], '-': []} # type: Dict[str, List[Any]]
84
+
85
+ def partition(objects: List[Any]) -> Dict[type, List[Any]]:
86
+ """Partition the passed object list."""
87
+ res = {} # type: Dict[type, List[Any]]
88
+ for o in objects:
89
+ t = type(o)
90
+ if type(o) not in res:
91
+ res[t] = []
92
+ res[t].append(o)
93
+ return res
94
+
95
+ def get_not_included(foo: List[Any], bar: Dict[type, List[Any]]
96
+ ) -> List[Any]:
97
+ """Compare objects from foo with objects defined in the values of
98
+ bar (set of partitions).
99
+ Returns a list of all objects included in list, but not dict values.
100
+ """
101
+ res = [] # type: List[Any]
102
+ for o in foo:
103
+ if not compat.object_in_list(type(o), bar):
104
+ res.append(o)
105
+ elif not compat.object_in_list(o, bar[type(o)]):
106
+ res.append(o)
107
+ return res
108
+
109
+ # Create partitions of both lists. This will reduce the time required for
110
+ # the comparison
111
+ left_objects = partition(left)
112
+ right_objects = partition(right)
113
+ # and then do the diff
114
+ res['+'] = get_not_included(right, left_objects)
115
+ res['-'] = get_not_included(left, right_objects)
116
+ return res
117
+
118
+
119
+ def sort(objects: List[Any]) -> List[Any]:
120
+ """Sort objects by size in bytes."""
121
+ objects = sorted(objects, key=getsizeof)
122
+ return objects
123
+
124
+
125
+ def filter(objects: List[Any], Type: Optional[type] = None, min: int = -1,
126
+ max: int = -1) -> List[Any]:
127
+ """Filter objects.
128
+
129
+ The filter can be by type, minimum size, and/or maximum size.
130
+
131
+ Keyword arguments:
132
+ Type -- object type to filter by
133
+ min -- minimum object size
134
+ max -- maximum object size
135
+
136
+ """
137
+ res = [] # type: List[Any]
138
+ if min > max and max > -1:
139
+ raise ValueError("minimum must be smaller than maximum")
140
+
141
+ if Type is not None:
142
+ objects = [o for o in objects if isinstance(o, Type)]
143
+ if min > -1:
144
+ objects = [o for o in objects if getsizeof(o) > min]
145
+ if max > -1:
146
+ objects = [o for o in objects if getsizeof(o) < max]
147
+ return objects
148
+
149
+
150
+ def get_referents(object: Any, level: int = 1) -> List[Any]:
151
+ """Get all referents of an object up to a certain level.
152
+
153
+ The referents will not be returned in a specific order and
154
+ will not contain duplicate objects. Duplicate objects will be removed.
155
+
156
+ Keyword arguments:
157
+ level -- level of indirection to which referents considered.
158
+
159
+ This function is recursive.
160
+
161
+ """
162
+ res = gc.get_referents(object)
163
+ level -= 1
164
+ if level > 0:
165
+ for o in res:
166
+ res.extend(get_referents(o, level))
167
+ res = _remove_duplicates(res)
168
+ return res
169
+
170
+
171
+ def _get_usage(function: Callable, *args: Any) -> Optional[List]:
172
+ """Test if more memory is used after the function has been called.
173
+
174
+ The function will be invoked twice and only the second measurement will be
175
+ considered. Thus, memory used in initialisation (e.g. loading modules)
176
+ will not be included in the result. The goal is to identify memory leaks
177
+ caused by functions which use more and more memory.
178
+
179
+ Any arguments next to the function will be passed on to the function
180
+ on invocation.
181
+
182
+ Note that this function is currently experimental, because it is not
183
+ tested thoroughly and performs poorly.
184
+
185
+ """
186
+ # The usage of a function is calculated by creating one summary of all
187
+ # objects before the function is invoked and afterwards. These summaries
188
+ # are compared and the diff is returned.
189
+ # This function works in a 2-steps process. Before the actual function is
190
+ # invoked an empty dummy function is measurement to identify the overhead
191
+ # involved in the measuring process. This overhead then is subtracted from
192
+ # the measurement performed on the passed function. The result reflects the
193
+ # actual usage of a function call.
194
+ # Also, a measurement is performed twice, allowing the adjustment to
195
+ # initializing things, e.g. modules
196
+
197
+ res = None
198
+
199
+ def _get_summaries(function: Callable, *args: Any) -> Tuple:
200
+ """Get a 2-tuple containing one summary from before, and one summary
201
+ from after the function has been invoked.
202
+
203
+ """
204
+ s_before = summary.summarize(get_objects())
205
+ function(*args)
206
+ s_after = summary.summarize(get_objects())
207
+ return (s_before, s_after)
208
+
209
+ def _get_usage(function: Callable, *args: Any) -> List:
210
+ """Get the usage of a function call.
211
+ This function is to be used only internally. The 'real' get_usage
212
+ function is a wrapper around _get_usage, but the workload is done
213
+ here.
214
+
215
+ """
216
+ # init before calling
217
+ (s_before, s_after) = _get_summaries(function, *args)
218
+ # ignore all objects used for the measurement
219
+ ignore = []
220
+ if s_before != s_after:
221
+ ignore.append(s_before)
222
+ for row in s_before:
223
+ # ignore refs from summary and frame (loop)
224
+ if len(gc.get_referrers(row)) == 2:
225
+ ignore.append(row)
226
+ for item in row:
227
+ # ignore refs from summary and frame (loop)
228
+ if len(gc.get_referrers(item)) == 2:
229
+ ignore.append(item)
230
+ for o in ignore:
231
+ s_after = summary._subtract(s_after, o)
232
+ res = summary.get_diff(s_before, s_after)
233
+ return summary._sweep(res)
234
+
235
+ # calibrate; twice for initialization
236
+ def noop() -> None:
237
+ pass
238
+ offset = _get_usage(noop)
239
+ offset = _get_usage(noop)
240
+ # perform operation twice to handle objects possibly used in
241
+ # initialisation
242
+ tmp = _get_usage(function, *args)
243
+ tmp = _get_usage(function, *args)
244
+ tmp = summary.get_diff(offset, tmp)
245
+ tmp = summary._sweep(tmp)
246
+ if len(tmp) != 0:
247
+ res = tmp
248
+ return res
249
+
250
+
251
+ def _is_containerobject(o: Any) -> bool:
252
+ """Is the passed object a container object."""
253
+ return bool(getattr(type(o), '__flags__', 0) & _Py_TPFLAGS_HAVE_GC)
254
+
255
+
256
+ def _remove_duplicates(objects: List[Any]) -> List[Any]:
257
+ """Remove duplicate objects.
258
+
259
+ Inspired by http://www.peterbe.com/plog/uniqifiers-benchmark
260
+
261
+ """
262
+ seen = set() # type: Set[int]
263
+ result = []
264
+ for item in objects:
265
+ marker = id(item)
266
+ if marker in seen:
267
+ continue
268
+ seen.add(marker)
269
+ result.append(item)
270
+ return result
271
+
272
+
273
+ def print_summary() -> None:
274
+ """Print a summary of all known objects."""
275
+ summary.print_(summary.summarize(get_objects()))
@@ -0,0 +1,115 @@
1
+ """
2
+ Expose a memory-profiling panel to the Django Debug toolbar.
3
+
4
+ Shows process memory information (virtual size, resident set size) and model
5
+ instances for the current request.
6
+
7
+ Requires Django and Django Debug toolbar:
8
+
9
+ https://github.com/jazzband/django-debug-toolbar
10
+
11
+ Pympler adds a memory panel as a third party addon (not included in the
12
+ django-debug-toolbar). It can be added by overriding the `DEBUG_TOOLBAR_PANELS`
13
+ setting in the Django project settings::
14
+
15
+ DEBUG_TOOLBAR_PANELS = (
16
+ 'debug_toolbar.panels.timer.TimerDebugPanel',
17
+ 'pympler.panels.MemoryPanel',
18
+ )
19
+
20
+ Pympler also needs to be added to the `INSTALLED_APPS` in the Django settings::
21
+
22
+ INSTALLED_APPS = INSTALLED_APPS + ('debug_toolbar', 'pympler')
23
+ """
24
+
25
+ from .classtracker import ClassTracker
26
+ from .process import ProcessMemoryInfo
27
+ from .util.stringutils import pp
28
+
29
+ try:
30
+ from debug_toolbar.panels import Panel
31
+ from django.apps import apps
32
+ from django.template import Context, Template
33
+ from django.template.loader import render_to_string
34
+ from django.http.request import HttpRequest
35
+ from django.http.response import HttpResponse
36
+ except ImportError:
37
+ class Panel(object): # type: ignore
38
+ pass
39
+
40
+ class Template(object): # type: ignore
41
+ pass
42
+
43
+ class Context(object): # type: ignore
44
+ pass
45
+
46
+ class HttpRequest(object): # type: ignore
47
+ pass
48
+
49
+ class HttpResponse(object): # type: ignore
50
+ pass
51
+
52
+
53
+ class MemoryPanel(Panel):
54
+
55
+ name = 'pympler'
56
+
57
+ title = 'Memory'
58
+
59
+ template = 'memory_panel.html'
60
+
61
+ classes = [Context, Template]
62
+
63
+ def process_request(self, request: HttpRequest) -> HttpResponse:
64
+ self._tracker = ClassTracker()
65
+ for cls in apps.get_models() + self.classes:
66
+ self._tracker.track_class(cls)
67
+ self._tracker.create_snapshot('before')
68
+ self.record_stats({'before': ProcessMemoryInfo()})
69
+ response = super(MemoryPanel, self).process_request(request)
70
+ self.record_stats({'after': ProcessMemoryInfo()})
71
+ self._tracker.create_snapshot('after')
72
+ stats = self._tracker.stats
73
+ stats.annotate()
74
+ self.record_stats({'stats': stats})
75
+ return response
76
+
77
+ def enable_instrumentation(self) -> None:
78
+ self._tracker = ClassTracker()
79
+ for cls in apps.get_models() + self.classes:
80
+ self._tracker.track_class(cls)
81
+
82
+ def disable_instrumentation(self) -> None:
83
+ self._tracker.detach_all_classes()
84
+
85
+ def nav_subtitle(self) -> str:
86
+ context = self.get_stats()
87
+ before = context['before']
88
+ after = context['after']
89
+ rss = after.rss
90
+ delta = rss - before.rss
91
+ delta = ('(+%s)' % pp(delta)) if delta > 0 else ''
92
+ return "%s %s" % (pp(rss), delta)
93
+
94
+ @property
95
+ def content(self) -> str:
96
+ context = self.get_stats()
97
+ before = context['before']
98
+ after = context['after']
99
+ stats = context['stats']
100
+ rows = [('Resident set size', after.rss),
101
+ ('Virtual size', after.vsz),
102
+ ]
103
+ rows.extend(after - before)
104
+ rows = [(key, pp(value)) for key, value in rows]
105
+ rows.extend(after.os_specific)
106
+
107
+ classes = []
108
+ snapshot = stats.snapshots[-1]
109
+ for model in stats.tracked_classes:
110
+ history = [cnt for _, cnt in stats.history[model]]
111
+ size = snapshot.classes.get(model, {}).get('sum', 0)
112
+ if history and history[-1] > 0:
113
+ classes.append((model, history, pp(size)))
114
+ context.update({'rows': rows, 'classes': classes})
115
+ return render_to_string(self.template, context)