kaqing 2.0.171__py3-none-any.whl → 2.0.204__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.
- adam/app_session.py +5 -10
- adam/apps.py +18 -4
- adam/batch.py +7 -7
- adam/checks/check_utils.py +3 -1
- adam/checks/disk.py +2 -3
- adam/columns/memory.py +3 -4
- adam/commands/__init__.py +15 -6
- adam/commands/alter_tables.py +26 -41
- adam/commands/app/__init__.py +0 -0
- adam/commands/{app_cmd.py → app/app.py} +2 -2
- adam/commands/{show → app}/show_app_actions.py +7 -15
- adam/commands/{show → app}/show_app_queues.py +1 -4
- adam/{utils_app.py → commands/app/utils_app.py} +9 -1
- adam/commands/audit/audit.py +9 -26
- adam/commands/audit/audit_repair_tables.py +5 -7
- adam/commands/audit/audit_run.py +1 -1
- adam/commands/audit/completions_l.py +15 -0
- adam/commands/audit/show_last10.py +2 -14
- adam/commands/audit/show_slow10.py +2 -13
- adam/commands/audit/show_top10.py +2 -11
- adam/commands/audit/utils_show_top10.py +15 -3
- adam/commands/bash/bash.py +1 -1
- adam/commands/bash/utils_bash.py +1 -1
- adam/commands/cassandra/__init__.py +0 -0
- adam/commands/cassandra/download_cassandra_log.py +45 -0
- adam/commands/cassandra/nodetool.py +64 -0
- adam/commands/cassandra/nodetool_commands.py +120 -0
- adam/commands/cassandra/restart_cluster.py +47 -0
- adam/commands/cassandra/restart_node.py +51 -0
- adam/commands/cassandra/restart_nodes.py +47 -0
- adam/commands/cassandra/rollout.py +88 -0
- adam/commands/cat.py +5 -19
- adam/commands/cd.py +7 -9
- adam/commands/check.py +10 -18
- adam/commands/cli_commands.py +6 -1
- adam/commands/{cp.py → clipboard_copy.py} +34 -36
- adam/commands/code.py +2 -2
- adam/commands/command.py +139 -22
- adam/commands/commands_utils.py +14 -12
- adam/commands/cql/alter_tables.py +66 -0
- adam/commands/cql/completions_c.py +29 -0
- adam/commands/cql/cqlsh.py +3 -7
- adam/commands/cql/utils_cql.py +23 -61
- adam/commands/debug/__init__.py +0 -0
- adam/commands/debug/debug.py +22 -0
- adam/commands/debug/debug_completes.py +35 -0
- adam/commands/debug/debug_timings.py +35 -0
- adam/commands/deploy/deploy_pg_agent.py +2 -2
- adam/commands/deploy/deploy_pod.py +2 -4
- adam/commands/deploy/undeploy_pg_agent.py +2 -2
- adam/commands/devices/device.py +40 -9
- adam/commands/devices/device_app.py +19 -29
- adam/commands/devices/device_auit_log.py +3 -3
- adam/commands/devices/device_cass.py +17 -23
- adam/commands/devices/device_export.py +12 -11
- adam/commands/devices/device_postgres.py +79 -63
- adam/commands/devices/devices.py +1 -1
- adam/commands/download_cassandra_log.py +45 -0
- adam/commands/download_file.py +47 -0
- adam/commands/export/clean_up_all_export_sessions.py +3 -3
- adam/commands/export/clean_up_export_sessions.py +7 -19
- adam/commands/export/completions_x.py +11 -0
- adam/commands/export/download_export_session.py +40 -0
- adam/commands/export/drop_export_database.py +6 -22
- adam/commands/export/drop_export_databases.py +3 -9
- adam/commands/export/export.py +1 -17
- adam/commands/export/export_databases.py +109 -32
- adam/commands/export/export_select.py +8 -55
- adam/commands/export/export_sessions.py +211 -0
- adam/commands/export/export_use.py +13 -16
- adam/commands/export/export_x_select.py +48 -0
- adam/commands/export/exporter.py +176 -167
- adam/commands/export/import_files.py +44 -0
- adam/commands/export/import_session.py +10 -6
- adam/commands/export/importer.py +24 -9
- adam/commands/export/importer_athena.py +114 -44
- adam/commands/export/importer_sqlite.py +45 -23
- adam/commands/export/show_column_counts.py +11 -20
- adam/commands/export/show_export_databases.py +5 -2
- adam/commands/export/show_export_session.py +6 -15
- adam/commands/export/show_export_sessions.py +4 -11
- adam/commands/export/utils_export.py +79 -27
- adam/commands/find_files.py +51 -0
- adam/commands/find_processes.py +76 -0
- adam/commands/generate_report.py +52 -0
- adam/commands/head.py +36 -0
- adam/commands/help.py +2 -2
- adam/commands/intermediate_command.py +6 -3
- adam/commands/login.py +3 -6
- adam/commands/ls.py +2 -2
- adam/commands/medusa/medusa_backup.py +13 -16
- adam/commands/medusa/medusa_restore.py +26 -37
- adam/commands/medusa/medusa_show_backupjobs.py +7 -7
- adam/commands/medusa/medusa_show_restorejobs.py +6 -6
- adam/commands/medusa/utils_medusa.py +15 -0
- adam/commands/nodetool.py +3 -8
- adam/commands/os/__init__.py +0 -0
- adam/commands/os/cat.py +36 -0
- adam/commands/os/download_file.py +47 -0
- adam/commands/os/find_files.py +51 -0
- adam/commands/os/find_processes.py +76 -0
- adam/commands/os/head.py +36 -0
- adam/commands/os/shell.py +41 -0
- adam/commands/param_get.py +10 -12
- adam/commands/param_set.py +7 -10
- adam/commands/postgres/completions_p.py +22 -0
- adam/commands/postgres/postgres.py +25 -40
- adam/commands/postgres/postgres_databases.py +269 -0
- adam/commands/postgres/utils_postgres.py +33 -20
- adam/commands/preview_table.py +4 -2
- adam/commands/pwd.py +4 -6
- adam/commands/reaper/reaper_forward.py +2 -2
- adam/commands/reaper/reaper_run_abort.py +4 -10
- adam/commands/reaper/reaper_runs.py +3 -3
- adam/commands/reaper/reaper_schedule_activate.py +12 -12
- adam/commands/reaper/reaper_schedule_start.py +7 -12
- adam/commands/reaper/reaper_schedule_stop.py +7 -12
- adam/commands/reaper/utils_reaper.py +13 -6
- adam/commands/repair/repair_log.py +1 -4
- adam/commands/repair/repair_run.py +3 -8
- adam/commands/repair/repair_scan.py +1 -6
- adam/commands/repair/repair_stop.py +1 -5
- adam/commands/restart_cluster.py +47 -0
- adam/commands/restart_node.py +51 -0
- adam/commands/restart_nodes.py +47 -0
- adam/commands/shell.py +9 -2
- adam/commands/show/show.py +4 -4
- adam/commands/show/show_adam.py +3 -3
- adam/commands/show/show_cassandra_repairs.py +5 -6
- adam/commands/show/show_cassandra_status.py +29 -29
- adam/commands/show/show_cassandra_version.py +1 -4
- adam/commands/show/{show_commands.py → show_cli_commands.py} +3 -6
- adam/commands/show/show_login.py +3 -9
- adam/commands/show/show_params.py +2 -5
- adam/commands/show/show_processes.py +15 -16
- adam/commands/show/show_storage.py +9 -8
- adam/config.py +4 -5
- adam/embedded_params.py +1 -1
- adam/log.py +4 -4
- adam/repl.py +26 -18
- adam/repl_commands.py +32 -20
- adam/repl_session.py +9 -1
- adam/repl_state.py +39 -10
- adam/sql/async_executor.py +44 -0
- adam/sql/lark_completer.py +286 -0
- adam/sql/lark_parser.py +604 -0
- adam/sql/qingl.lark +1076 -0
- adam/sql/sql_completer.py +4 -6
- adam/sql/sql_state_machine.py +25 -13
- adam/sso/authn_ad.py +2 -5
- adam/sso/authn_okta.py +2 -4
- adam/sso/cred_cache.py +2 -5
- adam/sso/idp.py +8 -11
- adam/utils.py +299 -105
- adam/utils_athena.py +18 -18
- adam/utils_audits.py +3 -7
- adam/utils_issues.py +2 -2
- adam/utils_k8s/app_clusters.py +4 -4
- adam/utils_k8s/app_pods.py +8 -6
- adam/utils_k8s/cassandra_clusters.py +16 -5
- adam/utils_k8s/cassandra_nodes.py +7 -6
- adam/utils_k8s/custom_resources.py +11 -17
- adam/utils_k8s/jobs.py +7 -11
- adam/utils_k8s/k8s.py +14 -5
- adam/utils_k8s/kube_context.py +3 -6
- adam/{pod_exec_result.py → utils_k8s/pod_exec_result.py} +4 -4
- adam/utils_k8s/pods.py +98 -36
- adam/utils_k8s/statefulsets.py +5 -2
- adam/utils_local.py +42 -0
- adam/utils_repl/appendable_completer.py +6 -0
- adam/utils_repl/repl_completer.py +45 -2
- adam/utils_repl/state_machine.py +3 -3
- adam/utils_sqlite.py +58 -30
- adam/version.py +1 -1
- {kaqing-2.0.171.dist-info → kaqing-2.0.204.dist-info}/METADATA +1 -1
- kaqing-2.0.204.dist-info/RECORD +277 -0
- kaqing-2.0.204.dist-info/top_level.txt +2 -0
- teddy/__init__.py +0 -0
- teddy/lark_parser.py +436 -0
- teddy/lark_parser2.py +618 -0
- adam/commands/cql/cql_completions.py +0 -33
- adam/commands/export/export_handlers.py +0 -71
- adam/commands/export/export_select_x.py +0 -54
- adam/commands/logs.py +0 -37
- adam/commands/postgres/postgres_context.py +0 -274
- adam/commands/postgres/psql_completions.py +0 -10
- adam/commands/report.py +0 -61
- adam/commands/restart.py +0 -60
- kaqing-2.0.171.dist-info/RECORD +0 -236
- kaqing-2.0.171.dist-info/top_level.txt +0 -1
- /adam/commands/{app_ping.py → app/app_ping.py} +0 -0
- /adam/commands/{show → app}/show_app_id.py +0 -0
- {kaqing-2.0.171.dist-info → kaqing-2.0.204.dist-info}/WHEEL +0 -0
- {kaqing-2.0.171.dist-info → kaqing-2.0.204.dist-info}/entry_points.txt +0 -0
adam/utils.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from abc import
|
|
1
|
+
from abc import ABC
|
|
2
2
|
from concurrent.futures import Future, ThreadPoolExecutor
|
|
3
3
|
from contextlib import redirect_stdout
|
|
4
4
|
import copy
|
|
@@ -12,27 +12,58 @@ from pathlib import Path
|
|
|
12
12
|
import random
|
|
13
13
|
import string
|
|
14
14
|
import threading
|
|
15
|
-
|
|
15
|
+
import traceback
|
|
16
|
+
from typing import Callable, Iterator, TypeVar, Union
|
|
16
17
|
from dateutil import parser
|
|
17
18
|
import subprocess
|
|
18
19
|
import sys
|
|
19
20
|
import time
|
|
20
21
|
import click
|
|
21
22
|
import yaml
|
|
23
|
+
from prompt_toolkit.completion import Completer
|
|
22
24
|
|
|
23
25
|
from . import __version__
|
|
24
26
|
|
|
27
|
+
T = TypeVar('T')
|
|
28
|
+
|
|
25
29
|
log_state = threading.local()
|
|
26
30
|
|
|
27
|
-
class
|
|
28
|
-
is_debug
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
class ConfigReadable:
|
|
32
|
+
def is_debug() -> bool:
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
def get(self, key: str, default: T) -> T:
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
class ConfigHolder:
|
|
39
|
+
# the singleton pattern
|
|
40
|
+
def __new__(cls, *args, **kwargs):
|
|
41
|
+
if not hasattr(cls, 'instance'): cls.instance = super(ConfigHolder, cls).__new__(cls)
|
|
42
|
+
|
|
43
|
+
return cls.instance
|
|
44
|
+
|
|
45
|
+
def __init__(self):
|
|
46
|
+
if not hasattr(self, 'config'):
|
|
47
|
+
# set by Config
|
|
48
|
+
self.config: 'ConfigReadable' = None
|
|
49
|
+
# only for testing
|
|
50
|
+
self.is_display_help = True
|
|
51
|
+
# set by ReplSession
|
|
52
|
+
self.append_command_history = lambda entry: None
|
|
53
|
+
|
|
54
|
+
NO_SORT = 0
|
|
55
|
+
SORT = 1
|
|
56
|
+
REVERSE_SORT = -1
|
|
57
|
+
|
|
58
|
+
def tabulize(lines: list[T], fn: Callable[..., T] = None, header: str = None, dashed_line = False, separator = ' ', to: int = 1, sorted: int = NO_SORT):
|
|
59
|
+
if fn:
|
|
60
|
+
lines = list(map(fn, lines))
|
|
31
61
|
|
|
32
|
-
|
|
33
|
-
|
|
62
|
+
if sorted == SORT:
|
|
63
|
+
lines.sort()
|
|
64
|
+
elif sorted == REVERSE_SORT:
|
|
65
|
+
lines.sort(reverse=True)
|
|
34
66
|
|
|
35
|
-
def lines_to_tabular(lines: list[str], header: str = None, dashed_line = False, separator = ' '):
|
|
36
67
|
maxes = []
|
|
37
68
|
nls = []
|
|
38
69
|
|
|
@@ -63,7 +94,14 @@ def lines_to_tabular(lines: list[str], header: str = None, dashed_line = False,
|
|
|
63
94
|
for line in lines:
|
|
64
95
|
format_line(line)
|
|
65
96
|
|
|
66
|
-
|
|
97
|
+
table = '\n'.join(nls)
|
|
98
|
+
|
|
99
|
+
if to == 1:
|
|
100
|
+
log(table)
|
|
101
|
+
elif to == 2:
|
|
102
|
+
log2(table)
|
|
103
|
+
|
|
104
|
+
return table
|
|
67
105
|
|
|
68
106
|
def convert_seconds(total_seconds_float):
|
|
69
107
|
total_seconds_int = int(total_seconds_float) # Convert float to integer seconds
|
|
@@ -117,7 +155,7 @@ def duration(start_time: float, end_time: float = None):
|
|
|
117
155
|
d = convert_seconds(end_time - start_time)
|
|
118
156
|
t = []
|
|
119
157
|
if d:
|
|
120
|
-
t.append(f'{d}h')
|
|
158
|
+
t.append(f'{d[0]}h')
|
|
121
159
|
if t or d[1]:
|
|
122
160
|
t.append(f'{d[1]}m')
|
|
123
161
|
t.append(f'{d[2]}s')
|
|
@@ -142,7 +180,11 @@ def deep_merge_dicts(dict1, dict2):
|
|
|
142
180
|
merged_dict[key] = deep_merge_dicts(merged_dict[key], value)
|
|
143
181
|
elif key not in merged_dict or value:
|
|
144
182
|
# Otherwise, overwrite or add the value from dict2
|
|
145
|
-
merged_dict[key]
|
|
183
|
+
if key in merged_dict and isinstance(merged_dict[key], Completer):
|
|
184
|
+
pass
|
|
185
|
+
# print('SEAN completer found, ignoring', key, value)
|
|
186
|
+
else:
|
|
187
|
+
merged_dict[key] = value
|
|
146
188
|
return merged_dict
|
|
147
189
|
|
|
148
190
|
def deep_sort_dict(d):
|
|
@@ -180,7 +222,7 @@ def get_deep_keys(d, current_path=""):
|
|
|
180
222
|
return keys
|
|
181
223
|
|
|
182
224
|
def display_help(replace_arg = False):
|
|
183
|
-
if not
|
|
225
|
+
if not ConfigHolder().is_display_help:
|
|
184
226
|
return
|
|
185
227
|
|
|
186
228
|
args = copy.copy(sys.argv)
|
|
@@ -233,7 +275,7 @@ def json_to_csv(json_data: list[dict[any, any]], delimiter: str = ','):
|
|
|
233
275
|
return None
|
|
234
276
|
|
|
235
277
|
def log_to_file(config: dict[any, any]):
|
|
236
|
-
|
|
278
|
+
with log_exc():
|
|
237
279
|
base = f"/kaqing/logs"
|
|
238
280
|
os.makedirs(base, exist_ok=True)
|
|
239
281
|
|
|
@@ -248,8 +290,6 @@ def log_to_file(config: dict[any, any]):
|
|
|
248
290
|
f.write(config)
|
|
249
291
|
else:
|
|
250
292
|
f.write(config)
|
|
251
|
-
except:
|
|
252
|
-
pass
|
|
253
293
|
|
|
254
294
|
def copy_config_file(rel_path: str, module: str, suffix: str = '.yaml', show_out = True):
|
|
255
295
|
dir = f'{Path.home()}/.kaqing'
|
|
@@ -271,9 +311,31 @@ def is_lambda(func):
|
|
|
271
311
|
return callable(func) and hasattr(func, '__name__') and func.__name__ == '<lambda>'
|
|
272
312
|
|
|
273
313
|
def debug(s = None):
|
|
274
|
-
if
|
|
314
|
+
if ConfigHolder().config.is_debug():
|
|
275
315
|
log2(f'DEBUG {s}')
|
|
276
316
|
|
|
317
|
+
def debug_complete(s = None):
|
|
318
|
+
CommandLog.log(f'DEBUG {s}', config=ConfigHolder().config.get('debugs.complete', 'off'))
|
|
319
|
+
|
|
320
|
+
def debug_trace():
|
|
321
|
+
if ConfigHolder().config.is_debug():
|
|
322
|
+
# if LogConfig.is_debug():
|
|
323
|
+
log2(traceback.format_exc())
|
|
324
|
+
|
|
325
|
+
def in_docker() -> bool:
|
|
326
|
+
if os.path.exists('/.dockerenv'):
|
|
327
|
+
return True
|
|
328
|
+
|
|
329
|
+
try:
|
|
330
|
+
with open('/proc/1/cgroup', 'rt') as f:
|
|
331
|
+
for line in f:
|
|
332
|
+
if 'docker' in line or 'lxc' in line:
|
|
333
|
+
return True
|
|
334
|
+
except FileNotFoundError:
|
|
335
|
+
pass
|
|
336
|
+
|
|
337
|
+
return False
|
|
338
|
+
|
|
277
339
|
class Ing:
|
|
278
340
|
def __init__(self, msg: str, suppress_log=False):
|
|
279
341
|
self.msg = msg
|
|
@@ -285,7 +347,7 @@ class Ing:
|
|
|
285
347
|
|
|
286
348
|
try:
|
|
287
349
|
if not log_state.ing_cnt:
|
|
288
|
-
if not self.suppress_log and not
|
|
350
|
+
if not self.suppress_log and not ConfigHolder().config.is_debug():
|
|
289
351
|
log2(f'{self.msg}...', nl=False)
|
|
290
352
|
|
|
291
353
|
return None
|
|
@@ -295,7 +357,7 @@ class Ing:
|
|
|
295
357
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
296
358
|
log_state.ing_cnt -= 1
|
|
297
359
|
if not log_state.ing_cnt:
|
|
298
|
-
if not self.suppress_log and not
|
|
360
|
+
if not self.suppress_log and not ConfigHolder().config.is_debug():
|
|
299
361
|
log2(' OK')
|
|
300
362
|
|
|
301
363
|
return False
|
|
@@ -316,7 +378,7 @@ def ing(msg: str, body: Callable[[], None]=None, suppress_log=False):
|
|
|
316
378
|
return r
|
|
317
379
|
|
|
318
380
|
def loggable():
|
|
319
|
-
return
|
|
381
|
+
return ConfigHolder().config and ConfigHolder().config.is_debug() or not hasattr(log_state, 'ing_cnt') or not log_state.ing_cnt
|
|
320
382
|
|
|
321
383
|
class TimingNode:
|
|
322
384
|
def __init__(self, depth: int, s0: time.time = time.time(), line: str = None):
|
|
@@ -344,7 +406,7 @@ class LogTiming:
|
|
|
344
406
|
self.s0 = s0
|
|
345
407
|
|
|
346
408
|
def __enter__(self):
|
|
347
|
-
if
|
|
409
|
+
if (config := ConfigHolder().config.get('debugs.timings', 'off')) not in ['on', 'file']:
|
|
348
410
|
return
|
|
349
411
|
|
|
350
412
|
if not hasattr(log_state, 'timings'):
|
|
@@ -356,7 +418,7 @@ class LogTiming:
|
|
|
356
418
|
self.s0 = time.time()
|
|
357
419
|
|
|
358
420
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
359
|
-
if
|
|
421
|
+
if (config := ConfigHolder().config.get('debugs.timings', 'off')) not in ['on', 'file']:
|
|
360
422
|
return False
|
|
361
423
|
|
|
362
424
|
child = log_state.timings
|
|
@@ -367,7 +429,9 @@ class LogTiming:
|
|
|
367
429
|
log_state.timings = self.me
|
|
368
430
|
|
|
369
431
|
if not self.me.depth:
|
|
370
|
-
|
|
432
|
+
# log timings finally
|
|
433
|
+
CommandLog.log(self.me.tree(), config)
|
|
434
|
+
|
|
371
435
|
log_state.timings = TimingNode(0)
|
|
372
436
|
|
|
373
437
|
return False
|
|
@@ -376,7 +440,7 @@ def log_timing(msg: str, body: Callable[[], None]=None, s0: time.time = None):
|
|
|
376
440
|
if not s0 and not body:
|
|
377
441
|
return LogTiming(msg, s0=s0)
|
|
378
442
|
|
|
379
|
-
if not
|
|
443
|
+
if not ConfigHolder().config.get('debugs.timings', False):
|
|
380
444
|
if body:
|
|
381
445
|
return body()
|
|
382
446
|
|
|
@@ -396,7 +460,9 @@ def log_timing(msg: str, body: Callable[[], None]=None, s0: time.time = None):
|
|
|
396
460
|
|
|
397
461
|
def timing_log_line(depth: int, msg: str, s0: time.time):
|
|
398
462
|
elapsed = time.time() - s0
|
|
399
|
-
|
|
463
|
+
offloaded = '-' if threading.current_thread().name.startswith('offload') or threading.current_thread().name.startswith('async') else '+'
|
|
464
|
+
prefix = f'[{offloaded} timings] '
|
|
465
|
+
|
|
400
466
|
if depth:
|
|
401
467
|
if elapsed > 0.01:
|
|
402
468
|
prefix = (' ' * (depth-1)) + '* '
|
|
@@ -416,7 +482,90 @@ def wait_log(msg: str):
|
|
|
416
482
|
def clear_wait_log_flag():
|
|
417
483
|
WaitLog.wait_log_flag = False
|
|
418
484
|
|
|
419
|
-
|
|
485
|
+
def bytes_generator_from_file(file_path, chunk_size=4096):
|
|
486
|
+
with open(file_path, 'rb') as f:
|
|
487
|
+
while True:
|
|
488
|
+
chunk = f.read(chunk_size)
|
|
489
|
+
if not chunk:
|
|
490
|
+
break
|
|
491
|
+
yield chunk
|
|
492
|
+
|
|
493
|
+
class GeneratorStream(io.RawIOBase):
|
|
494
|
+
def __init__(self, generator):
|
|
495
|
+
self._generator = generator
|
|
496
|
+
self._buffer = b'' # Buffer to store leftover bytes from generator yields
|
|
497
|
+
|
|
498
|
+
def readable(self):
|
|
499
|
+
return True
|
|
500
|
+
|
|
501
|
+
def _read_from_generator(self):
|
|
502
|
+
try:
|
|
503
|
+
chunk = next(self._generator)
|
|
504
|
+
if isinstance(chunk, str):
|
|
505
|
+
chunk = chunk.encode('utf-8') # Encode if generator yields strings
|
|
506
|
+
self._buffer += chunk
|
|
507
|
+
except StopIteration:
|
|
508
|
+
pass # Generator exhausted
|
|
509
|
+
|
|
510
|
+
def readinto(self, b):
|
|
511
|
+
# Fill the buffer if necessary
|
|
512
|
+
while len(self._buffer) < len(b):
|
|
513
|
+
old_buffer_len = len(self._buffer)
|
|
514
|
+
self._read_from_generator()
|
|
515
|
+
if len(self._buffer) == old_buffer_len: # Generator exhausted and buffer empty
|
|
516
|
+
break
|
|
517
|
+
|
|
518
|
+
bytes_to_read = min(len(b), len(self._buffer))
|
|
519
|
+
b[:bytes_to_read] = self._buffer[:bytes_to_read]
|
|
520
|
+
self._buffer = self._buffer[bytes_to_read:]
|
|
521
|
+
return bytes_to_read
|
|
522
|
+
|
|
523
|
+
def read(self, size=-1):
|
|
524
|
+
if size == -1: # Read all remaining data
|
|
525
|
+
while True:
|
|
526
|
+
old_buffer_len = len(self._buffer)
|
|
527
|
+
self._read_from_generator()
|
|
528
|
+
if len(self._buffer) == old_buffer_len:
|
|
529
|
+
break
|
|
530
|
+
data = self._buffer
|
|
531
|
+
self._buffer = b''
|
|
532
|
+
return data
|
|
533
|
+
else:
|
|
534
|
+
# Ensure enough data in buffer
|
|
535
|
+
while len(self._buffer) < size:
|
|
536
|
+
old_buffer_len = len(self._buffer)
|
|
537
|
+
self._read_from_generator()
|
|
538
|
+
if len(self._buffer) == old_buffer_len:
|
|
539
|
+
break
|
|
540
|
+
|
|
541
|
+
data = self._buffer[:size]
|
|
542
|
+
self._buffer = self._buffer[size:]
|
|
543
|
+
return data
|
|
544
|
+
|
|
545
|
+
class LogTrace:
|
|
546
|
+
def __init__(self, err_msg: Union[str, callable, bool] = None):
|
|
547
|
+
self.err_msg = err_msg
|
|
548
|
+
|
|
549
|
+
def __enter__(self):
|
|
550
|
+
return None
|
|
551
|
+
|
|
552
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
553
|
+
if exc_type is not None:
|
|
554
|
+
if self.err_msg is True:
|
|
555
|
+
log2(str(exc_val))
|
|
556
|
+
elif callable(self.err_msg):
|
|
557
|
+
log2(self.err_msg(exc_val))
|
|
558
|
+
elif self.err_msg is not False and self.err_msg:
|
|
559
|
+
log2(self.err_msg)
|
|
560
|
+
|
|
561
|
+
if self.err_msg is not False and ConfigHolder().config.is_debug():
|
|
562
|
+
traceback.print_exception(exc_type, exc_val, exc_tb, file=sys.stderr)
|
|
563
|
+
|
|
564
|
+
# swallow exception
|
|
565
|
+
return True
|
|
566
|
+
|
|
567
|
+
def log_exc(err_msg: Union[str, callable, bool] = None):
|
|
568
|
+
return LogTrace(err_msg=err_msg)
|
|
420
569
|
|
|
421
570
|
class ParallelService:
|
|
422
571
|
def __init__(self, handler: 'ParallelMapHandler'):
|
|
@@ -450,37 +599,44 @@ class ParallelService:
|
|
|
450
599
|
else:
|
|
451
600
|
return iterator
|
|
452
601
|
|
|
602
|
+
thread_pools: dict[str, ThreadPoolExecutor] = {}
|
|
603
|
+
thread_pool_lock = threading.Lock()
|
|
604
|
+
|
|
453
605
|
class ParallelMapHandler:
|
|
454
|
-
def __init__(self, collection: list,
|
|
606
|
+
def __init__(self, collection: list, workers: int, samples: int = sys.maxsize, msg: str = None, collect = True, name = None):
|
|
455
607
|
self.collection = collection
|
|
456
|
-
self.
|
|
608
|
+
self.workers = workers
|
|
457
609
|
self.executor = None
|
|
458
610
|
self.samples = samples
|
|
459
611
|
self.msg = msg
|
|
460
612
|
if msg and msg.startswith('d`'):
|
|
461
|
-
if
|
|
613
|
+
if ConfigHolder().config.is_debug():
|
|
462
614
|
self.msg = msg.replace('d`', '', 1)
|
|
463
615
|
else:
|
|
464
616
|
self.msg = None
|
|
465
617
|
self.collect = collect
|
|
618
|
+
self.name = name
|
|
466
619
|
|
|
467
620
|
self.begin = []
|
|
468
621
|
self.end = []
|
|
469
622
|
self.start_time = None
|
|
470
623
|
|
|
471
624
|
def __enter__(self):
|
|
625
|
+
self.start_time = None
|
|
626
|
+
|
|
472
627
|
self.calc_msgs()
|
|
473
628
|
|
|
474
|
-
if self.
|
|
629
|
+
if self.workers > 1 and (not self.size() or self.size()) and self.samples == sys.maxsize:
|
|
475
630
|
self.start_time = time.time()
|
|
476
631
|
|
|
477
|
-
self.executor =
|
|
632
|
+
self.executor = self.pool()
|
|
633
|
+
# self.executor = ThreadPoolExecutor(max_workers=self.workers)
|
|
478
634
|
self.executor.__enter__()
|
|
479
635
|
|
|
480
636
|
return ParallelService(self)
|
|
481
637
|
|
|
482
638
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
483
|
-
if self.executor:
|
|
639
|
+
if not self.name and self.executor:
|
|
484
640
|
self.executor.__exit__(exc_type, exc_val, exc_tb)
|
|
485
641
|
|
|
486
642
|
if self.end:
|
|
@@ -488,6 +644,15 @@ class ParallelMapHandler:
|
|
|
488
644
|
|
|
489
645
|
return False
|
|
490
646
|
|
|
647
|
+
def pool(self, thread_name_prefix: str = None):
|
|
648
|
+
if not self.name:
|
|
649
|
+
return ThreadPoolExecutor(max_workers=self.workers)
|
|
650
|
+
|
|
651
|
+
if self.name not in thread_pools:
|
|
652
|
+
thread_pools[self.name] = ThreadPoolExecutor(max_workers=self.workers, thread_name_prefix=thread_name_prefix)
|
|
653
|
+
|
|
654
|
+
return thread_pools[self.name]
|
|
655
|
+
|
|
491
656
|
def size(self):
|
|
492
657
|
if not self.collection:
|
|
493
658
|
return 0
|
|
@@ -498,28 +663,28 @@ class ParallelMapHandler:
|
|
|
498
663
|
if not self.msg:
|
|
499
664
|
return
|
|
500
665
|
|
|
666
|
+
self.begin = []
|
|
667
|
+
self.end = []
|
|
501
668
|
size = self.size()
|
|
502
|
-
# return
|
|
503
|
-
|
|
504
669
|
offloaded = False
|
|
505
670
|
serially = False
|
|
506
671
|
sampling = False
|
|
507
672
|
if size == 0:
|
|
508
673
|
offloaded = True
|
|
509
|
-
|
|
510
|
-
elif self.
|
|
511
|
-
|
|
674
|
+
msg = self.msg.replace('{size}', '1')
|
|
675
|
+
elif self.workers > 1 and size > 1 and self.samples == sys.maxsize:
|
|
676
|
+
msg = self.msg.replace('{size}', f'{size}')
|
|
512
677
|
elif self.samples < sys.maxsize:
|
|
513
678
|
sampling = True
|
|
679
|
+
samples = self.samples
|
|
514
680
|
if self.samples > size:
|
|
515
|
-
|
|
516
|
-
|
|
681
|
+
samples = size
|
|
682
|
+
msg = self.msg.replace('{size}', f'{samples}/{size} sample')
|
|
517
683
|
else:
|
|
518
684
|
serially = True
|
|
519
|
-
|
|
520
|
-
# return
|
|
685
|
+
msg = self.msg.replace('{size}', f'{size}')
|
|
521
686
|
|
|
522
|
-
for token in
|
|
687
|
+
for token in msg.split(' '):
|
|
523
688
|
if '|' in token:
|
|
524
689
|
self.begin.append(token.split('|')[0])
|
|
525
690
|
if not sampling and not serially and not offloaded:
|
|
@@ -534,7 +699,21 @@ class ParallelMapHandler:
|
|
|
534
699
|
elif sampling or serially:
|
|
535
700
|
log2(f'{" ".join(self.begin)} serially...')
|
|
536
701
|
else:
|
|
537
|
-
log2(f'{" ".join(self.begin)} with {self.
|
|
702
|
+
log2(f'{" ".join(self.begin)} with {self.workers} workers...')
|
|
703
|
+
|
|
704
|
+
# parallelizers: dict[str, ParallelMapHandler] = {}
|
|
705
|
+
# parallelizer_lock = threading.Lock()
|
|
706
|
+
|
|
707
|
+
def parallelize(collection: list, workers: int = 0, samples = sys.maxsize, msg: str = None, collect = True, name = None):
|
|
708
|
+
return ParallelMapHandler(collection, workers, samples = samples, msg = msg, collect = collect, name = name)
|
|
709
|
+
# if not name:
|
|
710
|
+
# return ParallelMapHandler(collection, workers, samples = samples, msg = msg, collect = collect)
|
|
711
|
+
|
|
712
|
+
# with parallelizer_lock:
|
|
713
|
+
# if name not in parallelizers:
|
|
714
|
+
# parallelizers[name] = ParallelMapHandler(collection, workers, samples = samples, msg = msg, collect = collect, name = name)
|
|
715
|
+
|
|
716
|
+
# return parallelizers[name]
|
|
538
717
|
|
|
539
718
|
class OffloadService:
|
|
540
719
|
def __init__(self, handler: 'OffloadHandler'):
|
|
@@ -553,22 +732,24 @@ class OffloadService:
|
|
|
553
732
|
return future
|
|
554
733
|
|
|
555
734
|
class OffloadHandler(ParallelMapHandler):
|
|
556
|
-
def __init__(self, max_workers: int, msg: str = None):
|
|
557
|
-
super().__init__(None, max_workers, msg=msg, collect=False )
|
|
735
|
+
def __init__(self, max_workers: int, msg: str = None, name: str = None):
|
|
736
|
+
super().__init__(None, max_workers, msg=msg, collect=False, name=f'offload-{name}')
|
|
558
737
|
|
|
559
738
|
def __enter__(self):
|
|
739
|
+
self.start_time = None
|
|
560
740
|
self.calc_msgs()
|
|
561
741
|
|
|
562
|
-
if self.
|
|
742
|
+
if self.workers > 1:
|
|
563
743
|
self.start_time = time.time()
|
|
564
744
|
|
|
565
|
-
self.executor =
|
|
745
|
+
self.executor = self.pool(thread_name_prefix='offload')
|
|
746
|
+
# self.executor = ThreadPoolExecutor(max_workers=self.workers, thread_name_prefix='offload')
|
|
566
747
|
self.executor.__enter__()
|
|
567
748
|
|
|
568
749
|
return OffloadService(self)
|
|
569
750
|
|
|
570
751
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
571
|
-
if self.executor:
|
|
752
|
+
if not self.name and self.executor:
|
|
572
753
|
self.executor.__exit__(exc_type, exc_val, exc_tb)
|
|
573
754
|
|
|
574
755
|
if self.end:
|
|
@@ -576,38 +757,33 @@ class OffloadHandler(ParallelMapHandler):
|
|
|
576
757
|
|
|
577
758
|
return False
|
|
578
759
|
|
|
579
|
-
def size(self):
|
|
580
|
-
if not self.collection:
|
|
581
|
-
return 0
|
|
582
|
-
|
|
583
|
-
return len(self.collection)
|
|
584
|
-
|
|
585
760
|
def calc_msgs(self):
|
|
586
761
|
if not self.msg:
|
|
587
762
|
return
|
|
588
763
|
|
|
764
|
+
self.begin = []
|
|
765
|
+
self.end = []
|
|
589
766
|
size = self.size()
|
|
590
|
-
# return
|
|
591
767
|
|
|
592
768
|
offloaded = False
|
|
593
769
|
serially = False
|
|
594
770
|
sampling = False
|
|
595
771
|
if size == 0:
|
|
596
772
|
offloaded = True
|
|
597
|
-
|
|
598
|
-
elif self.
|
|
599
|
-
|
|
773
|
+
msg = self.msg.replace('{size}', '1')
|
|
774
|
+
elif self.workers > 1 and size > 1 and self.samples == sys.maxsize:
|
|
775
|
+
msg = self.msg.replace('{size}', f'{size}')
|
|
600
776
|
elif self.samples < sys.maxsize:
|
|
601
777
|
sampling = True
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
778
|
+
samples = self.samples
|
|
779
|
+
if samples > size:
|
|
780
|
+
samples = size
|
|
781
|
+
msg = self.msg.replace('{size}', f'{samples}/{size} sample')
|
|
605
782
|
else:
|
|
606
783
|
serially = True
|
|
607
|
-
|
|
608
|
-
# return
|
|
784
|
+
msg = self.msg.replace('{size}', f'{size}')
|
|
609
785
|
|
|
610
|
-
for token in
|
|
786
|
+
for token in msg.split(' '):
|
|
611
787
|
if '|' in token:
|
|
612
788
|
self.begin.append(token.split('|')[0])
|
|
613
789
|
if not sampling and not serially and not offloaded:
|
|
@@ -622,58 +798,76 @@ class OffloadHandler(ParallelMapHandler):
|
|
|
622
798
|
elif sampling or serially:
|
|
623
799
|
log2(f'{" ".join(self.begin)} serially...')
|
|
624
800
|
else:
|
|
625
|
-
log2(f'{" ".join(self.begin)} with {self.
|
|
801
|
+
log2(f'{" ".join(self.begin)} with {self.workers} workers...')
|
|
626
802
|
|
|
627
|
-
#
|
|
628
|
-
#
|
|
629
|
-
# self.calc_msgs()
|
|
803
|
+
# offloaders: dict[str, OffloadHandler] = {}
|
|
804
|
+
# offloaders_lock = threading.Lock()
|
|
630
805
|
|
|
631
|
-
|
|
632
|
-
|
|
806
|
+
def offload(max_workers: int = 3, msg: str = None, name: str = None):
|
|
807
|
+
return OffloadHandler(max_workers, msg = msg, name = name)
|
|
808
|
+
# if not name:
|
|
809
|
+
# return OffloadHandler(max_workers, msg = msg)
|
|
633
810
|
|
|
634
|
-
#
|
|
635
|
-
#
|
|
811
|
+
# with offloaders_lock:
|
|
812
|
+
# if name not in offloaders:
|
|
813
|
+
# offloaders[name] = OffloadHandler(max_workers, msg = msg, name = name)
|
|
636
814
|
|
|
637
|
-
#
|
|
638
|
-
# return self.map
|
|
639
|
-
# else:
|
|
640
|
-
# return self.submit
|
|
815
|
+
# return offloaders[name]
|
|
641
816
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
# if self.executor:
|
|
645
|
-
# iterator = self.executor.map(fn, self.collection)
|
|
646
|
-
# elif self.samples < sys.maxsize:
|
|
647
|
-
# samples = []
|
|
817
|
+
def kaqing_log_file_name(suffix = 'log'):
|
|
818
|
+
return f"{ConfigHolder().config.get('log-prefix', '/tmp/qing')}-{datetime.now().strftime('%d%H%M%S')}.{suffix}"
|
|
648
819
|
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
820
|
+
class LogFileHandler:
|
|
821
|
+
def __init__(self, suffix = 'log'):
|
|
822
|
+
self.suffix = suffix
|
|
652
823
|
|
|
653
|
-
|
|
654
|
-
|
|
824
|
+
def __enter__(self):
|
|
825
|
+
self.f = open(kaqing_log_file_name(), 'w')
|
|
826
|
+
self.f.__enter__()
|
|
655
827
|
|
|
656
|
-
|
|
657
|
-
# else:
|
|
658
|
-
# iterator = map(fn, self.collection)
|
|
828
|
+
return self.f
|
|
659
829
|
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
# else:
|
|
663
|
-
# return iterator
|
|
830
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
831
|
+
self.f.__exit__(exc_type, exc_val, exc_tb)
|
|
664
832
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
# return self.executor.submit(fn, *args, **kwargs)
|
|
668
|
-
# else:
|
|
669
|
-
# future = Future()
|
|
833
|
+
if ConfigHolder().append_command_history:
|
|
834
|
+
ConfigHolder().append_command_history(f':sh cat {self.f.name}')
|
|
670
835
|
|
|
671
|
-
|
|
836
|
+
return False
|
|
837
|
+
|
|
838
|
+
def kaqing_log_file(suffix = 'log'):
|
|
839
|
+
return LogFileHandler(suffix = suffix)
|
|
840
|
+
|
|
841
|
+
class CommandLog:
|
|
842
|
+
log_file = None
|
|
672
843
|
|
|
673
|
-
|
|
844
|
+
def log(line: str, config: str = 'off'):
|
|
845
|
+
if config == 'file':
|
|
846
|
+
if not CommandLog.log_file:
|
|
847
|
+
try:
|
|
848
|
+
CommandLog.log_file = open(kaqing_log_file_name(suffix='cmd.log'), 'w')
|
|
849
|
+
except:
|
|
850
|
+
pass
|
|
674
851
|
|
|
675
|
-
|
|
676
|
-
|
|
852
|
+
try:
|
|
853
|
+
CommandLog.log_file.write(line + '\n')
|
|
854
|
+
except:
|
|
855
|
+
pass
|
|
856
|
+
elif config == 'on':
|
|
857
|
+
log2(line)
|
|
677
858
|
|
|
678
|
-
def
|
|
679
|
-
|
|
859
|
+
def close_log_file():
|
|
860
|
+
if CommandLog.log_file:
|
|
861
|
+
try:
|
|
862
|
+
CommandLog.log_file.close()
|
|
863
|
+
except:
|
|
864
|
+
pass
|
|
865
|
+
|
|
866
|
+
if ConfigHolder().append_command_history:
|
|
867
|
+
ConfigHolder().append_command_history(f':sh cat {CommandLog.log_file.name}')
|
|
868
|
+
|
|
869
|
+
CommandLog.log_file = None
|
|
870
|
+
|
|
871
|
+
class ExecResult(ABC):
|
|
872
|
+
def exit_code(self) -> int:
|
|
873
|
+
pass
|