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.
- dbgr_server/__init__.py +1 -0
- dbgr_server/__main__.py +61 -0
- dbgr_server/app.py +139 -0
- dbgr_server/constants.py +10 -0
- dbgr_server/routes.py +22 -0
- dbgr_server/static/fonts/MapleMono-NF-Bold.ttf +0 -0
- dbgr_server/static/fonts/MapleMono-NF-BoldItalic.ttf +0 -0
- dbgr_server/static/fonts/MapleMono-NF-Italic.ttf +0 -0
- dbgr_server/static/fonts/MapleMono-NF-Regular.ttf +0 -0
- dbgr_server/static/images/favicon.ico +0 -0
- dbgr_server/static/javascripts/dist/0d8bb5b3ee5f5dac9e44.woff2 +0 -0
- dbgr_server/static/javascripts/dist/16b1558858fe1064b5af.woff +0 -0
- dbgr_server/static/javascripts/dist/176f8f5bd5f02b3abfcf.woff2 +0 -0
- dbgr_server/static/javascripts/dist/18d00f739ff1e1c52db1.woff +0 -0
- dbgr_server/static/javascripts/dist/1b3b4f49d5b3b3fb2c20.jpg +0 -0
- dbgr_server/static/javascripts/dist/1ce08cfde36c0d81af5b.woff +0 -0
- dbgr_server/static/javascripts/dist/213eedde97d22010287c.woff +0 -0
- dbgr_server/static/javascripts/dist/2267169ee7270a22a963.woff +0 -0
- dbgr_server/static/javascripts/dist/24a9d3bbf9b715214bf5.woff +0 -0
- dbgr_server/static/javascripts/dist/2900d24e7347deaf65cd.woff +0 -0
- dbgr_server/static/javascripts/dist/2ed4651232a34d737777.woff +0 -0
- dbgr_server/static/javascripts/dist/37a7069dc30fc663c878.woff2 +0 -0
- dbgr_server/static/javascripts/dist/451d4e559d6f57cdf6a1.woff +0 -0
- dbgr_server/static/javascripts/dist/4674f8ded773cb03e824.eot +0 -0
- dbgr_server/static/javascripts/dist/498042b7fe9cd07b4fd1.woff +0 -0
- dbgr_server/static/javascripts/dist/49ae34d4cc6b98c00c69.woff +0 -0
- dbgr_server/static/javascripts/dist/51aa168dd4ba03bd83f0.woff2 +0 -0
- dbgr_server/static/javascripts/dist/535bc89d4af715503b01.woff2 +0 -0
- dbgr_server/static/javascripts/dist/53b43867955fbecc811a.woff2 +0 -0
- dbgr_server/static/javascripts/dist/5e7382c63da0098d634a.ttf +0 -0
- dbgr_server/static/javascripts/dist/640f8692dc49a19f1541.woff +0 -0
- dbgr_server/static/javascripts/dist/79f21d796f266ce0ea70.woff +0 -0
- dbgr_server/static/javascripts/dist/7d8125ff7f707231fd89.woff2 +0 -0
- dbgr_server/static/javascripts/dist/7f7e9cd596438fea4b02.woff2 +0 -0
- dbgr_server/static/javascripts/dist/7f839a8652da29745ce4.woff2 +0 -0
- dbgr_server/static/javascripts/dist/83bebaf37c09c7e1c3ee.woff +0 -0
- dbgr_server/static/javascripts/dist/865f928cbabcc9f8f2b5.woff +0 -0
- dbgr_server/static/javascripts/dist/870c8c1486f76054301a.woff2 +0 -0
- dbgr_server/static/javascripts/dist/9216be4e13fe7dbe70ef.woff2 +0 -0
- dbgr_server/static/javascripts/dist/9360531f9bb817f917f0.woff +0 -0
- dbgr_server/static/javascripts/dist/969a44e1b6fc26105bd6.woff2 +0 -0
- dbgr_server/static/javascripts/dist/a45108d3b34af91f9113.woff +0 -0
- dbgr_server/static/javascripts/dist/b1d9d9904bfca8802a63.woff +0 -0
- dbgr_server/static/javascripts/dist/b39a11f9a6107e57a21f.woff2 +0 -0
- dbgr_server/static/javascripts/dist/bac8362e7a6ea60b6983.woff +0 -0
- dbgr_server/static/javascripts/dist/bbbf6951525a78fff340.woff +0 -0
- dbgr_server/static/javascripts/dist/bc628a95b5026af0df2c.woff2 +0 -0
- dbgr_server/static/javascripts/dist/bd5b7a13f2c52b531a2a.woff +0 -0
- dbgr_server/static/javascripts/dist/bf4b4e73015a3066da91.woff +0 -0
- dbgr_server/static/javascripts/dist/bfe6eaec56c141a0bd72.woff2 +0 -0
- dbgr_server/static/javascripts/dist/c18ee39fb002ad58b6dc.woff2 +0 -0
- dbgr_server/static/javascripts/dist/c1f17364fee75703a7ee.woff2 +0 -0
- dbgr_server/static/javascripts/dist/c20d916c1a1b094c1cec.woff +0 -0
- dbgr_server/static/javascripts/dist/c2aa4ab115bf9c6057cb.woff2 +0 -0
- dbgr_server/static/javascripts/dist/c5c3959c04004102ea46.woff2 +0 -0
- dbgr_server/static/javascripts/dist/c64e7e354c88e613c77c.woff2 +0 -0
- dbgr_server/static/javascripts/dist/ca100b2be6274105bb16.woff +0 -0
- dbgr_server/static/javascripts/dist/cb5ad999740e9d8a8bd1.woff2 +0 -0
- dbgr_server/static/javascripts/dist/cc9e42999888377183f8.woff2 +0 -0
- dbgr_server/static/javascripts/dist/cea99d3e3e13a3a599a0.woff +0 -0
- dbgr_server/static/javascripts/dist/cff684e59ffb052d72cb.woff2 +0 -0
- dbgr_server/static/javascripts/dist/d022bc70dc1bf7b3425d.woff2 +0 -0
- dbgr_server/static/javascripts/dist/d97850bb044fb5584361.woff2 +0 -0
- dbgr_server/static/javascripts/dist/da65dcd09cdc4863ece2.woff +0 -0
- dbgr_server/static/javascripts/dist/dbgr.js +1463 -0
- dbgr_server/static/javascripts/dist/eba647ae67ba7dc20f05.woff +0 -0
- dbgr_server/static/javascripts/dist/f5b74d7ffcdf85b9dd60.woff2 +0 -0
- dbgr_server/static/javascripts/dist/ff2c5c919a01e9ddda85.woff2 +0 -0
- dbgr_server/static/javascripts/dist/home.js +1037 -0
- dbgr_server/static/libs/material-design-lite/material.indigo-red.min.css +8 -0
- dbgr_server/templates/_layout.html +17 -0
- dbgr_server/templates/dbgr.html +138 -0
- dbgr_server/templates/home.html +115 -0
- dbgr_server/utils/__init__.py +0 -0
- dbgr_server/utils/state.py +249 -0
- dbgr_server/utils/streams.py +151 -0
- dbgr_server/utils/technical.py +132 -0
- dbgr_server/views.py +298 -0
- dbgr_server-1.1.0.dev1.data/scripts/dbgr.server.py +105 -0
- dbgr_server-1.1.0.dev1.dist-info/METADATA +534 -0
- dbgr_server-1.1.0.dev1.dist-info/RECORD +86 -0
- dbgr_server-1.1.0.dev1.dist-info/WHEEL +5 -0
- dbgr_server-1.1.0.dev1.dist-info/licenses/LICENSE +674 -0
- dbgr_server-1.1.0.dev1.dist-info/top_level.txt +2 -0
- tests/__init__.py +0 -0
- 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)
|