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.
Files changed (194) hide show
  1. adam/app_session.py +5 -10
  2. adam/apps.py +18 -4
  3. adam/batch.py +7 -7
  4. adam/checks/check_utils.py +3 -1
  5. adam/checks/disk.py +2 -3
  6. adam/columns/memory.py +3 -4
  7. adam/commands/__init__.py +15 -6
  8. adam/commands/alter_tables.py +26 -41
  9. adam/commands/app/__init__.py +0 -0
  10. adam/commands/{app_cmd.py → app/app.py} +2 -2
  11. adam/commands/{show → app}/show_app_actions.py +7 -15
  12. adam/commands/{show → app}/show_app_queues.py +1 -4
  13. adam/{utils_app.py → commands/app/utils_app.py} +9 -1
  14. adam/commands/audit/audit.py +9 -26
  15. adam/commands/audit/audit_repair_tables.py +5 -7
  16. adam/commands/audit/audit_run.py +1 -1
  17. adam/commands/audit/completions_l.py +15 -0
  18. adam/commands/audit/show_last10.py +2 -14
  19. adam/commands/audit/show_slow10.py +2 -13
  20. adam/commands/audit/show_top10.py +2 -11
  21. adam/commands/audit/utils_show_top10.py +15 -3
  22. adam/commands/bash/bash.py +1 -1
  23. adam/commands/bash/utils_bash.py +1 -1
  24. adam/commands/cassandra/__init__.py +0 -0
  25. adam/commands/cassandra/download_cassandra_log.py +45 -0
  26. adam/commands/cassandra/nodetool.py +64 -0
  27. adam/commands/cassandra/nodetool_commands.py +120 -0
  28. adam/commands/cassandra/restart_cluster.py +47 -0
  29. adam/commands/cassandra/restart_node.py +51 -0
  30. adam/commands/cassandra/restart_nodes.py +47 -0
  31. adam/commands/cassandra/rollout.py +88 -0
  32. adam/commands/cat.py +5 -19
  33. adam/commands/cd.py +7 -9
  34. adam/commands/check.py +10 -18
  35. adam/commands/cli_commands.py +6 -1
  36. adam/commands/{cp.py → clipboard_copy.py} +34 -36
  37. adam/commands/code.py +2 -2
  38. adam/commands/command.py +139 -22
  39. adam/commands/commands_utils.py +14 -12
  40. adam/commands/cql/alter_tables.py +66 -0
  41. adam/commands/cql/completions_c.py +29 -0
  42. adam/commands/cql/cqlsh.py +3 -7
  43. adam/commands/cql/utils_cql.py +23 -61
  44. adam/commands/debug/__init__.py +0 -0
  45. adam/commands/debug/debug.py +22 -0
  46. adam/commands/debug/debug_completes.py +35 -0
  47. adam/commands/debug/debug_timings.py +35 -0
  48. adam/commands/deploy/deploy_pg_agent.py +2 -2
  49. adam/commands/deploy/deploy_pod.py +2 -4
  50. adam/commands/deploy/undeploy_pg_agent.py +2 -2
  51. adam/commands/devices/device.py +40 -9
  52. adam/commands/devices/device_app.py +19 -29
  53. adam/commands/devices/device_auit_log.py +3 -3
  54. adam/commands/devices/device_cass.py +17 -23
  55. adam/commands/devices/device_export.py +12 -11
  56. adam/commands/devices/device_postgres.py +79 -63
  57. adam/commands/devices/devices.py +1 -1
  58. adam/commands/download_cassandra_log.py +45 -0
  59. adam/commands/download_file.py +47 -0
  60. adam/commands/export/clean_up_all_export_sessions.py +3 -3
  61. adam/commands/export/clean_up_export_sessions.py +7 -19
  62. adam/commands/export/completions_x.py +11 -0
  63. adam/commands/export/download_export_session.py +40 -0
  64. adam/commands/export/drop_export_database.py +6 -22
  65. adam/commands/export/drop_export_databases.py +3 -9
  66. adam/commands/export/export.py +1 -17
  67. adam/commands/export/export_databases.py +109 -32
  68. adam/commands/export/export_select.py +8 -55
  69. adam/commands/export/export_sessions.py +211 -0
  70. adam/commands/export/export_use.py +13 -16
  71. adam/commands/export/export_x_select.py +48 -0
  72. adam/commands/export/exporter.py +176 -167
  73. adam/commands/export/import_files.py +44 -0
  74. adam/commands/export/import_session.py +10 -6
  75. adam/commands/export/importer.py +24 -9
  76. adam/commands/export/importer_athena.py +114 -44
  77. adam/commands/export/importer_sqlite.py +45 -23
  78. adam/commands/export/show_column_counts.py +11 -20
  79. adam/commands/export/show_export_databases.py +5 -2
  80. adam/commands/export/show_export_session.py +6 -15
  81. adam/commands/export/show_export_sessions.py +4 -11
  82. adam/commands/export/utils_export.py +79 -27
  83. adam/commands/find_files.py +51 -0
  84. adam/commands/find_processes.py +76 -0
  85. adam/commands/generate_report.py +52 -0
  86. adam/commands/head.py +36 -0
  87. adam/commands/help.py +2 -2
  88. adam/commands/intermediate_command.py +6 -3
  89. adam/commands/login.py +3 -6
  90. adam/commands/ls.py +2 -2
  91. adam/commands/medusa/medusa_backup.py +13 -16
  92. adam/commands/medusa/medusa_restore.py +26 -37
  93. adam/commands/medusa/medusa_show_backupjobs.py +7 -7
  94. adam/commands/medusa/medusa_show_restorejobs.py +6 -6
  95. adam/commands/medusa/utils_medusa.py +15 -0
  96. adam/commands/nodetool.py +3 -8
  97. adam/commands/os/__init__.py +0 -0
  98. adam/commands/os/cat.py +36 -0
  99. adam/commands/os/download_file.py +47 -0
  100. adam/commands/os/find_files.py +51 -0
  101. adam/commands/os/find_processes.py +76 -0
  102. adam/commands/os/head.py +36 -0
  103. adam/commands/os/shell.py +41 -0
  104. adam/commands/param_get.py +10 -12
  105. adam/commands/param_set.py +7 -10
  106. adam/commands/postgres/completions_p.py +22 -0
  107. adam/commands/postgres/postgres.py +25 -40
  108. adam/commands/postgres/postgres_databases.py +269 -0
  109. adam/commands/postgres/utils_postgres.py +33 -20
  110. adam/commands/preview_table.py +4 -2
  111. adam/commands/pwd.py +4 -6
  112. adam/commands/reaper/reaper_forward.py +2 -2
  113. adam/commands/reaper/reaper_run_abort.py +4 -10
  114. adam/commands/reaper/reaper_runs.py +3 -3
  115. adam/commands/reaper/reaper_schedule_activate.py +12 -12
  116. adam/commands/reaper/reaper_schedule_start.py +7 -12
  117. adam/commands/reaper/reaper_schedule_stop.py +7 -12
  118. adam/commands/reaper/utils_reaper.py +13 -6
  119. adam/commands/repair/repair_log.py +1 -4
  120. adam/commands/repair/repair_run.py +3 -8
  121. adam/commands/repair/repair_scan.py +1 -6
  122. adam/commands/repair/repair_stop.py +1 -5
  123. adam/commands/restart_cluster.py +47 -0
  124. adam/commands/restart_node.py +51 -0
  125. adam/commands/restart_nodes.py +47 -0
  126. adam/commands/shell.py +9 -2
  127. adam/commands/show/show.py +4 -4
  128. adam/commands/show/show_adam.py +3 -3
  129. adam/commands/show/show_cassandra_repairs.py +5 -6
  130. adam/commands/show/show_cassandra_status.py +29 -29
  131. adam/commands/show/show_cassandra_version.py +1 -4
  132. adam/commands/show/{show_commands.py → show_cli_commands.py} +3 -6
  133. adam/commands/show/show_login.py +3 -9
  134. adam/commands/show/show_params.py +2 -5
  135. adam/commands/show/show_processes.py +15 -16
  136. adam/commands/show/show_storage.py +9 -8
  137. adam/config.py +4 -5
  138. adam/embedded_params.py +1 -1
  139. adam/log.py +4 -4
  140. adam/repl.py +26 -18
  141. adam/repl_commands.py +32 -20
  142. adam/repl_session.py +9 -1
  143. adam/repl_state.py +39 -10
  144. adam/sql/async_executor.py +44 -0
  145. adam/sql/lark_completer.py +286 -0
  146. adam/sql/lark_parser.py +604 -0
  147. adam/sql/qingl.lark +1076 -0
  148. adam/sql/sql_completer.py +4 -6
  149. adam/sql/sql_state_machine.py +25 -13
  150. adam/sso/authn_ad.py +2 -5
  151. adam/sso/authn_okta.py +2 -4
  152. adam/sso/cred_cache.py +2 -5
  153. adam/sso/idp.py +8 -11
  154. adam/utils.py +299 -105
  155. adam/utils_athena.py +18 -18
  156. adam/utils_audits.py +3 -7
  157. adam/utils_issues.py +2 -2
  158. adam/utils_k8s/app_clusters.py +4 -4
  159. adam/utils_k8s/app_pods.py +8 -6
  160. adam/utils_k8s/cassandra_clusters.py +16 -5
  161. adam/utils_k8s/cassandra_nodes.py +7 -6
  162. adam/utils_k8s/custom_resources.py +11 -17
  163. adam/utils_k8s/jobs.py +7 -11
  164. adam/utils_k8s/k8s.py +14 -5
  165. adam/utils_k8s/kube_context.py +3 -6
  166. adam/{pod_exec_result.py → utils_k8s/pod_exec_result.py} +4 -4
  167. adam/utils_k8s/pods.py +98 -36
  168. adam/utils_k8s/statefulsets.py +5 -2
  169. adam/utils_local.py +42 -0
  170. adam/utils_repl/appendable_completer.py +6 -0
  171. adam/utils_repl/repl_completer.py +45 -2
  172. adam/utils_repl/state_machine.py +3 -3
  173. adam/utils_sqlite.py +58 -30
  174. adam/version.py +1 -1
  175. {kaqing-2.0.171.dist-info → kaqing-2.0.204.dist-info}/METADATA +1 -1
  176. kaqing-2.0.204.dist-info/RECORD +277 -0
  177. kaqing-2.0.204.dist-info/top_level.txt +2 -0
  178. teddy/__init__.py +0 -0
  179. teddy/lark_parser.py +436 -0
  180. teddy/lark_parser2.py +618 -0
  181. adam/commands/cql/cql_completions.py +0 -33
  182. adam/commands/export/export_handlers.py +0 -71
  183. adam/commands/export/export_select_x.py +0 -54
  184. adam/commands/logs.py +0 -37
  185. adam/commands/postgres/postgres_context.py +0 -274
  186. adam/commands/postgres/psql_completions.py +0 -10
  187. adam/commands/report.py +0 -61
  188. adam/commands/restart.py +0 -60
  189. kaqing-2.0.171.dist-info/RECORD +0 -236
  190. kaqing-2.0.171.dist-info/top_level.txt +0 -1
  191. /adam/commands/{app_ping.py → app/app_ping.py} +0 -0
  192. /adam/commands/{show → app}/show_app_id.py +0 -0
  193. {kaqing-2.0.171.dist-info → kaqing-2.0.204.dist-info}/WHEEL +0 -0
  194. {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 abstractmethod
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
- from typing import Callable, Iterator, TypeVar
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 LogConfig:
28
- is_debug = lambda: False
29
- is_debug_timing = lambda: False
30
- is_display_help = True
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
- def to_tabular(lines: str, header: str = None, dashed_line = False):
33
- return lines_to_tabular(lines.split('\n'), header, dashed_line)
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
- return '\n'.join(nls)
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] = value
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 LogConfig.is_display_help:
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
- try:
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 LogConfig.is_debug():
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 LogConfig.is_debug():
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 LogConfig.is_debug():
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 LogConfig.is_debug() or not hasattr(log_state, 'ing_cnt') or not log_state.ing_cnt
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 not LogConfig.is_debug_timing():
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 not LogConfig.is_debug_timing():
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
- log2(self.me.tree())
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 LogConfig.is_debug_timing():
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
- prefix = '[timings] '
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
- T = TypeVar('T')
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, max_workers: int, samples: int = sys.maxsize, msg: str = None, collect = True):
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.max_workers = max_workers
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 LogConfig.is_debug():
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.max_workers > 1 and (not self.size() or self.size()) and self.samples == sys.maxsize:
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 = ThreadPoolExecutor(max_workers=self.max_workers)
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
- self.msg = self.msg.replace('{size}', '1')
510
- elif self.max_workers > 1 and size > 1 and self.samples == sys.maxsize:
511
- self.msg = self.msg.replace('{size}', f'{size}')
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
- self.samples = size
516
- self.msg = self.msg.replace('{size}', f'{self.samples}/{size} sample')
681
+ samples = size
682
+ msg = self.msg.replace('{size}', f'{samples}/{size} sample')
517
683
  else:
518
684
  serially = True
519
- self.msg = self.msg.replace('{size}', f'{size}')
520
- # return
685
+ msg = self.msg.replace('{size}', f'{size}')
521
686
 
522
- for token in self.msg.split(' '):
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.max_workers} workers...')
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.max_workers > 1 and (not self.size() or self.size()) and self.samples == sys.maxsize:
742
+ if self.workers > 1:
563
743
  self.start_time = time.time()
564
744
 
565
- self.executor = ThreadPoolExecutor(max_workers=self.max_workers)
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
- self.msg = self.msg.replace('{size}', '1')
598
- elif self.max_workers > 1 and size > 1 and self.samples == sys.maxsize:
599
- self.msg = self.msg.replace('{size}', f'{size}')
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
- if self.samples > size:
603
- self.samples = size
604
- self.msg = self.msg.replace('{size}', f'{self.samples}/{size} sample')
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
- self.msg = self.msg.replace('{size}', f'{size}')
608
- # return
784
+ msg = self.msg.replace('{size}', f'{size}')
609
785
 
610
- for token in self.msg.split(' '):
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.max_workers} workers...')
801
+ log2(f'{" ".join(self.begin)} with {self.workers} workers...')
626
802
 
627
- # class ParallelMapHandler(ParallelHandler):
628
- # def __enter__(self):
629
- # self.calc_msgs()
803
+ # offloaders: dict[str, OffloadHandler] = {}
804
+ # offloaders_lock = threading.Lock()
630
805
 
631
- # if self.max_workers > 1 and (not self.size() or self.size()) and self.samples == sys.maxsize:
632
- # self.start_time = time.time()
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
- # self.executor = ThreadPoolExecutor(max_workers=self.max_workers)
635
- # self.executor.__enter__()
811
+ # with offloaders_lock:
812
+ # if name not in offloaders:
813
+ # offloaders[name] = OffloadHandler(max_workers, msg = msg, name = name)
636
814
 
637
- # if self.collection:
638
- # return self.map
639
- # else:
640
- # return self.submit
815
+ # return offloaders[name]
641
816
 
642
- # def map(self, fn: Callable[..., T]) -> Iterator[T]:
643
- # iterator = None
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
- # for elem in self.collection:
650
- # if not self.samples:
651
- # break
820
+ class LogFileHandler:
821
+ def __init__(self, suffix = 'log'):
822
+ self.suffix = suffix
652
823
 
653
- # samples.append(fn(elem))
654
- # self.samples -= 1
824
+ def __enter__(self):
825
+ self.f = open(kaqing_log_file_name(), 'w')
826
+ self.f.__enter__()
655
827
 
656
- # iterator = iter(samples)
657
- # else:
658
- # iterator = map(fn, self.collection)
828
+ return self.f
659
829
 
660
- # if self.collect:
661
- # return list(iterator)
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
- # def submit(self, fn: Callable[..., T], /, *args, **kwargs) -> Future[T]:
666
- # if self.executor:
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
- # future.set_result(fn(*args, **kwargs))
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
- # return future
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
- def parallelize(collection: list, max_workers: int = 0, samples = sys.maxsize, msg: str = None, collect = True):
676
- return ParallelMapHandler(collection, max_workers, samples = samples, msg = msg, collect = collect)
852
+ try:
853
+ CommandLog.log_file.write(line + '\n')
854
+ except:
855
+ pass
856
+ elif config == 'on':
857
+ log2(line)
677
858
 
678
- def offload(max_workers: int = 3, msg: str = None):
679
- return OffloadHandler(max_workers, msg = msg)
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