dbgr-server 1.1.0.dev1__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 (86) hide show
  1. dbgr_server/__init__.py +1 -0
  2. dbgr_server/__main__.py +61 -0
  3. dbgr_server/app.py +139 -0
  4. dbgr_server/constants.py +10 -0
  5. dbgr_server/routes.py +22 -0
  6. dbgr_server/static/fonts/MapleMono-NF-Bold.ttf +0 -0
  7. dbgr_server/static/fonts/MapleMono-NF-BoldItalic.ttf +0 -0
  8. dbgr_server/static/fonts/MapleMono-NF-Italic.ttf +0 -0
  9. dbgr_server/static/fonts/MapleMono-NF-Regular.ttf +0 -0
  10. dbgr_server/static/images/favicon.ico +0 -0
  11. dbgr_server/static/javascripts/dist/0d8bb5b3ee5f5dac9e44.woff2 +0 -0
  12. dbgr_server/static/javascripts/dist/16b1558858fe1064b5af.woff +0 -0
  13. dbgr_server/static/javascripts/dist/176f8f5bd5f02b3abfcf.woff2 +0 -0
  14. dbgr_server/static/javascripts/dist/18d00f739ff1e1c52db1.woff +0 -0
  15. dbgr_server/static/javascripts/dist/1b3b4f49d5b3b3fb2c20.jpg +0 -0
  16. dbgr_server/static/javascripts/dist/1ce08cfde36c0d81af5b.woff +0 -0
  17. dbgr_server/static/javascripts/dist/213eedde97d22010287c.woff +0 -0
  18. dbgr_server/static/javascripts/dist/2267169ee7270a22a963.woff +0 -0
  19. dbgr_server/static/javascripts/dist/24a9d3bbf9b715214bf5.woff +0 -0
  20. dbgr_server/static/javascripts/dist/2900d24e7347deaf65cd.woff +0 -0
  21. dbgr_server/static/javascripts/dist/2ed4651232a34d737777.woff +0 -0
  22. dbgr_server/static/javascripts/dist/37a7069dc30fc663c878.woff2 +0 -0
  23. dbgr_server/static/javascripts/dist/451d4e559d6f57cdf6a1.woff +0 -0
  24. dbgr_server/static/javascripts/dist/4674f8ded773cb03e824.eot +0 -0
  25. dbgr_server/static/javascripts/dist/498042b7fe9cd07b4fd1.woff +0 -0
  26. dbgr_server/static/javascripts/dist/49ae34d4cc6b98c00c69.woff +0 -0
  27. dbgr_server/static/javascripts/dist/51aa168dd4ba03bd83f0.woff2 +0 -0
  28. dbgr_server/static/javascripts/dist/535bc89d4af715503b01.woff2 +0 -0
  29. dbgr_server/static/javascripts/dist/53b43867955fbecc811a.woff2 +0 -0
  30. dbgr_server/static/javascripts/dist/5e7382c63da0098d634a.ttf +0 -0
  31. dbgr_server/static/javascripts/dist/640f8692dc49a19f1541.woff +0 -0
  32. dbgr_server/static/javascripts/dist/79f21d796f266ce0ea70.woff +0 -0
  33. dbgr_server/static/javascripts/dist/7d8125ff7f707231fd89.woff2 +0 -0
  34. dbgr_server/static/javascripts/dist/7f7e9cd596438fea4b02.woff2 +0 -0
  35. dbgr_server/static/javascripts/dist/7f839a8652da29745ce4.woff2 +0 -0
  36. dbgr_server/static/javascripts/dist/83bebaf37c09c7e1c3ee.woff +0 -0
  37. dbgr_server/static/javascripts/dist/865f928cbabcc9f8f2b5.woff +0 -0
  38. dbgr_server/static/javascripts/dist/870c8c1486f76054301a.woff2 +0 -0
  39. dbgr_server/static/javascripts/dist/9216be4e13fe7dbe70ef.woff2 +0 -0
  40. dbgr_server/static/javascripts/dist/9360531f9bb817f917f0.woff +0 -0
  41. dbgr_server/static/javascripts/dist/969a44e1b6fc26105bd6.woff2 +0 -0
  42. dbgr_server/static/javascripts/dist/a45108d3b34af91f9113.woff +0 -0
  43. dbgr_server/static/javascripts/dist/b1d9d9904bfca8802a63.woff +0 -0
  44. dbgr_server/static/javascripts/dist/b39a11f9a6107e57a21f.woff2 +0 -0
  45. dbgr_server/static/javascripts/dist/bac8362e7a6ea60b6983.woff +0 -0
  46. dbgr_server/static/javascripts/dist/bbbf6951525a78fff340.woff +0 -0
  47. dbgr_server/static/javascripts/dist/bc628a95b5026af0df2c.woff2 +0 -0
  48. dbgr_server/static/javascripts/dist/bd5b7a13f2c52b531a2a.woff +0 -0
  49. dbgr_server/static/javascripts/dist/bf4b4e73015a3066da91.woff +0 -0
  50. dbgr_server/static/javascripts/dist/bfe6eaec56c141a0bd72.woff2 +0 -0
  51. dbgr_server/static/javascripts/dist/c18ee39fb002ad58b6dc.woff2 +0 -0
  52. dbgr_server/static/javascripts/dist/c1f17364fee75703a7ee.woff2 +0 -0
  53. dbgr_server/static/javascripts/dist/c20d916c1a1b094c1cec.woff +0 -0
  54. dbgr_server/static/javascripts/dist/c2aa4ab115bf9c6057cb.woff2 +0 -0
  55. dbgr_server/static/javascripts/dist/c5c3959c04004102ea46.woff2 +0 -0
  56. dbgr_server/static/javascripts/dist/c64e7e354c88e613c77c.woff2 +0 -0
  57. dbgr_server/static/javascripts/dist/ca100b2be6274105bb16.woff +0 -0
  58. dbgr_server/static/javascripts/dist/cb5ad999740e9d8a8bd1.woff2 +0 -0
  59. dbgr_server/static/javascripts/dist/cc9e42999888377183f8.woff2 +0 -0
  60. dbgr_server/static/javascripts/dist/cea99d3e3e13a3a599a0.woff +0 -0
  61. dbgr_server/static/javascripts/dist/cff684e59ffb052d72cb.woff2 +0 -0
  62. dbgr_server/static/javascripts/dist/d022bc70dc1bf7b3425d.woff2 +0 -0
  63. dbgr_server/static/javascripts/dist/d97850bb044fb5584361.woff2 +0 -0
  64. dbgr_server/static/javascripts/dist/da65dcd09cdc4863ece2.woff +0 -0
  65. dbgr_server/static/javascripts/dist/dbgr.js +1463 -0
  66. dbgr_server/static/javascripts/dist/eba647ae67ba7dc20f05.woff +0 -0
  67. dbgr_server/static/javascripts/dist/f5b74d7ffcdf85b9dd60.woff2 +0 -0
  68. dbgr_server/static/javascripts/dist/ff2c5c919a01e9ddda85.woff2 +0 -0
  69. dbgr_server/static/javascripts/dist/home.js +1037 -0
  70. dbgr_server/static/libs/material-design-lite/material.indigo-red.min.css +8 -0
  71. dbgr_server/templates/_layout.html +17 -0
  72. dbgr_server/templates/dbgr.html +138 -0
  73. dbgr_server/templates/home.html +115 -0
  74. dbgr_server/utils/__init__.py +0 -0
  75. dbgr_server/utils/state.py +249 -0
  76. dbgr_server/utils/streams.py +151 -0
  77. dbgr_server/utils/technical.py +132 -0
  78. dbgr_server/views.py +298 -0
  79. dbgr_server-1.1.0.dev1.data/scripts/dbgr.server.py +105 -0
  80. dbgr_server-1.1.0.dev1.dist-info/METADATA +534 -0
  81. dbgr_server-1.1.0.dev1.dist-info/RECORD +86 -0
  82. dbgr_server-1.1.0.dev1.dist-info/WHEEL +5 -0
  83. dbgr_server-1.1.0.dev1.dist-info/licenses/LICENSE +674 -0
  84. dbgr_server-1.1.0.dev1.dist-info/top_level.txt +2 -0
  85. tests/__init__.py +0 -0
  86. tests/test_app.py +84 -0
@@ -0,0 +1,249 @@
1
+ # Stdlib:
2
+ import json
3
+ import logging
4
+ from struct import pack
5
+ from typing import Any, Dict, List, Optional, Set, Union
6
+
7
+ log = logging.getLogger("dbgr_server")
8
+
9
+
10
+ class BaseSockets:
11
+ """
12
+ Base class for implement protocol for TCP/Web Socket.
13
+ """
14
+
15
+ __slots__ = ["_sockets"]
16
+
17
+ def __init__(self) -> None:
18
+ self._sockets: Dict[str, Any] = {}
19
+
20
+ async def send(
21
+ self,
22
+ uuid: str,
23
+ data: str,
24
+ message: Optional[Union[Dict[str, Any], List[int], str]] = None,
25
+ ) -> None:
26
+ """
27
+ Send messsage to TCP/Web Socket by uuid.
28
+ """
29
+ if message:
30
+ data = data + "|" + json.dumps(message)
31
+ sck = self.get(uuid)
32
+ if sck is not None:
33
+ await self._send(sck, data)
34
+ else:
35
+ log.warning("No socket found for %s", uuid)
36
+
37
+ async def _send(self, sck: Any, data: str) -> None:
38
+ raise NotImplementedError
39
+
40
+ def get(self, uuid: str) -> Any:
41
+ """
42
+ Simple getter for retrieve TCP/Web Socket by uuid.
43
+ """
44
+ return self._sockets.get(uuid)
45
+
46
+ async def broadcast(
47
+ self,
48
+ cmd: str,
49
+ message: Optional[Union[Dict[str, Any], List[int], str]] = None,
50
+ ) -> None:
51
+ """
52
+ Broadcast message to TCP/Web Socket.
53
+ """
54
+ for uuid in list(self._sockets.keys()):
55
+ try:
56
+ log.debug("Broadcast to socket %s", uuid)
57
+ await self.send(uuid, cmd, message)
58
+ except Exception: # pylint: disable=broad-except
59
+ log.warning("Failed broadcast to socket %s", uuid)
60
+ await self.close(uuid)
61
+ await self.remove(uuid)
62
+
63
+ async def add(self, uuid: str, sck: Any) -> None:
64
+ """
65
+ Function which add/re-add TCP/Web Socket to store.
66
+ """
67
+ if uuid in self._sockets:
68
+ await self.remove(uuid)
69
+ await self.close(uuid)
70
+
71
+ self._sockets[uuid] = sck
72
+
73
+ async def remove(self, uuid: str) -> None:
74
+ """
75
+ Function which remove TCP/Web Socket from store.
76
+ """
77
+ sck = self._sockets.pop(uuid, None)
78
+ if sck:
79
+ await syncwebsockets.broadcast(
80
+ "Remove" + self.__class__.__name__.rstrip("s"), uuid
81
+ )
82
+
83
+ async def close(self, uuid: str) -> None:
84
+ """
85
+ Function which close TCP/Web Socket.
86
+ """
87
+ sck = self.get(uuid)
88
+ if sck is not None:
89
+ try:
90
+ await sck.close()
91
+ except Exception: # pylint: disable=broad-except
92
+ log.warning("Failed close to socket %s", uuid)
93
+
94
+ @property
95
+ def uuids(self) -> Set[str]:
96
+ """
97
+ Simple property to return all UUID's.
98
+ """
99
+ return set(self._sockets.keys())
100
+
101
+
102
+ class Sockets(BaseSockets):
103
+ """
104
+ General store for active TCP Sockets.
105
+ """
106
+
107
+ __slots__ = ["_sockets", "_filenames"]
108
+
109
+ def __init__(self) -> None:
110
+ super().__init__()
111
+ self._filenames: Dict[str, Any] = {}
112
+
113
+ async def add(self, uuid: str, sck: Any) -> None:
114
+ await super().add(uuid, sck)
115
+ await syncwebsockets.broadcast("AddSocket", {"uuid": uuid})
116
+
117
+ async def remove(self, uuid: str) -> None:
118
+ await super().remove(uuid)
119
+ self._filenames.pop(uuid, None)
120
+
121
+ def get_filename(self, uuid: str) -> str:
122
+ """
123
+ Simple getter for retrieve filename by uuid.
124
+ """
125
+ return self._filenames.get(uuid, "")
126
+
127
+ async def set_filename(self, uuid: str, filename: str) -> None:
128
+ """
129
+ Simple setter for set filename for uuid.
130
+ """
131
+ self._filenames[uuid] = filename
132
+ await syncwebsockets.broadcast(
133
+ "AddSocket",
134
+ {
135
+ "uuid": uuid,
136
+ "filename": filename if settings.show_filename else "",
137
+ },
138
+ )
139
+
140
+ async def _send(self, sck: Any, data: str) -> None:
141
+ await sck.write(pack("!i", len(data)))
142
+ await sck.write(data.encode("utf-8"))
143
+
144
+
145
+ class BaseWebSockets(BaseSockets):
146
+ """
147
+ Base class for implement WebSockets/SyncWebSockets
148
+ """
149
+
150
+ __slots__ = ["_sockets"]
151
+
152
+ async def _send(self, sck: Any, data: str) -> None:
153
+ if hasattr(sck, "ws"):
154
+ await sck.write_message(data)
155
+ else:
156
+ log.warning("Websocket is closed")
157
+
158
+
159
+ class WebSockets(BaseWebSockets):
160
+ """
161
+ General store for active WebSockets.
162
+ """
163
+
164
+ __slots__ = ["_sockets"]
165
+
166
+ async def add(self, uuid: str, sck: Any) -> None:
167
+ await super().add(uuid, sck)
168
+ await syncwebsockets.broadcast("AddWebSocket", uuid)
169
+
170
+
171
+ class SyncWebSockets(BaseWebSockets):
172
+ """
173
+ General store for active synchronization WebSockets.
174
+ """
175
+
176
+ __slots__ = ["_sockets"]
177
+
178
+
179
+ class Breakpoints:
180
+ """
181
+ General store for active breakpoints.
182
+ """
183
+
184
+ __slots__ = ["_breakpoints"]
185
+
186
+ def __init__(self) -> None:
187
+ self._breakpoints: List[Any] = []
188
+
189
+ async def add(self, brk: Any) -> None:
190
+ """
191
+ Add breakpoint to store and broadcast messaage about it.
192
+ """
193
+ if brk not in self._breakpoints:
194
+ self._breakpoints.append(brk)
195
+ await syncwebsockets.broadcast("AddBreak|" + json.dumps(brk))
196
+
197
+ async def remove(self, brk: Any) -> None:
198
+ """
199
+ Remove breakpoint from store and broadcast messaage about it.
200
+ """
201
+ if brk in self._breakpoints:
202
+ self._breakpoints.remove(brk)
203
+ await syncwebsockets.broadcast("RemoveBreak|" + json.dumps(brk))
204
+
205
+ def get(self) -> List[Any]:
206
+ """
207
+ Simple getter for access to breakpoints store.
208
+ """
209
+ return self._breakpoints
210
+
211
+
212
+ # pylint: disable=too-few-public-methods
213
+ class Settings:
214
+ """
215
+ Simple class for store application settings
216
+ """
217
+
218
+ __slots__ = [
219
+ "debug",
220
+ "extra_search_path",
221
+ "more",
222
+ "detached_session",
223
+ "show_filename",
224
+ ]
225
+
226
+ def __init__(self, **kwargs: Any) -> None:
227
+ self.debug: bool = False
228
+ self.extra_search_path: bool = False
229
+ self.more: bool = False
230
+ self.detached_session: bool = False
231
+ self.show_filename: bool = False
232
+ self._set_params(**kwargs)
233
+
234
+ def update(self, **kwargs: Any) -> None:
235
+ """
236
+ Update attributes afteer init
237
+ """
238
+ self._set_params(**kwargs)
239
+
240
+ def _set_params(self, **kwargs: Any) -> None:
241
+ for name, value in kwargs.items():
242
+ setattr(self, name, value)
243
+
244
+
245
+ sockets = Sockets()
246
+ websockets = WebSockets()
247
+ syncwebsockets = SyncWebSockets()
248
+ breakpoints = Breakpoints()
249
+ settings = Settings()
@@ -0,0 +1,151 @@
1
+ # Stdlib:
2
+ import asyncio
3
+ import json
4
+ import logging
5
+ from asyncio.exceptions import IncompleteReadError
6
+ from struct import unpack
7
+
8
+ # Firstparty:
9
+ from dbgr_server.constants import UNKNOWN_UUID
10
+ from dbgr_server.utils.state import breakpoints, sockets, websockets
11
+
12
+ log = logging.getLogger("dbgr_server")
13
+
14
+
15
+ class IOStream:
16
+ """
17
+ Base class for interacting with reader/writer
18
+ """
19
+
20
+ __slots__ = ["_reader", "_writer", "_uuid"]
21
+
22
+ def __init__(
23
+ self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter
24
+ ) -> None:
25
+ self._reader: asyncio.StreamReader = reader
26
+ self._writer: asyncio.StreamWriter = writer
27
+ self._uuid: str = UNKNOWN_UUID
28
+
29
+ @property
30
+ def uuid(self) -> str:
31
+ """The uuid property."""
32
+ return self._uuid
33
+
34
+ @uuid.setter
35
+ def uuid(self, uuid: str) -> None:
36
+ self._uuid = uuid
37
+
38
+ async def readexactly(self, num_bytes: int) -> bytes:
39
+ """
40
+ Alias to asyncio.StreamReader.readexactly
41
+ """
42
+ return await self._reader.readexactly(num_bytes)
43
+
44
+ async def write(self, data: bytes) -> None:
45
+ """
46
+ Alias to asyncio.StreamWriter.write with drain after
47
+ """
48
+ self._writer.write(data)
49
+ await self._writer.drain()
50
+
51
+ async def close(self) -> None:
52
+ """
53
+ Close active socket
54
+ """
55
+ self._writer.close()
56
+ try:
57
+ await self._writer.wait_closed()
58
+ except BrokenPipeError:
59
+ pass
60
+ finally:
61
+ await self.on_close()
62
+
63
+ async def on_close(self) -> None:
64
+ """
65
+ Clean store and broadcast messages
66
+ """
67
+ # None if the user closed the window
68
+ log.info("uuid %s closed", self.uuid)
69
+ if websockets.get(self.uuid):
70
+ try:
71
+ await websockets.send(self.uuid, "Die")
72
+ except ConnectionResetError:
73
+ log.warning("Closed stream for %s", self.uuid)
74
+ await websockets.close(self.uuid)
75
+ await websockets.remove(self.uuid)
76
+ await sockets.remove(self.uuid)
77
+
78
+
79
+ async def read_frame(stream: IOStream, uuid: str, frame: bytes) -> None:
80
+ """
81
+ Decode frame and process.
82
+ """
83
+ decoded_frame = frame.decode("utf-8")
84
+ if decoded_frame == "ServerBreaks":
85
+ await sockets.send(uuid, json.dumps(breakpoints.get()))
86
+ elif decoded_frame == "PING":
87
+ log.info("%s PONG", uuid)
88
+ elif decoded_frame.startswith("UPDATE_FILENAME"):
89
+ filename = decoded_frame.split("|", 1)[1]
90
+ await sockets.set_filename(uuid, filename)
91
+ else:
92
+ await websockets.send(uuid, frame.decode("utf-8"))
93
+ try:
94
+ await read_header(stream, uuid, await stream.readexactly(4))
95
+ except IncompleteReadError:
96
+ log.warning("Closed stream for %s", uuid)
97
+
98
+
99
+ async def read_header(stream: IOStream, uuid: str, _length: bytes) -> None:
100
+ """
101
+ Read header data and process frame.
102
+ """
103
+ (length,) = unpack("!i", _length)
104
+ try:
105
+ await read_frame(stream, uuid, await stream.readexactly(length))
106
+ except IncompleteReadError:
107
+ log.warning("Closed stream for %s", uuid)
108
+
109
+
110
+ async def assign_stream(stream: IOStream, _uuid: bytes) -> None:
111
+ """
112
+ Assign IOStream and socket.
113
+ """
114
+ uuid = _uuid.decode("utf-8")
115
+ log.debug("Assigning stream to %s", uuid)
116
+ await sockets.add(uuid, stream)
117
+ stream.uuid = uuid
118
+ try:
119
+ await read_header(stream, uuid, await stream.readexactly(4))
120
+ except IncompleteReadError:
121
+ log.warning("Closed stream for %s", uuid)
122
+
123
+
124
+ async def read_uuid_size(stream: IOStream, _length: bytes) -> None:
125
+ """
126
+ Read UUID and go ahead.
127
+ """
128
+ (length,) = unpack("!i", _length)
129
+ assert length == 36, "Wrong uuid"
130
+ try:
131
+ await assign_stream(stream, await stream.readexactly(length))
132
+ except IncompleteReadError:
133
+ log.warning("Closed stream for getting uuid")
134
+
135
+
136
+ async def handle_tcp_connection(
137
+ reader: asyncio.StreamReader, writer: asyncio.StreamWriter
138
+ ) -> None:
139
+ """
140
+ General handler function to handle input TCP connections.
141
+ """
142
+ address = writer.get_extra_info("peername")[0]
143
+ log.info("Connection received from %s", str(address))
144
+ stream = IOStream(reader, writer)
145
+ try:
146
+ await read_uuid_size(stream, await stream.readexactly(4))
147
+ except IncompleteReadError:
148
+ log.warning("Closed stream for getting uuid length")
149
+ finally:
150
+ await stream.close()
151
+ del stream
@@ -0,0 +1,132 @@
1
+ # pylint: disable=missing-class-docstring
2
+ # pylint: disable=missing-function-docstring
3
+ # pylint: disable=unsubscriptable-object
4
+ # Stdlib:
5
+ import asyncio
6
+ import logging
7
+ from glob import glob
8
+ from pathlib import Path
9
+ from typing import Any, Dict, List, Optional, Union
10
+
11
+ # Thirdparty:
12
+ import psutil
13
+
14
+ # Firstparty:
15
+ from dbgr_server.constants import UNKNOWN_UUID
16
+ from dbgr_server.utils.state import syncwebsockets
17
+
18
+ log = logging.getLogger("dbgr_server")
19
+
20
+ try:
21
+ # Thirdparty:
22
+ import pyinotify
23
+ except ImportError:
24
+ LibPythonWatcher = None # pylint: disable=invalid-name
25
+ else:
26
+
27
+ class LibPythonWatcher: # type: ignore # pragma: no cover
28
+ def __init__(self, extra_search_path: Optional[str] = None) -> None:
29
+ inotify = pyinotify.WatchManager()
30
+ self.files = glob("/usr/lib/libpython*")
31
+ if not self.files:
32
+ self.files = glob("/lib/libpython*")
33
+
34
+ if extra_search_path is not None:
35
+ # Handle custom installation paths
36
+ for file in Path(extra_search_path).rglob("libpython*"):
37
+ self.files.append(str(file.resolve()))
38
+
39
+ log.debug("Watching for %s", self.files)
40
+ self.notifier = pyinotify.AsyncioNotifier(
41
+ inotify,
42
+ asyncio.get_event_loop(),
43
+ self.notified,
44
+ pyinotify.ProcessEvent(),
45
+ )
46
+ inotify.add_watch(
47
+ self.files,
48
+ pyinotify.EventsCodes.ALL_FLAGS["IN_OPEN"]
49
+ | pyinotify.EventsCodes.ALL_FLAGS["IN_CLOSE_NOWRITE"],
50
+ )
51
+
52
+ # pylint: disable=unused-argument
53
+ async def notified(self, notifier: Any) -> None:
54
+ log.debug("Got notified for %s", self.files)
55
+ await refresh_process()
56
+ log.debug("Process refreshed")
57
+
58
+ def close(self) -> None:
59
+ log.debug("Closing for %s", self.files)
60
+ self.notifier.stop()
61
+
62
+
63
+ async def refresh_process(uuid: str = UNKNOWN_UUID) -> None:
64
+
65
+ if uuid != UNKNOWN_UUID:
66
+
67
+ async def send(
68
+ data: str,
69
+ message: Optional[Union[Dict[str, Any], List[int], str]] = None,
70
+ ) -> None:
71
+ await syncwebsockets.send(uuid, data, message)
72
+
73
+ else:
74
+
75
+ async def send(
76
+ data: str,
77
+ message: Optional[Union[Dict[str, Any], List[int], str]] = None,
78
+ ) -> None:
79
+ await syncwebsockets.broadcast(data, message)
80
+
81
+ remaining_pids = []
82
+ remaining_tids = []
83
+ for proc in psutil.process_iter():
84
+ try:
85
+ cl = proc.cmdline() # pylint: disable=invalid-name
86
+ except (
87
+ psutil.ZombieProcess,
88
+ psutil.AccessDenied,
89
+ psutil.NoSuchProcess,
90
+ ):
91
+ continue
92
+ else:
93
+ if len(cl) == 0:
94
+ continue
95
+
96
+ binary = cl[0].split("/")[-1]
97
+ if (
98
+ ("python" in binary or "pypy" in binary)
99
+ and proc.is_running()
100
+ and proc.status() != psutil.STATUS_ZOMBIE
101
+ ):
102
+ try:
103
+ try:
104
+ cpu = proc.cpu_percent(interval=0.01)
105
+ await send(
106
+ "AddProcess",
107
+ {
108
+ "pid": proc.pid,
109
+ "user": proc.username(),
110
+ "cmd": " ".join(proc.cmdline()),
111
+ "threads": proc.num_threads(),
112
+ "time": proc.create_time(),
113
+ "mem": proc.memory_percent(),
114
+ "cpu": cpu,
115
+ },
116
+ )
117
+ remaining_pids.append(proc.pid)
118
+ for thread in proc.threads():
119
+ await send(
120
+ "AddThread", {"id": thread.id, "of": proc.pid}
121
+ )
122
+ remaining_tids.append(thread.id)
123
+ except psutil.NoSuchProcess:
124
+ pass
125
+ except psutil.AccessDenied:
126
+ pass
127
+ except Exception: # pylint: disable=broad-except
128
+ log.warning("", exc_info=True)
129
+ continue
130
+
131
+ await send("KeepProcess", remaining_pids)
132
+ await send("KeepThreads", remaining_tids)