omlish 0.0.0.dev225__py3-none-any.whl → 0.0.0.dev227__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
omlish/__about__.py CHANGED
@@ -1,5 +1,5 @@
1
- __version__ = '0.0.0.dev225'
2
- __revision__ = '99eb1740a6c8647c20c80278b6d0bedff0f7c103'
1
+ __version__ = '0.0.0.dev227'
2
+ __revision__ = '2faa3e7f9120edd3e6dd79fad6401fd87d89f5cf'
3
3
 
4
4
 
5
5
  #
@@ -10,6 +10,7 @@ import sys
10
10
  import typing as ta
11
11
 
12
12
  from ...lite.check import check
13
+ from ...lite.timeouts import TimeoutLike
13
14
  from ...subprocesses.async_ import AbstractAsyncSubprocesses
14
15
  from ...subprocesses.run import SubprocessRun
15
16
  from ...subprocesses.run import SubprocessRunOutput
@@ -130,7 +131,7 @@ class AsyncioProcessCommunicator:
130
131
  async def communicate(
131
132
  self,
132
133
  input: ta.Any = None, # noqa
133
- timeout: ta.Optional[float] = None,
134
+ timeout: ta.Optional[TimeoutLike] = None,
134
135
  ) -> Communication:
135
136
  return await asyncio_maybe_timeout(self._communicate(input), timeout)
136
137
 
@@ -143,7 +144,7 @@ class AsyncioSubprocesses(AbstractAsyncSubprocesses):
143
144
  self,
144
145
  proc: asyncio.subprocess.Process,
145
146
  input: ta.Any = None, # noqa
146
- timeout: ta.Optional[float] = None,
147
+ timeout: ta.Optional[TimeoutLike] = None,
147
148
  ) -> ta.Tuple[ta.Optional[bytes], ta.Optional[bytes]]:
148
149
  return await AsyncioProcessCommunicator(proc).communicate(input, timeout) # noqa
149
150
 
@@ -154,7 +155,7 @@ class AsyncioSubprocesses(AbstractAsyncSubprocesses):
154
155
  self,
155
156
  *cmd: str,
156
157
  shell: bool = False,
157
- timeout: ta.Optional[float] = None,
158
+ timeout: ta.Optional[TimeoutLike] = None,
158
159
  **kwargs: ta.Any,
159
160
  ) -> ta.AsyncGenerator[asyncio.subprocess.Process, None]:
160
161
  with self.prepare_and_wrap( *cmd, shell=shell, **kwargs) as (cmd, kwargs): # noqa
@@ -3,14 +3,17 @@
3
3
  import asyncio
4
4
  import typing as ta
5
5
 
6
+ from ...lite.timeouts import Timeout
7
+ from ...lite.timeouts import TimeoutLike
8
+
6
9
 
7
10
  AwaitableT = ta.TypeVar('AwaitableT', bound=ta.Awaitable)
8
11
 
9
12
 
10
13
  def asyncio_maybe_timeout(
11
14
  fut: AwaitableT,
12
- timeout: ta.Optional[float] = None,
15
+ timeout: ta.Optional[TimeoutLike] = None,
13
16
  ) -> AwaitableT:
14
17
  if timeout is not None:
15
- fut = asyncio.wait_for(fut, timeout) # type: ignore
18
+ fut = asyncio.wait_for(fut, Timeout.of(timeout)()) # type: ignore
16
19
  return fut
omlish/diag/lslocks.py ADDED
@@ -0,0 +1,64 @@
1
+ # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
3
+ """
4
+ https://github.com/util-linux/util-linux/blob/a4436c7bf07f98a6381c7dfa2ab3f9a415f9c479/misc-utils/lslocks.c
5
+ """
6
+ import dataclasses as dc
7
+ import json
8
+ import typing as ta
9
+
10
+ from ..lite.check import check
11
+ from ..lite.marshal import OBJ_MARSHALER_FIELD_KEY
12
+ from ..lite.marshal import unmarshal_obj
13
+ from ..subprocesses.run import SubprocessRun
14
+ from ..subprocesses.run import SubprocessRunnable
15
+ from ..subprocesses.run import SubprocessRunOutput
16
+
17
+
18
+ ##
19
+
20
+
21
+ @dc.dataclass(frozen=True)
22
+ class LslocksItem:
23
+ """https://manpages.ubuntu.com/manpages/lunar/man8/lslocks.8.html"""
24
+
25
+ command: str
26
+ pid: int
27
+ type: str # POSIX | FLOCK | OFDLCK
28
+ size: ta.Optional[int]
29
+ mode: str # READ | WRITE
30
+ mandatory: bool = dc.field(metadata={OBJ_MARSHALER_FIELD_KEY: 'm'})
31
+ start: int
32
+ end: int
33
+ path: str
34
+ blocker: ta.Optional[int] = None
35
+
36
+
37
+ ##
38
+
39
+
40
+ @dc.dataclass(frozen=True)
41
+ class LslocksCommand(SubprocessRunnable[ta.List[LslocksItem]]):
42
+ pid: ta.Optional[int] = None
43
+ no_inaccessible: bool = False
44
+
45
+ def make_run(self) -> SubprocessRun:
46
+ return SubprocessRun.of(
47
+ 'lslocks',
48
+ '--json',
49
+ '--bytes',
50
+ '--notruncate',
51
+ *(['--pid', str(self.pid)] if self.pid is not None else []),
52
+ *(['--noinaccessible'] if self.no_inaccessible else []),
53
+
54
+ check=True,
55
+ stdout='pipe',
56
+ stderr='devnull',
57
+ )
58
+
59
+ def handle_run_output(self, output: SubprocessRunOutput) -> ta.List[LslocksItem]:
60
+ buf = check.not_none(output.stdout).decode().strip()
61
+ if not buf:
62
+ return []
63
+ obj = json.loads(buf)
64
+ return unmarshal_obj(obj['locks'], ta.List[LslocksItem])
omlish/diag/lsof.py ADDED
@@ -0,0 +1,264 @@
1
+ # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
3
+ """
4
+ https://man7.org/linux/man-pages/man8/lsof.8.html
5
+
6
+ Included in the process set are fields that identify the command, the process group IDentification (PGID) number, the
7
+ task (thread) ID (TID), and the user ID (UID) number or login name.
8
+ """
9
+ import dataclasses as dc
10
+ import enum
11
+ import typing as ta
12
+
13
+ from ..lite.check import check
14
+ from ..lite.dataclasses import dataclass_repr_omit_falsey
15
+ from ..lite.marshal import OBJ_MARSHALER_OMIT_IF_NONE
16
+ from ..subprocesses.run import SubprocessRun
17
+ from ..subprocesses.run import SubprocessRunnable
18
+ from ..subprocesses.run import SubprocessRunOutput
19
+
20
+
21
+ ##
22
+
23
+
24
+ # FIXME: ??
25
+ # https://unix.stackexchange.com/a/86011
26
+ class LsofItemMode(enum.Enum):
27
+ SOLARIS_NFS_LOCK = 'N'
28
+ READ_LOCK_PARTIAL = 'r'
29
+ READ_LOCK_FULL = 'R'
30
+ WRITE_LOCK_PARTIAL = 'w'
31
+ WRITE_LOCK_FULL = 'W'
32
+ READ_WRITE_LOCK = 'u'
33
+ UNKNOWN_LOCK_TYPE = 'U'
34
+ XENIX_LOCK_PARTIAL = 'x'
35
+ XENIX_LOCK_FULL = 'X'
36
+
37
+
38
+ @dc.dataclass(frozen=True)
39
+ class _LsofFieldMeta:
40
+ prefix: str
41
+ process: bool = False
42
+ variadic: bool = False
43
+
44
+ @classmethod
45
+ def make(cls, *args, **kwargs):
46
+ return {
47
+ cls: cls(*args, **kwargs),
48
+ OBJ_MARSHALER_OMIT_IF_NONE: True,
49
+ }
50
+
51
+
52
+ @dc.dataclass(frozen=True)
53
+ class LsofItem:
54
+ __repr__ = dataclass_repr_omit_falsey
55
+
56
+ class _PREFIX:
57
+ def __new__(cls, *args, **kwargs): # noqa
58
+ raise TypeError
59
+
60
+ class _PROCESS:
61
+ def __new__(cls, *args, **kwargs): # noqa
62
+ raise TypeError
63
+
64
+ pid: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('p', process=True))
65
+ pgid: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('g', process=True))
66
+ ppid: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('R', process=True))
67
+ cmd: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('c', process=True))
68
+
69
+ uid: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('u', process=True))
70
+ login_name: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('L', process=True))
71
+
72
+ # FD is the File Descriptor number of the file or:
73
+ # cwd current working directory;
74
+ # Lnn library references (AIX);
75
+ # ctty character tty;
76
+ # DEL deleted file;
77
+ # err FD information error (see NAME column);
78
+ # fp. Fileport (Darwin);
79
+ # jld jail directory (FreeBSD);
80
+ # ltx shared library text (code and data);
81
+ # Mxx hex memory-mapped type number xx.
82
+ # m86 DOS Merge mapped file;
83
+ # mem memory-mapped file;
84
+ # mmap memory-mapped device;
85
+ # NOFD for a Linux /proc/<PID>/fd directory that can't be opened -- the directory path appears in the NAME column,
86
+ # followed by an error message;
87
+ # pd parent directory;
88
+ # Rnn unknown pregion number (HP-UX);
89
+ # rtd root directory;
90
+ # twd per task current working directory;
91
+ # txt program text (code and data);
92
+ # v86 VP/ix mapped file;
93
+ #
94
+ # FD is followed by one of these characters, describing the mode under which the file is open:
95
+ # r for read access;
96
+ # w for write access;
97
+ # u for read and write access;
98
+ # space if mode unknown and no lock
99
+ # character follows;
100
+ # `-' if mode unknown and lock
101
+ # character follows.
102
+ #
103
+ # The mode character is followed by one of these lock characters, describing the type of lock applied to the file:
104
+ # N for a Solaris NFS lock of unknown type;
105
+ # r for read lock on part of the file;
106
+ # R for a read lock on the entire file;
107
+ # w for a write lock on part of the file;
108
+ # W for a write lock on the entire file;
109
+ # u for a read and write lock of any length;
110
+ # U for a lock of unknown type;
111
+ # x for an SCO OpenServer Xenix lock on part of the file;
112
+ # X for an SCO OpenServer Xenix lock on the entire file;
113
+ # space if there is no lock.
114
+ #
115
+ fd: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('f'))
116
+
117
+ inode: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('i'))
118
+ name: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('n'))
119
+
120
+ # r = read; w = write; u = read/write
121
+ access: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('a'))
122
+
123
+ # r/R = read; w/W = write; u = read/write
124
+ lock: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('l'))
125
+
126
+ file_flags: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('G'))
127
+ file_type: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('t'))
128
+ file_size: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('s'))
129
+ file_offset: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('o')) # as 0t<dec> or 0x<hex>
130
+
131
+ device_character_code: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('d'))
132
+ device_number: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('D')) # as 0x<hex>
133
+ raw_device_number: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('r')) # as 0x<hex>
134
+
135
+ share_count: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('C'))
136
+ link_count: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('k'))
137
+
138
+ stream_info: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('S'))
139
+ protocol_name: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('P'))
140
+
141
+ tcp_tpi_info: ta.Optional[ta.Sequence[str]] = dc.field(
142
+ default=None,
143
+ metadata=_LsofFieldMeta.make('T', variadic=True),
144
+ )
145
+
146
+ # 'K': 'task_id', # unknown field
147
+ # 'M': 'task_command_name', # unknown field
148
+
149
+ _FIELDS_BY_PREFIX: ta.ClassVar[ta.Mapping[str, dc.Field]]
150
+ _DEFAULT_PREFIXES: ta.ClassVar[str]
151
+
152
+ @classmethod
153
+ def from_prefixes(cls, dct: ta.Mapping[str, ta.Any]) -> 'LsofItem':
154
+ kw: ta.Dict[str, ta.Any] = {
155
+ fld.name: val
156
+ for pfx, val in dct.items()
157
+ if (fld := cls._FIELDS_BY_PREFIX.get(pfx)) is not None
158
+ }
159
+
160
+ return cls(**kw)
161
+
162
+ @classmethod
163
+ def from_prefix_lines(cls, lines: ta.Iterable[str]) -> ta.List['LsofItem']:
164
+ proc_dct: ta.Dict[str, ta.Any] = {}
165
+ file_dct: ta.Dict[str, ta.Any] = {}
166
+ out: ta.List[LsofItem] = []
167
+
168
+ def emit() -> None:
169
+ if file_dct:
170
+ out.append(cls.from_prefixes({**proc_dct, **file_dct}))
171
+
172
+ for line in lines:
173
+ pfx, val = line[0], line[1:]
174
+ try:
175
+ fld = cls._FIELDS_BY_PREFIX[pfx]
176
+ except KeyError:
177
+ continue
178
+ meta: _LsofFieldMeta = fld.metadata[_LsofFieldMeta]
179
+
180
+ if pfx == 'p':
181
+ emit()
182
+ proc_dct = {}
183
+ file_dct = {}
184
+ elif pfx == 'f':
185
+ emit()
186
+ file_dct = {}
187
+
188
+ if meta.process:
189
+ dct = proc_dct
190
+ else:
191
+ dct = file_dct
192
+
193
+ if meta.variadic:
194
+ dct.setdefault(pfx, []).append(val)
195
+ else:
196
+ if pfx in dct:
197
+ raise KeyError(pfx)
198
+ dct[pfx] = val
199
+
200
+ emit()
201
+
202
+ return out
203
+
204
+
205
+ LsofItem._FIELDS_BY_PREFIX = { # noqa
206
+ meta.prefix: fld
207
+ for fld in dc.fields(LsofItem) # noqa
208
+ if (meta := fld.metadata.get(_LsofFieldMeta)) is not None # noqa
209
+ }
210
+
211
+ LsofItem._DEFAULT_PREFIXES = ''.join(LsofItem._FIELDS_BY_PREFIX) # noqa
212
+
213
+
214
+ ##
215
+
216
+
217
+ @dc.dataclass(frozen=True)
218
+ class \
219
+ LsofCommand(SubprocessRunnable[ta.List[LsofItem]]):
220
+ pid: ta.Optional[int] = None
221
+ file: ta.Optional[str] = None
222
+
223
+ prefixes: ta.Optional[ta.Sequence[str]] = None
224
+
225
+ def make_run(self) -> SubprocessRun:
226
+ if (prefixes := self.prefixes) is None:
227
+ prefixes = LsofItem._DEFAULT_PREFIXES # noqa
228
+
229
+ return SubprocessRun.of(
230
+ 'lsof',
231
+ '-F', ''.join(prefixes),
232
+ *(['-p', str(self.pid)] if self.pid is not None else []),
233
+ *([self.file] if self.file is not None else []),
234
+
235
+ stdout='pipe',
236
+ stderr='devnull',
237
+ check=True,
238
+ )
239
+
240
+ def handle_run_output(self, output: SubprocessRunOutput) -> ta.List[LsofItem]:
241
+ lines = [s for l in check.not_none(output.stdout).decode().splitlines() if (s := l.strip())]
242
+ return LsofItem.from_prefix_lines(lines)
243
+
244
+
245
+ if __name__ == '__main__':
246
+ def _main() -> None:
247
+ argparse = __import__('argparse')
248
+ parser = argparse.ArgumentParser()
249
+ parser.add_argument('--pid', '-p', type=int)
250
+ parser.add_argument('file', nargs='?')
251
+ args = parser.parse_args()
252
+
253
+ importlib = __import__('importlib')
254
+ subprocesses = importlib.import_module('..subprocesses.sync', package=__package__).subprocesses
255
+ items = LsofCommand(
256
+ pid=args.pid,
257
+ file=args.file,
258
+ ).run(subprocesses)
259
+
260
+ json = __import__('json')
261
+ marshal_obj = importlib.import_module('..lite.marshal', package=__package__).marshal_obj
262
+ print(json.dumps(marshal_obj(items), indent=2))
263
+
264
+ _main()
omlish/diag/ps.py CHANGED
@@ -1,8 +1,15 @@
1
+ # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
1
3
  import dataclasses as dc
2
4
  import os
3
- import subprocess
5
+ import typing as ta
4
6
 
5
- from .. import lang
7
+ from ..lite.check import check
8
+ from ..lite.timeouts import Timeout
9
+ from ..subprocesses.run import SubprocessRun
10
+ from ..subprocesses.run import SubprocessRunnable
11
+ from ..subprocesses.run import SubprocessRunOutput
12
+ from ..subprocesses.sync import subprocesses
6
13
 
7
14
 
8
15
  @dc.dataclass(frozen=True)
@@ -12,23 +19,36 @@ class PsItem:
12
19
  cmd: str
13
20
 
14
21
 
15
- def get_ps_item(pid: int, timeout: lang.Timeout | None = None) -> PsItem:
16
- timeout = lang.timeout(timeout)
17
- out = subprocess.check_output(
18
- ['ps', '-o', 'pid=,ppid=,command=', str(int(pid))],
19
- timeout=timeout.or_(None),
20
- ).decode().strip()
21
- opid, _, rest = out.partition(' ')
22
- ppid, _, cmd = rest.strip().partition(' ')
23
- return PsItem(
24
- int(opid),
25
- int(ppid),
26
- cmd.strip(),
27
- )
22
+ @dc.dataclass(frozen=True)
23
+ class PsCommand(SubprocessRunnable):
24
+ pid: ta.Optional[int] = None
25
+
26
+ def make_run(self) -> SubprocessRun:
27
+ return SubprocessRun.of(
28
+ 'ps',
29
+ '-o', 'pid=,ppid=,command=',
30
+ *([str(int(self.pid))] if self.pid is not None else []),
31
+
32
+ check=True,
33
+ stdout='pipe',
34
+ stderr='devnull',
35
+ )
36
+
37
+ def handle_run_output(self, output: SubprocessRunOutput) -> PsItem:
38
+ opid, ppid, cmd = check.not_none(output.stdout).decode().split(maxsplit=2)
39
+ return PsItem(
40
+ int(opid),
41
+ int(ppid),
42
+ cmd.strip(),
43
+ )
44
+
28
45
 
46
+ def get_ps_item(pid: int, timeout: ta.Optional[Timeout] = None) -> PsItem:
47
+ return PsCommand(pid).run(subprocesses, timeout=timeout)
29
48
 
30
- def get_ps_lineage(pid: int, timeout: lang.Timeout | None = None) -> list[PsItem]:
31
- timeout = lang.timeout(timeout)
49
+
50
+ def get_ps_lineage(pid: int, timeout: ta.Optional[Timeout] = None) -> ta.List[PsItem]:
51
+ timeout = Timeout.of(timeout)
32
52
  ret: list[PsItem] = []
33
53
  while True:
34
54
  cur = get_ps_item(pid, timeout)
@@ -39,9 +59,8 @@ def get_ps_lineage(pid: int, timeout: lang.Timeout | None = None) -> list[PsItem
39
59
  return ret
40
60
 
41
61
 
42
- def _main() -> None:
43
- print(get_ps_lineage(os.getpid()))
44
-
45
-
46
62
  if __name__ == '__main__':
63
+ def _main() -> None:
64
+ print(get_ps_lineage(os.getpid()))
65
+
47
66
  _main()
omlish/lang/__init__.py CHANGED
@@ -226,14 +226,6 @@ from .sys import ( # noqa
226
226
  is_gil_enabled,
227
227
  )
228
228
 
229
- from .timeouts import ( # noqa
230
- DeadlineTimeout,
231
- InfiniteTimeout,
232
- Timeout,
233
- TimeoutLike,
234
- timeout,
235
- )
236
-
237
229
  from .typing import ( # noqa
238
230
  BytesLike,
239
231
  SequenceNotStr,
@@ -244,6 +236,13 @@ from .typing import ( # noqa
244
236
 
245
237
  ##
246
238
 
239
+ from ..lite.timeouts import ( # noqa
240
+ DeadlineTimeout,
241
+ InfiniteTimeout,
242
+ Timeout,
243
+ TimeoutLike,
244
+ )
245
+
247
246
  from ..lite.types import ( # noqa
248
247
  BUILTIN_SCALAR_ITERABLE_TYPES,
249
248
  )
@@ -42,3 +42,26 @@ def dataclass_maybe_post_init(sup: ta.Any) -> bool:
42
42
  return False
43
43
  fn()
44
44
  return True
45
+
46
+
47
+ def dataclass_repr_filtered(
48
+ obj: ta.Any,
49
+ fn: ta.Callable[[ta.Any, dc.Field, ta.Any], bool],
50
+ ) -> str:
51
+ return (
52
+ f'{obj.__class__.__qualname__}(' +
53
+ ', '.join([
54
+ f'{f.name}={v!r}'
55
+ for f in dc.fields(obj)
56
+ if fn(obj, f, v := getattr(obj, f.name))
57
+ ]) +
58
+ ')'
59
+ )
60
+
61
+
62
+ def dataclass_repr_omit_none(obj: ta.Any) -> str:
63
+ return dataclass_repr_filtered(obj, lambda o, f, v: v is not None)
64
+
65
+
66
+ def dataclass_repr_omit_falsey(obj: ta.Any) -> str:
67
+ return dataclass_repr_filtered(obj, lambda o, f, v: bool(v))