opengris-scaler 1.12.7__cp312-cp312-musllinux_1_2_x86_64.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.

Potentially problematic release.


This version of opengris-scaler might be problematic. Click here for more details.

Files changed (234) hide show
  1. opengris_scaler-1.12.7.dist-info/METADATA +729 -0
  2. opengris_scaler-1.12.7.dist-info/RECORD +234 -0
  3. opengris_scaler-1.12.7.dist-info/WHEEL +5 -0
  4. opengris_scaler-1.12.7.dist-info/entry_points.txt +9 -0
  5. opengris_scaler-1.12.7.dist-info/licenses/LICENSE +201 -0
  6. opengris_scaler-1.12.7.dist-info/licenses/LICENSE.spdx +7 -0
  7. opengris_scaler-1.12.7.dist-info/licenses/NOTICE +8 -0
  8. opengris_scaler.libs/libcapnp-1-61c06778.1.0.so +0 -0
  9. opengris_scaler.libs/libgcc_s-2298274a.so.1 +0 -0
  10. opengris_scaler.libs/libkj-1-21b63b70.1.0.so +0 -0
  11. opengris_scaler.libs/libstdc++-08d5c7eb.so.6.0.33 +0 -0
  12. scaler/CMakeLists.txt +11 -0
  13. scaler/__init__.py +14 -0
  14. scaler/about.py +5 -0
  15. scaler/client/__init__.py +0 -0
  16. scaler/client/agent/__init__.py +0 -0
  17. scaler/client/agent/client_agent.py +210 -0
  18. scaler/client/agent/disconnect_manager.py +27 -0
  19. scaler/client/agent/future_manager.py +112 -0
  20. scaler/client/agent/heartbeat_manager.py +74 -0
  21. scaler/client/agent/mixins.py +89 -0
  22. scaler/client/agent/object_manager.py +98 -0
  23. scaler/client/agent/task_manager.py +64 -0
  24. scaler/client/client.py +635 -0
  25. scaler/client/future.py +252 -0
  26. scaler/client/object_buffer.py +129 -0
  27. scaler/client/object_reference.py +25 -0
  28. scaler/client/serializer/__init__.py +0 -0
  29. scaler/client/serializer/default.py +16 -0
  30. scaler/client/serializer/mixins.py +38 -0
  31. scaler/cluster/__init__.py +0 -0
  32. scaler/cluster/cluster.py +115 -0
  33. scaler/cluster/combo.py +148 -0
  34. scaler/cluster/object_storage_server.py +45 -0
  35. scaler/cluster/scheduler.py +83 -0
  36. scaler/config/__init__.py +0 -0
  37. scaler/config/defaults.py +87 -0
  38. scaler/config/loader.py +95 -0
  39. scaler/config/mixins.py +15 -0
  40. scaler/config/section/__init__.py +0 -0
  41. scaler/config/section/cluster.py +56 -0
  42. scaler/config/section/native_worker_adapter.py +44 -0
  43. scaler/config/section/object_storage_server.py +7 -0
  44. scaler/config/section/scheduler.py +53 -0
  45. scaler/config/section/symphony_worker_adapter.py +47 -0
  46. scaler/config/section/top.py +13 -0
  47. scaler/config/section/webui.py +16 -0
  48. scaler/config/types/__init__.py +0 -0
  49. scaler/config/types/object_storage_server.py +45 -0
  50. scaler/config/types/worker.py +57 -0
  51. scaler/config/types/zmq.py +79 -0
  52. scaler/entry_points/__init__.py +0 -0
  53. scaler/entry_points/cluster.py +133 -0
  54. scaler/entry_points/object_storage_server.py +41 -0
  55. scaler/entry_points/scheduler.py +135 -0
  56. scaler/entry_points/top.py +286 -0
  57. scaler/entry_points/webui.py +26 -0
  58. scaler/entry_points/worker_adapter_native.py +137 -0
  59. scaler/entry_points/worker_adapter_symphony.py +102 -0
  60. scaler/io/__init__.py +0 -0
  61. scaler/io/async_binder.py +85 -0
  62. scaler/io/async_connector.py +95 -0
  63. scaler/io/async_object_storage_connector.py +185 -0
  64. scaler/io/mixins.py +154 -0
  65. scaler/io/sync_connector.py +68 -0
  66. scaler/io/sync_object_storage_connector.py +185 -0
  67. scaler/io/sync_subscriber.py +83 -0
  68. scaler/io/utility.py +31 -0
  69. scaler/io/ymq/CMakeLists.txt +98 -0
  70. scaler/io/ymq/__init__.py +0 -0
  71. scaler/io/ymq/_ymq.pyi +96 -0
  72. scaler/io/ymq/_ymq.so +0 -0
  73. scaler/io/ymq/bytes.h +114 -0
  74. scaler/io/ymq/common.h +29 -0
  75. scaler/io/ymq/configuration.h +60 -0
  76. scaler/io/ymq/epoll_context.cpp +185 -0
  77. scaler/io/ymq/epoll_context.h +85 -0
  78. scaler/io/ymq/error.h +132 -0
  79. scaler/io/ymq/event_loop.h +55 -0
  80. scaler/io/ymq/event_loop_thread.cpp +64 -0
  81. scaler/io/ymq/event_loop_thread.h +46 -0
  82. scaler/io/ymq/event_manager.h +81 -0
  83. scaler/io/ymq/file_descriptor.h +203 -0
  84. scaler/io/ymq/interruptive_concurrent_queue.h +169 -0
  85. scaler/io/ymq/io_context.cpp +98 -0
  86. scaler/io/ymq/io_context.h +44 -0
  87. scaler/io/ymq/io_socket.cpp +299 -0
  88. scaler/io/ymq/io_socket.h +121 -0
  89. scaler/io/ymq/iocp_context.cpp +102 -0
  90. scaler/io/ymq/iocp_context.h +83 -0
  91. scaler/io/ymq/logging.h +163 -0
  92. scaler/io/ymq/message.h +15 -0
  93. scaler/io/ymq/message_connection.h +16 -0
  94. scaler/io/ymq/message_connection_tcp.cpp +672 -0
  95. scaler/io/ymq/message_connection_tcp.h +96 -0
  96. scaler/io/ymq/network_utils.h +179 -0
  97. scaler/io/ymq/pymod_ymq/bytes.h +113 -0
  98. scaler/io/ymq/pymod_ymq/exception.h +124 -0
  99. scaler/io/ymq/pymod_ymq/gil.h +15 -0
  100. scaler/io/ymq/pymod_ymq/io_context.h +166 -0
  101. scaler/io/ymq/pymod_ymq/io_socket.h +285 -0
  102. scaler/io/ymq/pymod_ymq/message.h +99 -0
  103. scaler/io/ymq/pymod_ymq/python.h +153 -0
  104. scaler/io/ymq/pymod_ymq/ymq.cpp +23 -0
  105. scaler/io/ymq/pymod_ymq/ymq.h +357 -0
  106. scaler/io/ymq/readme.md +114 -0
  107. scaler/io/ymq/simple_interface.cpp +80 -0
  108. scaler/io/ymq/simple_interface.h +24 -0
  109. scaler/io/ymq/tcp_client.cpp +367 -0
  110. scaler/io/ymq/tcp_client.h +75 -0
  111. scaler/io/ymq/tcp_operations.h +41 -0
  112. scaler/io/ymq/tcp_server.cpp +410 -0
  113. scaler/io/ymq/tcp_server.h +79 -0
  114. scaler/io/ymq/third_party/concurrentqueue.h +3747 -0
  115. scaler/io/ymq/timed_queue.h +272 -0
  116. scaler/io/ymq/timestamp.h +102 -0
  117. scaler/io/ymq/typedefs.h +20 -0
  118. scaler/io/ymq/utils.h +34 -0
  119. scaler/io/ymq/ymq.py +130 -0
  120. scaler/object_storage/CMakeLists.txt +50 -0
  121. scaler/object_storage/__init__.py +0 -0
  122. scaler/object_storage/constants.h +11 -0
  123. scaler/object_storage/defs.h +14 -0
  124. scaler/object_storage/io_helper.cpp +44 -0
  125. scaler/object_storage/io_helper.h +9 -0
  126. scaler/object_storage/message.cpp +56 -0
  127. scaler/object_storage/message.h +130 -0
  128. scaler/object_storage/object_manager.cpp +126 -0
  129. scaler/object_storage/object_manager.h +52 -0
  130. scaler/object_storage/object_storage_server.cpp +359 -0
  131. scaler/object_storage/object_storage_server.h +126 -0
  132. scaler/object_storage/object_storage_server.so +0 -0
  133. scaler/object_storage/pymod_object_storage_server.cpp +104 -0
  134. scaler/protocol/__init__.py +0 -0
  135. scaler/protocol/capnp/__init__.py +0 -0
  136. scaler/protocol/capnp/_python.py +6 -0
  137. scaler/protocol/capnp/common.capnp +63 -0
  138. scaler/protocol/capnp/message.capnp +216 -0
  139. scaler/protocol/capnp/object_storage.capnp +52 -0
  140. scaler/protocol/capnp/status.capnp +73 -0
  141. scaler/protocol/introduction.md +105 -0
  142. scaler/protocol/python/__init__.py +0 -0
  143. scaler/protocol/python/common.py +135 -0
  144. scaler/protocol/python/message.py +726 -0
  145. scaler/protocol/python/mixins.py +13 -0
  146. scaler/protocol/python/object_storage.py +118 -0
  147. scaler/protocol/python/status.py +279 -0
  148. scaler/protocol/worker.md +228 -0
  149. scaler/scheduler/__init__.py +0 -0
  150. scaler/scheduler/allocate_policy/__init__.py +0 -0
  151. scaler/scheduler/allocate_policy/allocate_policy.py +9 -0
  152. scaler/scheduler/allocate_policy/capability_allocate_policy.py +280 -0
  153. scaler/scheduler/allocate_policy/even_load_allocate_policy.py +159 -0
  154. scaler/scheduler/allocate_policy/mixins.py +55 -0
  155. scaler/scheduler/controllers/__init__.py +0 -0
  156. scaler/scheduler/controllers/balance_controller.py +65 -0
  157. scaler/scheduler/controllers/client_controller.py +131 -0
  158. scaler/scheduler/controllers/config_controller.py +31 -0
  159. scaler/scheduler/controllers/graph_controller.py +424 -0
  160. scaler/scheduler/controllers/information_controller.py +81 -0
  161. scaler/scheduler/controllers/mixins.py +201 -0
  162. scaler/scheduler/controllers/object_controller.py +147 -0
  163. scaler/scheduler/controllers/scaling_controller.py +86 -0
  164. scaler/scheduler/controllers/task_controller.py +373 -0
  165. scaler/scheduler/controllers/worker_controller.py +168 -0
  166. scaler/scheduler/object_usage/__init__.py +0 -0
  167. scaler/scheduler/object_usage/object_tracker.py +131 -0
  168. scaler/scheduler/scheduler.py +253 -0
  169. scaler/scheduler/task/__init__.py +0 -0
  170. scaler/scheduler/task/task_state_machine.py +92 -0
  171. scaler/scheduler/task/task_state_manager.py +61 -0
  172. scaler/ui/__init__.py +0 -0
  173. scaler/ui/constants.py +9 -0
  174. scaler/ui/live_display.py +118 -0
  175. scaler/ui/memory_window.py +146 -0
  176. scaler/ui/setting_page.py +47 -0
  177. scaler/ui/task_graph.py +370 -0
  178. scaler/ui/task_log.py +83 -0
  179. scaler/ui/utility.py +35 -0
  180. scaler/ui/webui.py +125 -0
  181. scaler/ui/worker_processors.py +85 -0
  182. scaler/utility/__init__.py +0 -0
  183. scaler/utility/debug.py +19 -0
  184. scaler/utility/event_list.py +63 -0
  185. scaler/utility/event_loop.py +58 -0
  186. scaler/utility/exceptions.py +42 -0
  187. scaler/utility/formatter.py +44 -0
  188. scaler/utility/graph/__init__.py +0 -0
  189. scaler/utility/graph/optimization.py +27 -0
  190. scaler/utility/graph/topological_sorter.py +11 -0
  191. scaler/utility/graph/topological_sorter_graphblas.py +174 -0
  192. scaler/utility/identifiers.py +105 -0
  193. scaler/utility/logging/__init__.py +0 -0
  194. scaler/utility/logging/decorators.py +25 -0
  195. scaler/utility/logging/scoped_logger.py +33 -0
  196. scaler/utility/logging/utility.py +183 -0
  197. scaler/utility/many_to_many_dict.py +123 -0
  198. scaler/utility/metadata/__init__.py +0 -0
  199. scaler/utility/metadata/profile_result.py +31 -0
  200. scaler/utility/metadata/task_flags.py +30 -0
  201. scaler/utility/mixins.py +13 -0
  202. scaler/utility/network_util.py +7 -0
  203. scaler/utility/one_to_many_dict.py +72 -0
  204. scaler/utility/queues/__init__.py +0 -0
  205. scaler/utility/queues/async_indexed_queue.py +37 -0
  206. scaler/utility/queues/async_priority_queue.py +70 -0
  207. scaler/utility/queues/async_sorted_priority_queue.py +45 -0
  208. scaler/utility/queues/indexed_queue.py +114 -0
  209. scaler/utility/serialization.py +9 -0
  210. scaler/version.txt +1 -0
  211. scaler/worker/__init__.py +0 -0
  212. scaler/worker/agent/__init__.py +0 -0
  213. scaler/worker/agent/heartbeat_manager.py +107 -0
  214. scaler/worker/agent/mixins.py +137 -0
  215. scaler/worker/agent/processor/__init__.py +0 -0
  216. scaler/worker/agent/processor/object_cache.py +107 -0
  217. scaler/worker/agent/processor/processor.py +279 -0
  218. scaler/worker/agent/processor/streaming_buffer.py +28 -0
  219. scaler/worker/agent/processor_holder.py +145 -0
  220. scaler/worker/agent/processor_manager.py +365 -0
  221. scaler/worker/agent/profiling_manager.py +109 -0
  222. scaler/worker/agent/task_manager.py +150 -0
  223. scaler/worker/agent/timeout_manager.py +19 -0
  224. scaler/worker/preload.py +84 -0
  225. scaler/worker/worker.py +264 -0
  226. scaler/worker_adapter/__init__.py +0 -0
  227. scaler/worker_adapter/native.py +154 -0
  228. scaler/worker_adapter/symphony/__init__.py +0 -0
  229. scaler/worker_adapter/symphony/callback.py +45 -0
  230. scaler/worker_adapter/symphony/heartbeat_manager.py +79 -0
  231. scaler/worker_adapter/symphony/message.py +24 -0
  232. scaler/worker_adapter/symphony/task_manager.py +288 -0
  233. scaler/worker_adapter/symphony/worker.py +205 -0
  234. scaler/worker_adapter/symphony/worker_adapter.py +142 -0
@@ -0,0 +1,85 @@
1
+ import dataclasses
2
+ from threading import Lock
3
+ from typing import Dict, List, Optional
4
+
5
+ from nicegui import ui
6
+ from nicegui.element import Element
7
+
8
+ from scaler.protocol.python.status import ProcessorStatus, WorkerStatus
9
+ from scaler.ui.utility import format_worker_name
10
+
11
+
12
+ @dataclasses.dataclass
13
+ class WorkerProcessors:
14
+ workers: Dict[str, "WorkerProcessorTable"] = dataclasses.field(default_factory=dict)
15
+ _lock: Lock = Lock()
16
+
17
+ @ui.refreshable
18
+ def draw_section(self):
19
+ with self._lock:
20
+ for processor_table in self.workers.values():
21
+ processor_table.draw_table()
22
+
23
+ def update_data(self, data: List[WorkerStatus]):
24
+ with self._lock:
25
+ for worker in data:
26
+ worker_name = worker.worker_id.decode()
27
+ processor_table = self.workers.get(worker_name)
28
+
29
+ if processor_table is None:
30
+ processor_table = WorkerProcessorTable(worker_name, worker.rss_free, worker.processor_statuses)
31
+ self.workers[worker_name] = processor_table
32
+ elif processor_table.processor_statuses != worker.processor_statuses:
33
+ processor_table.processor_statuses = worker.processor_statuses
34
+
35
+ def remove_worker(self, dead_worker: str):
36
+ with self._lock:
37
+ self.workers.pop(dead_worker, None)
38
+
39
+
40
+ @dataclasses.dataclass
41
+ class WorkerProcessorTable:
42
+ worker_name: str
43
+ rss_free: int
44
+ processor_statuses: List[ProcessorStatus]
45
+
46
+ handler: Optional[Element] = dataclasses.field(default=None)
47
+
48
+ def draw_table(self):
49
+ formatted_worker_name = format_worker_name(self.worker_name)
50
+ with ui.card().classes("w-full") as handler:
51
+ self.handler = handler
52
+
53
+ ui.markdown(f"Worker **{formatted_worker_name}**").classes("text-xl")
54
+
55
+ with ui.grid(columns=6).classes("w-full"):
56
+ self.draw_titles()
57
+ for processor in sorted(self.processor_statuses, key=lambda x: x.pid):
58
+ self.draw_row(processor, self.rss_free)
59
+
60
+ @staticmethod
61
+ def draw_titles():
62
+ ui.label("Processor PID")
63
+ ui.label("CPU %")
64
+ ui.label("RSS (in MB)")
65
+ ui.label("Initialized")
66
+ ui.label("Has Task")
67
+ ui.label("Suspended")
68
+
69
+ @staticmethod
70
+ def draw_row(processor_status: ProcessorStatus, rss_free: int):
71
+ cpu = processor_status.resource.cpu / 10
72
+ rss = int(processor_status.resource.rss / 1e6)
73
+ rss_free = int(rss_free / 1e6)
74
+
75
+ ui.label(str(processor_status.pid))
76
+ ui.knob(value=cpu, track_color="grey-2", show_value=True, min=0, max=100)
77
+ ui.knob(value=rss, track_color="grey-2", show_value=True, min=0, max=rss + rss_free)
78
+ ui.checkbox().bind_value_from(processor_status, "initialized")
79
+ ui.checkbox().bind_value_from(processor_status, "has_task")
80
+ ui.checkbox().bind_value_from(processor_status, "suspended")
81
+
82
+ def delete_row(self):
83
+ assert self.handler is not None
84
+ self.handler.clear()
85
+ self.handler.delete()
File without changes
@@ -0,0 +1,19 @@
1
+ import functools
2
+ import pdb
3
+ import sys
4
+ from typing import Callable
5
+
6
+
7
+ def pdb_wrapped(func: Callable):
8
+ @functools.wraps(func)
9
+ def pdb_wrapper(*args, **kwargs):
10
+ try:
11
+ exit_code = func(*args, **kwargs)
12
+ sys.exit(exit_code)
13
+
14
+ except Exception:
15
+ ex_type, value, tb = sys.exc_info()
16
+ pdb.post_mortem(tb)
17
+ raise
18
+
19
+ return pdb_wrapper
@@ -0,0 +1,63 @@
1
+ import collections
2
+ from typing import Callable
3
+
4
+
5
+ class EventList(collections.UserList):
6
+ """A list that emits events when it is modified."""
7
+
8
+ def __init__(self, initlist=None):
9
+ super().__init__(initlist=initlist)
10
+ self._callbacks = []
11
+
12
+ def add_update_callback(self, callback: Callable[["EventList"], None]):
13
+ self._callbacks.append(callback)
14
+
15
+ def __setitem__(self, i, item):
16
+ super().__setitem__(i, item)
17
+ self._list_updated()
18
+
19
+ def __delitem__(self, i):
20
+ super().__delitem__(i)
21
+ self._list_updated()
22
+
23
+ def __add__(self, other):
24
+ super().__add__(other)
25
+ self._list_updated()
26
+
27
+ def __iadd__(self, other):
28
+ super().__iadd__(other)
29
+ self._list_updated()
30
+ return self
31
+
32
+ def append(self, item):
33
+ super().append(item)
34
+ self._list_updated()
35
+
36
+ def insert(self, i, item):
37
+ super().insert(i, item)
38
+ self._list_updated()
39
+
40
+ def pop(self, i: int = -1):
41
+ v = super().pop(i)
42
+ self._list_updated()
43
+ return v
44
+
45
+ def remove(self, item):
46
+ super().remove(item)
47
+ self._list_updated()
48
+
49
+ def clear(self) -> None:
50
+ super().clear()
51
+ self._list_updated()
52
+
53
+ def sort(self, /, *args, **kwargs):
54
+ super().sort(*args, **kwargs)
55
+ self._list_updated()
56
+
57
+ def extend(self, other) -> None:
58
+ super().extend(other)
59
+ self._list_updated()
60
+
61
+ def _list_updated(self):
62
+ for callback in self._callbacks:
63
+ callback(self)
@@ -0,0 +1,58 @@
1
+ import asyncio
2
+ import enum
3
+ import logging
4
+ from typing import Awaitable, Callable
5
+
6
+
7
+ class EventLoopType(enum.Enum):
8
+ builtin = enum.auto()
9
+ uvloop = enum.auto()
10
+
11
+ @staticmethod
12
+ def allowed_types():
13
+ return {m.name for m in EventLoopType}
14
+
15
+
16
+ def register_event_loop(event_loop_type: str):
17
+ if event_loop_type not in EventLoopType.allowed_types():
18
+ raise TypeError(f"allowed event loop types are: {EventLoopType.allowed_types()}")
19
+
20
+ event_loop_type_enum = EventLoopType[event_loop_type]
21
+ if event_loop_type_enum == EventLoopType.uvloop:
22
+ try:
23
+ import uvloop # noqa
24
+ except ImportError:
25
+ raise ImportError("please use pip install uvloop if try to use uvloop as event loop")
26
+
27
+ uvloop.install()
28
+
29
+ assert event_loop_type in EventLoopType.allowed_types()
30
+
31
+ logging.info(f"use event loop: {event_loop_type}")
32
+
33
+
34
+ def create_async_loop_routine(routine: Callable[[], Awaitable], seconds: int):
35
+ """create async loop routine,
36
+
37
+ - if seconds is negative, means disable
38
+ - 0 means looping without any wait, as fast as possible
39
+ - positive number means execute routine every positive seconds, if passing 1 means run once every 1 seconds"""
40
+
41
+ async def loop():
42
+ if seconds < 0:
43
+ logging.info(f"{routine.__self__.__class__.__name__}: disabled") # type: ignore[attr-defined]
44
+ return
45
+
46
+ logging.info(f"{routine.__self__.__class__.__name__}: started") # type: ignore[attr-defined]
47
+ try:
48
+ while True:
49
+ await routine()
50
+ await asyncio.sleep(seconds)
51
+ except asyncio.CancelledError:
52
+ pass
53
+ except KeyboardInterrupt:
54
+ pass
55
+
56
+ logging.info(f"{routine.__self__.__class__.__name__}: exited") # type: ignore[attr-defined]
57
+
58
+ return loop()
@@ -0,0 +1,42 @@
1
+ class TaskNotFoundError(Exception):
2
+ pass
3
+
4
+
5
+ class WorkerDiedError(Exception):
6
+ pass
7
+
8
+
9
+ class NoWorkerError(Exception):
10
+ pass
11
+
12
+
13
+ class DisconnectedError(Exception):
14
+ pass
15
+
16
+
17
+ class ProcessorDiedError(Exception):
18
+ pass
19
+
20
+
21
+ class DeserializeObjectError(Exception):
22
+ pass
23
+
24
+
25
+ class MissingObjects(Exception):
26
+ pass
27
+
28
+
29
+ class ClientCancelledException(Exception):
30
+ pass
31
+
32
+
33
+ class ClientShutdownException(Exception):
34
+ pass
35
+
36
+
37
+ class ClientQuitException(Exception):
38
+ pass
39
+
40
+
41
+ class ObjectStorageException(Exception):
42
+ pass
@@ -0,0 +1,44 @@
1
+ STORAGE_SIZE_MODULUS = 1024.0
2
+ TIME_MODULUS = 1000
3
+
4
+
5
+ def format_bytes(number) -> str:
6
+ for unit in ["B", "K", "M", "G", "T"]:
7
+ if number >= STORAGE_SIZE_MODULUS:
8
+ number /= STORAGE_SIZE_MODULUS
9
+ continue
10
+
11
+ if unit in {"B", "K"}:
12
+ return f"{int(number)}{unit}"
13
+
14
+ return f"{number:.1f}{unit}"
15
+
16
+ raise ValueError("This should not happen")
17
+
18
+
19
+ def format_integer(number):
20
+ return f"{number:,}"
21
+
22
+
23
+ def format_percentage(number: int):
24
+ return f"{(number/1000):.1%}"
25
+
26
+
27
+ def format_microseconds(number: int):
28
+ for unit in ["us", "ms", "s"]:
29
+ if number >= TIME_MODULUS:
30
+ number = int(number / TIME_MODULUS)
31
+ continue
32
+
33
+ if unit == "us":
34
+ return f"{number/TIME_MODULUS:.1f}ms"
35
+
36
+ too_big_sign = "+" if unit == "s" and number > TIME_MODULUS else ""
37
+ return f"{int(number)}{too_big_sign}{unit}"
38
+
39
+
40
+ def format_seconds(number: int):
41
+ if number > 60:
42
+ return "60+s"
43
+
44
+ return f"{number}s"
File without changes
@@ -0,0 +1,27 @@
1
+ from collections import deque
2
+ from typing import Any, Callable, Dict, List, Tuple, Union
3
+
4
+
5
+ def cull_graph(
6
+ graph: Dict[str, Tuple[Union[Callable, Any], ...]], keys: List[str]
7
+ ) -> Dict[str, Tuple[Union[Callable, Any], ...]]:
8
+ queue = deque(keys)
9
+ visited = set()
10
+ for target_key in keys:
11
+ visited.add(target_key)
12
+
13
+ while queue:
14
+ key = queue.popleft()
15
+
16
+ task = graph[key]
17
+ if not (isinstance(task, tuple) and task and callable(task[0])):
18
+ continue
19
+
20
+ dependencies = set(task[1:])
21
+ for predecessor_key in dependencies:
22
+ if predecessor_key in visited:
23
+ continue
24
+ visited.add(predecessor_key)
25
+ queue.append(predecessor_key)
26
+
27
+ return {key: graph[key] for key in visited}
@@ -0,0 +1,11 @@
1
+ import logging
2
+
3
+ try:
4
+ from scaler.utility.graph.topological_sorter_graphblas import TopologicalSorter
5
+
6
+ logging.info("using GraphBLAS for calculate graph")
7
+ except ImportError as e:
8
+ assert isinstance(e, Exception)
9
+ from graphlib import TopologicalSorter # type: ignore[assignment, no-redef]
10
+
11
+ assert isinstance(TopologicalSorter, object)
@@ -0,0 +1,174 @@
1
+ import collections
2
+ import graphlib
3
+ import itertools
4
+ from typing import Generic, Hashable, Iterable, List, Mapping, Optional, Tuple, TypeVar
5
+
6
+ from bidict import bidict
7
+
8
+ try:
9
+ import graphblas as gb
10
+ import numpy as np # noqa
11
+ except ImportError:
12
+ raise ImportError("Please use 'pip install python-graphblas' to have graph blas support")
13
+
14
+ GraphKeyType = TypeVar("GraphKeyType", bound=Hashable)
15
+
16
+
17
+ class TopologicalSorter(Generic[GraphKeyType]):
18
+ """
19
+ Implements graphlib's TopologicalSorter, but the graph handling is backed by GraphBLAS
20
+ Reference: https://github.com/python/cpython/blob/4a3ea1fdd890e5e2ec26540dc3c958a52fba6556/Lib/graphlib.py
21
+ """
22
+
23
+ def __init__(self, graph: Optional[Mapping[GraphKeyType, Iterable[GraphKeyType]]] = None):
24
+ # the layout of the matrix is (in-vertex, out-vertex)
25
+ self._matrix = gb.Matrix(gb.dtypes.BOOL)
26
+ self._key_to_id: bidict[GraphKeyType, int] = bidict()
27
+
28
+ self._graph_matrix_mask: Optional[np.ndarray] = None
29
+ self._visited_vertices_mask: Optional[np.ndarray] = None
30
+ self._ready_nodes: Optional[List[GraphKeyType]] = None
31
+
32
+ self._n_done = 0
33
+ self._n_visited = 0
34
+
35
+ if graph is not None:
36
+ self.merge_graph(graph)
37
+
38
+ def add(self, node: GraphKeyType, *predecessors: GraphKeyType) -> None:
39
+ self.merge_graph({node: predecessors})
40
+
41
+ def merge_graph(self, graph: Mapping[GraphKeyType, Iterable[GraphKeyType]]) -> None:
42
+ if self._ready_nodes is not None:
43
+ raise ValueError("nodes cannot be added after a call to prepare()")
44
+
45
+ # cache old dim to compare later when resizing matrix
46
+ old_dim = len(self._key_to_id)
47
+
48
+ # maintain iterable copies for iterable predecessors
49
+ graph_iterable_copy = {}
50
+
51
+ # update key to id mappings
52
+ for node, predecessors in graph.items():
53
+ if node not in self._key_to_id:
54
+ self._key_to_id[node] = len(self._key_to_id)
55
+
56
+ # copy iterator if predecessors is an iterable
57
+ if isinstance(predecessors, collections.abc.Iterable):
58
+ predecessors, graph_iterable_copy[node] = itertools.tee(predecessors)
59
+
60
+ for pred in predecessors:
61
+ if pred not in self._key_to_id:
62
+ self._key_to_id[pred] = len(self._key_to_id)
63
+
64
+ # resize at once as it is faster
65
+ if old_dim != len(self._key_to_id):
66
+ self._matrix.resize(len(self._key_to_id), len(self._key_to_id))
67
+
68
+ # update matrix
69
+ for node, predecessors in graph.items():
70
+ if node in graph_iterable_copy:
71
+ predecessors = graph_iterable_copy[node]
72
+
73
+ for pred in predecessors:
74
+ self._matrix[self._key_to_id[node], self._key_to_id[pred]] = True
75
+
76
+ def prepare(self) -> None:
77
+ if self._ready_nodes is not None:
78
+ raise ValueError("cannot prepare() more than once")
79
+
80
+ self._graph_matrix_mask = np.ones(len(self._key_to_id), bool)
81
+ self._visited_vertices_mask = np.zeros(len(self._key_to_id), bool)
82
+
83
+ self._ready_nodes = self._get_zero_degree_keys()
84
+ for node in self._ready_nodes:
85
+ self._visited_vertices_mask[self._key_to_id[node]] = True
86
+ self._n_visited += len(self._ready_nodes)
87
+
88
+ if self._has_cycle():
89
+ raise graphlib.CycleError("cycle detected")
90
+
91
+ def get_ready(self) -> Tuple[GraphKeyType, ...]:
92
+ if self._ready_nodes is None:
93
+ raise ValueError("prepare() must be called first")
94
+
95
+ result = tuple(self._ready_nodes)
96
+ self._ready_nodes.clear()
97
+ return result
98
+
99
+ def is_active(self) -> bool:
100
+ if self._ready_nodes is None:
101
+ raise ValueError("prepare() must be called first")
102
+ return self._n_done < self._n_visited or bool(self._ready_nodes)
103
+
104
+ def __bool__(self) -> bool:
105
+ return self.is_active()
106
+
107
+ def done(self, *nodes: GraphKeyType) -> None:
108
+ if self._ready_nodes is None:
109
+ raise ValueError("prepare() must be called first")
110
+
111
+ for node in nodes:
112
+ if node not in self._key_to_id:
113
+ raise ValueError(f"node {node!r} was not added using add()")
114
+
115
+ _id = self._key_to_id[node]
116
+
117
+ if not self._visited_vertices_mask[_id]:
118
+ raise ValueError(f"node {node!r} is not ready")
119
+
120
+ if not self._graph_matrix_mask[_id]:
121
+ raise ValueError(f"node {node!r} is already done")
122
+
123
+ self._graph_matrix_mask[_id] = False
124
+ self._n_done += len(nodes)
125
+
126
+ new_ready_nodes = self._get_zero_degree_keys()
127
+ for node in new_ready_nodes:
128
+ self._visited_vertices_mask[self._key_to_id[node]] = True
129
+ self._ready_nodes.extend(new_ready_nodes)
130
+ self._n_visited += len(new_ready_nodes)
131
+
132
+ def static_order(self) -> Iterable[GraphKeyType]:
133
+ self.prepare()
134
+ while self.is_active():
135
+ node_group = self.get_ready()
136
+ yield from node_group
137
+ self.done(*node_group)
138
+
139
+ def _has_cycle(self) -> bool:
140
+ """
141
+ Detect cycle using trace(A^n) != 0.
142
+ https://arxiv.org/pdf/1610.01200.pdf
143
+
144
+ :return: True if cycle is found, otherwise False
145
+ """
146
+ matrix_n = gb.Vector.from_dense(np.ones(len(self._key_to_id), bool), missing_value=False).diag()
147
+ for _ in range(len(self._key_to_id)):
148
+ # use LOR_PAIR to compute matrix multiplication over boolean matrices
149
+ matrix_n << gb.semiring.lor_pair(matrix_n @ self._matrix)
150
+ # check diagonal for any truthy values
151
+ if matrix_n.diag().reduce(gb.monoid.lor):
152
+ return True
153
+ return False
154
+
155
+ def _get_zero_degree_keys(self) -> List[GraphKeyType]:
156
+ ids = self._get_mask_diff(self._visited_vertices_mask, self._get_zero_degree_mask(self._get_masked_matrix()))
157
+ return [self._key_to_id.inverse[_id] for _id in ids]
158
+
159
+ def _get_masked_matrix(self) -> gb.Matrix:
160
+ # convert vector mask to matrix diagonal and then perform matrix multiplication to mask matrix
161
+ # https://github.com/DrTimothyAldenDavis/GraphBLAS/issues/48#issuecomment-858596341
162
+ return gb.semiring.lor_pair(
163
+ self._matrix @ gb.Vector.from_dense(self._graph_matrix_mask, missing_value=False).diag()
164
+ )
165
+
166
+ @classmethod
167
+ def _get_zero_degree_mask(cls, masked_matrix: gb.Matrix) -> np.ndarray:
168
+ degrees = masked_matrix.reduce_rowwise(gb.monoid.lor)
169
+ indices, _ = degrees.to_coo(indices=True, values=False, sort=False)
170
+ return np.logical_not(np.in1d(np.arange(masked_matrix.nrows), indices)) # type: ignore[attr-defined]
171
+
172
+ @staticmethod
173
+ def _get_mask_diff(old_mask: np.ndarray, new_mask: np.ndarray) -> List[int]:
174
+ return np.argwhere(old_mask != new_mask).ravel().tolist()
@@ -0,0 +1,105 @@
1
+ import abc
2
+ import hashlib
3
+ import os
4
+ import uuid
5
+ from typing import Optional
6
+
7
+
8
+ class Identifier(bytes, metaclass=abc.ABCMeta):
9
+ @abc.abstractmethod
10
+ def __repr__(self) -> str:
11
+ raise NotImplementedError()
12
+
13
+
14
+ class ClientID(Identifier):
15
+ def __repr__(self) -> str:
16
+ return f"ClientID({self.decode()})"
17
+
18
+ @staticmethod
19
+ def generate_client_id(name: Optional[str] = None) -> "ClientID":
20
+ if name is None:
21
+ name = uuid.uuid4().bytes.hex()
22
+
23
+ return ClientID(f"{os.getpid()}|Client|{name}".encode())
24
+
25
+
26
+ class WorkerID(Identifier):
27
+ def __repr__(self) -> str:
28
+ return f"WorkerID({self.decode()})"
29
+
30
+ def is_valid(self) -> bool:
31
+ return self != _INVALID_WORKER_ID
32
+
33
+ @staticmethod
34
+ def invalid_worker_id() -> "WorkerID":
35
+ return _INVALID_WORKER_ID
36
+
37
+ @staticmethod
38
+ def generate_worker_id(name: str) -> "WorkerID":
39
+ return WorkerID(f"{os.getpid()}|Worker|{name}|{uuid.uuid4().bytes.hex()}".encode())
40
+
41
+
42
+ _INVALID_WORKER_ID = WorkerID(b"")
43
+
44
+
45
+ class ProcessorID(Identifier):
46
+ def __repr__(self) -> str:
47
+ return f"ProcessorID({self.hex()})"
48
+
49
+ @staticmethod
50
+ def generate_processor_id() -> "ProcessorID":
51
+ return ProcessorID(uuid.uuid4().bytes)
52
+
53
+
54
+ class TaskID(Identifier):
55
+ def __repr__(self) -> str:
56
+ return f"TaskID({self.hex()})"
57
+
58
+ @staticmethod
59
+ def generate_task_id() -> "TaskID":
60
+ return TaskID(uuid.uuid4().bytes)
61
+
62
+
63
+ class ObjectID(bytes):
64
+ SERIALIZER_TAG = hashlib.md5(b"serializer").digest()
65
+
66
+ """
67
+ Scaler 32-bytes object IDs.
68
+
69
+ Object ID are built from 2x16-bytes parts:
70
+
71
+ - the first 16-bytes uniquely identify the owner of the object (i.e. the Scaler client's hash);
72
+ - the second 16-bytes uniquely identify the object's content.
73
+ """
74
+
75
+ def __new__(cls, value: bytes):
76
+ if len(value) != 32:
77
+ raise ValueError("Scaler object ID must be 32 bytes.")
78
+
79
+ return super().__new__(cls, value)
80
+
81
+ @staticmethod
82
+ def generate_object_id(owner: ClientID) -> "ObjectID":
83
+ owner_hash = hashlib.md5(owner).digest()
84
+ unique_object_tag = uuid.uuid4().bytes
85
+ return ObjectID(owner_hash + unique_object_tag)
86
+
87
+ @staticmethod
88
+ def generate_serializer_object_id(owner: ClientID) -> "ObjectID":
89
+ owner_hash = hashlib.md5(owner).digest()
90
+ return ObjectID(owner_hash + ObjectID.SERIALIZER_TAG)
91
+
92
+ def owner_hash(self) -> bytes:
93
+ return self[:16]
94
+
95
+ def object_tag(self) -> bytes:
96
+ return self[16:]
97
+
98
+ def is_serializer(self) -> bool:
99
+ return self.object_tag() == ObjectID.SERIALIZER_TAG
100
+
101
+ def is_owner(self, owner: ClientID) -> bool:
102
+ return hashlib.md5(owner).digest() == self.owner_hash()
103
+
104
+ def __repr__(self) -> str:
105
+ return f"ObjectID(owner_hash={self.owner_hash().hex()}, object_tag={self.object_tag().hex()})"
File without changes
@@ -0,0 +1,25 @@
1
+ import functools
2
+ import inspect
3
+ import logging
4
+ import typing
5
+
6
+ from scaler.utility.logging.scoped_logger import ScopedLogger
7
+
8
+
9
+ def log_function(level_number: int = 2, logging_level: int = logging.INFO) -> typing.Callable:
10
+ def decorator(func: typing.Callable) -> typing.Callable:
11
+ @functools.wraps(func)
12
+ def wrapper(*args, **kwargs):
13
+ with ScopedLogger(
14
+ f"execute {func.__name__} at {get_caller_location(level_number)}", logging_level=logging_level
15
+ ):
16
+ return func(*args, **kwargs)
17
+
18
+ return wrapper
19
+
20
+ return decorator
21
+
22
+
23
+ def get_caller_location(stack_level: int):
24
+ caller = inspect.getframeinfo(inspect.stack()[stack_level][0])
25
+ return f"{caller.filename}:{caller.lineno}"