opengris-scaler 1.12.37__cp38-cp38-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.
Files changed (196) hide show
  1. opengris_scaler-1.12.37.dist-info/METADATA +730 -0
  2. opengris_scaler-1.12.37.dist-info/RECORD +196 -0
  3. opengris_scaler-1.12.37.dist-info/WHEEL +5 -0
  4. opengris_scaler-1.12.37.dist-info/entry_points.txt +10 -0
  5. opengris_scaler-1.12.37.dist-info/licenses/LICENSE +201 -0
  6. opengris_scaler-1.12.37.dist-info/licenses/LICENSE.spdx +7 -0
  7. opengris_scaler-1.12.37.dist-info/licenses/NOTICE +8 -0
  8. opengris_scaler.libs/libcapnp-1-e88d5415.0.1.so +0 -0
  9. opengris_scaler.libs/libgcc_s-2298274a.so.1 +0 -0
  10. opengris_scaler.libs/libkj-1-9bebd8ac.0.1.so +0 -0
  11. opengris_scaler.libs/libstdc++-08d5c7eb.so.6.0.33 +0 -0
  12. scaler/__init__.py +14 -0
  13. scaler/about.py +5 -0
  14. scaler/client/__init__.py +0 -0
  15. scaler/client/agent/__init__.py +0 -0
  16. scaler/client/agent/client_agent.py +218 -0
  17. scaler/client/agent/disconnect_manager.py +27 -0
  18. scaler/client/agent/future_manager.py +112 -0
  19. scaler/client/agent/heartbeat_manager.py +74 -0
  20. scaler/client/agent/mixins.py +89 -0
  21. scaler/client/agent/object_manager.py +98 -0
  22. scaler/client/agent/task_manager.py +64 -0
  23. scaler/client/client.py +672 -0
  24. scaler/client/future.py +252 -0
  25. scaler/client/object_buffer.py +129 -0
  26. scaler/client/object_reference.py +25 -0
  27. scaler/client/serializer/__init__.py +0 -0
  28. scaler/client/serializer/default.py +16 -0
  29. scaler/client/serializer/mixins.py +38 -0
  30. scaler/cluster/__init__.py +0 -0
  31. scaler/cluster/cluster.py +95 -0
  32. scaler/cluster/combo.py +157 -0
  33. scaler/cluster/object_storage_server.py +45 -0
  34. scaler/cluster/scheduler.py +86 -0
  35. scaler/config/__init__.py +0 -0
  36. scaler/config/common/__init__.py +0 -0
  37. scaler/config/common/logging.py +41 -0
  38. scaler/config/common/web.py +18 -0
  39. scaler/config/common/worker.py +65 -0
  40. scaler/config/common/worker_adapter.py +28 -0
  41. scaler/config/config_class.py +317 -0
  42. scaler/config/defaults.py +94 -0
  43. scaler/config/mixins.py +20 -0
  44. scaler/config/section/__init__.py +0 -0
  45. scaler/config/section/cluster.py +66 -0
  46. scaler/config/section/ecs_worker_adapter.py +78 -0
  47. scaler/config/section/native_worker_adapter.py +30 -0
  48. scaler/config/section/object_storage_server.py +13 -0
  49. scaler/config/section/scheduler.py +126 -0
  50. scaler/config/section/symphony_worker_adapter.py +35 -0
  51. scaler/config/section/top.py +16 -0
  52. scaler/config/section/webui.py +16 -0
  53. scaler/config/types/__init__.py +0 -0
  54. scaler/config/types/network_backend.py +12 -0
  55. scaler/config/types/object_storage_server.py +45 -0
  56. scaler/config/types/worker.py +67 -0
  57. scaler/config/types/zmq.py +83 -0
  58. scaler/entry_points/__init__.py +0 -0
  59. scaler/entry_points/cluster.py +10 -0
  60. scaler/entry_points/object_storage_server.py +26 -0
  61. scaler/entry_points/scheduler.py +51 -0
  62. scaler/entry_points/top.py +272 -0
  63. scaler/entry_points/webui.py +6 -0
  64. scaler/entry_points/worker_adapter_ecs.py +22 -0
  65. scaler/entry_points/worker_adapter_native.py +31 -0
  66. scaler/entry_points/worker_adapter_symphony.py +26 -0
  67. scaler/io/__init__.py +0 -0
  68. scaler/io/async_binder.py +89 -0
  69. scaler/io/async_connector.py +95 -0
  70. scaler/io/async_object_storage_connector.py +225 -0
  71. scaler/io/mixins.py +154 -0
  72. scaler/io/sync_connector.py +68 -0
  73. scaler/io/sync_object_storage_connector.py +249 -0
  74. scaler/io/sync_subscriber.py +83 -0
  75. scaler/io/utility.py +80 -0
  76. scaler/io/ymq/__init__.py +0 -0
  77. scaler/io/ymq/_ymq.pyi +95 -0
  78. scaler/io/ymq/_ymq.so +0 -0
  79. scaler/io/ymq/ymq.py +138 -0
  80. scaler/io/ymq_async_object_storage_connector.py +184 -0
  81. scaler/io/ymq_sync_object_storage_connector.py +184 -0
  82. scaler/object_storage/__init__.py +0 -0
  83. scaler/object_storage/object_storage_server.so +0 -0
  84. scaler/protocol/__init__.py +0 -0
  85. scaler/protocol/capnp/__init__.py +0 -0
  86. scaler/protocol/capnp/_python.py +6 -0
  87. scaler/protocol/capnp/common.capnp +68 -0
  88. scaler/protocol/capnp/message.capnp +218 -0
  89. scaler/protocol/capnp/object_storage.capnp +57 -0
  90. scaler/protocol/capnp/status.capnp +73 -0
  91. scaler/protocol/introduction.md +105 -0
  92. scaler/protocol/python/__init__.py +0 -0
  93. scaler/protocol/python/common.py +140 -0
  94. scaler/protocol/python/message.py +751 -0
  95. scaler/protocol/python/mixins.py +13 -0
  96. scaler/protocol/python/object_storage.py +118 -0
  97. scaler/protocol/python/status.py +279 -0
  98. scaler/protocol/worker.md +228 -0
  99. scaler/scheduler/__init__.py +0 -0
  100. scaler/scheduler/allocate_policy/__init__.py +0 -0
  101. scaler/scheduler/allocate_policy/allocate_policy.py +9 -0
  102. scaler/scheduler/allocate_policy/capability_allocate_policy.py +280 -0
  103. scaler/scheduler/allocate_policy/even_load_allocate_policy.py +159 -0
  104. scaler/scheduler/allocate_policy/mixins.py +55 -0
  105. scaler/scheduler/controllers/__init__.py +0 -0
  106. scaler/scheduler/controllers/balance_controller.py +65 -0
  107. scaler/scheduler/controllers/client_controller.py +131 -0
  108. scaler/scheduler/controllers/config_controller.py +31 -0
  109. scaler/scheduler/controllers/graph_controller.py +424 -0
  110. scaler/scheduler/controllers/information_controller.py +81 -0
  111. scaler/scheduler/controllers/mixins.py +194 -0
  112. scaler/scheduler/controllers/object_controller.py +147 -0
  113. scaler/scheduler/controllers/scaling_policies/__init__.py +0 -0
  114. scaler/scheduler/controllers/scaling_policies/fixed_elastic.py +145 -0
  115. scaler/scheduler/controllers/scaling_policies/mixins.py +10 -0
  116. scaler/scheduler/controllers/scaling_policies/null.py +14 -0
  117. scaler/scheduler/controllers/scaling_policies/types.py +9 -0
  118. scaler/scheduler/controllers/scaling_policies/utility.py +20 -0
  119. scaler/scheduler/controllers/scaling_policies/vanilla.py +95 -0
  120. scaler/scheduler/controllers/task_controller.py +376 -0
  121. scaler/scheduler/controllers/worker_controller.py +169 -0
  122. scaler/scheduler/object_usage/__init__.py +0 -0
  123. scaler/scheduler/object_usage/object_tracker.py +131 -0
  124. scaler/scheduler/scheduler.py +251 -0
  125. scaler/scheduler/task/__init__.py +0 -0
  126. scaler/scheduler/task/task_state_machine.py +92 -0
  127. scaler/scheduler/task/task_state_manager.py +61 -0
  128. scaler/ui/__init__.py +0 -0
  129. scaler/ui/common/__init__.py +0 -0
  130. scaler/ui/common/constants.py +9 -0
  131. scaler/ui/common/live_display.py +147 -0
  132. scaler/ui/common/memory_window.py +146 -0
  133. scaler/ui/common/setting_page.py +40 -0
  134. scaler/ui/common/task_graph.py +840 -0
  135. scaler/ui/common/task_log.py +111 -0
  136. scaler/ui/common/utility.py +66 -0
  137. scaler/ui/common/webui.py +80 -0
  138. scaler/ui/common/worker_processors.py +104 -0
  139. scaler/ui/v1.py +76 -0
  140. scaler/ui/v2.py +102 -0
  141. scaler/ui/webui.py +21 -0
  142. scaler/utility/__init__.py +0 -0
  143. scaler/utility/debug.py +19 -0
  144. scaler/utility/event_list.py +63 -0
  145. scaler/utility/event_loop.py +58 -0
  146. scaler/utility/exceptions.py +42 -0
  147. scaler/utility/formatter.py +44 -0
  148. scaler/utility/graph/__init__.py +0 -0
  149. scaler/utility/graph/optimization.py +27 -0
  150. scaler/utility/graph/topological_sorter.py +11 -0
  151. scaler/utility/graph/topological_sorter_graphblas.py +174 -0
  152. scaler/utility/identifiers.py +107 -0
  153. scaler/utility/logging/__init__.py +0 -0
  154. scaler/utility/logging/decorators.py +25 -0
  155. scaler/utility/logging/scoped_logger.py +33 -0
  156. scaler/utility/logging/utility.py +183 -0
  157. scaler/utility/many_to_many_dict.py +123 -0
  158. scaler/utility/metadata/__init__.py +0 -0
  159. scaler/utility/metadata/profile_result.py +31 -0
  160. scaler/utility/metadata/task_flags.py +30 -0
  161. scaler/utility/mixins.py +13 -0
  162. scaler/utility/network_util.py +7 -0
  163. scaler/utility/one_to_many_dict.py +72 -0
  164. scaler/utility/queues/__init__.py +0 -0
  165. scaler/utility/queues/async_indexed_queue.py +37 -0
  166. scaler/utility/queues/async_priority_queue.py +70 -0
  167. scaler/utility/queues/async_sorted_priority_queue.py +45 -0
  168. scaler/utility/queues/indexed_queue.py +114 -0
  169. scaler/utility/serialization.py +9 -0
  170. scaler/version.txt +1 -0
  171. scaler/worker/__init__.py +0 -0
  172. scaler/worker/agent/__init__.py +0 -0
  173. scaler/worker/agent/heartbeat_manager.py +110 -0
  174. scaler/worker/agent/mixins.py +137 -0
  175. scaler/worker/agent/processor/__init__.py +0 -0
  176. scaler/worker/agent/processor/object_cache.py +107 -0
  177. scaler/worker/agent/processor/processor.py +285 -0
  178. scaler/worker/agent/processor/streaming_buffer.py +28 -0
  179. scaler/worker/agent/processor_holder.py +147 -0
  180. scaler/worker/agent/processor_manager.py +369 -0
  181. scaler/worker/agent/profiling_manager.py +109 -0
  182. scaler/worker/agent/task_manager.py +150 -0
  183. scaler/worker/agent/timeout_manager.py +19 -0
  184. scaler/worker/preload.py +84 -0
  185. scaler/worker/worker.py +265 -0
  186. scaler/worker_adapter/__init__.py +0 -0
  187. scaler/worker_adapter/common.py +26 -0
  188. scaler/worker_adapter/ecs.py +241 -0
  189. scaler/worker_adapter/native.py +138 -0
  190. scaler/worker_adapter/symphony/__init__.py +0 -0
  191. scaler/worker_adapter/symphony/callback.py +45 -0
  192. scaler/worker_adapter/symphony/heartbeat_manager.py +82 -0
  193. scaler/worker_adapter/symphony/message.py +24 -0
  194. scaler/worker_adapter/symphony/task_manager.py +289 -0
  195. scaler/worker_adapter/symphony/worker.py +204 -0
  196. scaler/worker_adapter/symphony/worker_adapter.py +123 -0
@@ -0,0 +1,183 @@
1
+ import dataclasses
2
+ import enum
3
+ import logging
4
+ import logging.config
5
+ import logging.handlers
6
+ import os
7
+ import typing
8
+
9
+ from scaler.config.defaults import DEFAULT_LOGGING_PATHS
10
+
11
+
12
+ class LogType(enum.Enum):
13
+ Screen = enum.auto()
14
+ File = enum.auto()
15
+
16
+
17
+ @dataclasses.dataclass
18
+ class LogPath:
19
+ log_type: LogType
20
+ path: str
21
+
22
+
23
+ class LoggingLevel(enum.Enum):
24
+ CRITICAL = logging.CRITICAL
25
+ ERROR = logging.ERROR
26
+ WARNING = logging.WARNING
27
+ INFO = logging.INFO
28
+ DEBUG = logging.DEBUG
29
+ NOTSET = logging.NOTSET
30
+
31
+
32
+ def setup_logger(
33
+ log_paths: typing.Tuple[str, ...] = DEFAULT_LOGGING_PATHS,
34
+ logging_config_file: typing.Optional[str] = None,
35
+ logging_level: str = LoggingLevel.INFO.name,
36
+ ):
37
+ if not log_paths and not logging_config_file:
38
+ return
39
+
40
+ if isinstance(log_paths, str):
41
+ log_paths = (log_paths,)
42
+
43
+ if logging_config_file is not None:
44
+ print(f"use logging config file: {logging_config_file}")
45
+ logging.config.fileConfig(logging_config_file, disable_existing_loggers=True)
46
+ return
47
+
48
+ resolved_log_paths = [LogPath(log_type=__detect_log_types(file_name), path=file_name) for file_name in log_paths]
49
+ __logging_config(log_paths=resolved_log_paths, logging_level=logging_level)
50
+ logging.info(f"logging to {log_paths}")
51
+
52
+
53
+ def __detect_log_types(file_name: str) -> LogType:
54
+ if file_name in {"-", "/dev/stdout"}:
55
+ return LogType.Screen
56
+
57
+ return LogType.File
58
+
59
+
60
+ def __format(name) -> str:
61
+ if not name:
62
+ return ""
63
+
64
+ return "%({name})s".format(name=name)
65
+
66
+
67
+ def __generate_log_config() -> typing.Dict:
68
+ return {
69
+ "version": 1,
70
+ "disable_existing_loggers": False, # this fixes the problem
71
+ "formatters": {
72
+ "standard": {
73
+ "format": "[{levelname}]{asctime}: {message}".format(
74
+ levelname=__format("levelname"), asctime=__format("asctime"), message=__format("message")
75
+ ),
76
+ "datefmt": "%Y-%m-%d %H:%M:%S%z",
77
+ },
78
+ "verbose": {
79
+ "format": "[{levelname}]{asctime}:{module}:{funcName}:{lineno}: {message}".format(
80
+ levelname=__format("levelname"),
81
+ asctime=__format("asctime"),
82
+ module=__format("module"),
83
+ funcName=__format("funcName"),
84
+ lineno=__format("lineno"),
85
+ message=__format("message"),
86
+ ),
87
+ "datefmt": "%Y-%m-%d %H:%M:%S%z",
88
+ },
89
+ },
90
+ "handlers": {},
91
+ "loggers": {"": {"handlers": [], "level": "DEBUG", "propagate": True}},
92
+ }
93
+
94
+
95
+ def __logging_config(log_paths: typing.List[LogPath], logging_level: str = LoggingLevel.INFO.name):
96
+ logging.addLevelName(logging.INFO, "INFO")
97
+ logging.addLevelName(logging.WARNING, "WARN")
98
+ logging.addLevelName(logging.ERROR, "EROR")
99
+ logging.addLevelName(logging.DEBUG, "DEBG")
100
+ logging.addLevelName(logging.CRITICAL, "CTIC")
101
+
102
+ config = __generate_log_config()
103
+ handlers = config["handlers"]
104
+ root_loggers = config["loggers"][""]["handlers"]
105
+
106
+ for log_path in log_paths:
107
+ if log_path.log_type == LogType.Screen:
108
+ handlers["console"] = __create_stdout_handler(logging_level)
109
+ root_loggers.append("console")
110
+ continue
111
+
112
+ elif log_path.log_type == LogType.File:
113
+ handlers[log_path.path] = __create_time_rotating_file_handler(logging_level, log_path.path)
114
+ root_loggers.append(log_path.path)
115
+ continue
116
+
117
+ raise TypeError(f"Unsupported LogPath: {log_path}")
118
+
119
+ logging.config.dictConfig(config)
120
+
121
+
122
+ def __create_stdout_handler(logging_level: str):
123
+ return {
124
+ "class": "logging.StreamHandler",
125
+ "level": logging_level,
126
+ "formatter": "standard",
127
+ "stream": "ext://sys.stdout",
128
+ }
129
+
130
+
131
+ def __create_time_rotating_file_handler(logging_level: str, file_path: str):
132
+ return {
133
+ "class": "logging.handlers.TimedRotatingFileHandler",
134
+ "level": logging_level,
135
+ "formatter": "verbose",
136
+ "filename": os.path.expandvars(os.path.expanduser(file_path)),
137
+ "when": "midnight",
138
+ }
139
+
140
+
141
+ def __create_size_rotating_file_handler(log_path) -> typing.Dict:
142
+ return {
143
+ "class": "logging.handlers.RotatingFileHandler",
144
+ "level": "INFO",
145
+ "formatter": "verbose",
146
+ "filename": os.path.expandvars(os.path.expanduser(log_path)),
147
+ "maxBytes": 10485760,
148
+ "backupCount": 20,
149
+ "encoding": "utf8",
150
+ }
151
+
152
+
153
+ def __parse_logging_level(value):
154
+ return LoggingLevel(value).value
155
+
156
+
157
+ def get_logger_info(logger: logging.Logger) -> typing.Tuple[str, str, typing.Tuple[str, ...]]:
158
+ """
159
+ Retrieves the format string, level string, and all active log paths from a logger's handlers.
160
+ """
161
+ log_level_str = logging.getLevelName(logger.getEffectiveLevel())
162
+ log_format_str = ""
163
+ log_paths: typing.List[str] = []
164
+
165
+ if logger.hasHandlers():
166
+ first_handler = logger.handlers[0]
167
+ if first_handler.formatter:
168
+ log_format_str = getattr(first_handler.formatter, "_fmt", "")
169
+
170
+ for handler in logger.handlers:
171
+ if isinstance(handler, logging.handlers.BaseRotatingHandler):
172
+ log_paths.append(handler.baseFilename)
173
+ elif isinstance(handler, logging.StreamHandler) and hasattr(handler.stream, "name"):
174
+ if "stdout" in handler.stream.name:
175
+ log_paths.append("/dev/stdout")
176
+ elif "stderr" in handler.stream.name:
177
+ log_paths.append("/dev/stderr")
178
+
179
+ # If no specific path was found, default to stdout
180
+ if not log_paths:
181
+ log_paths.append("/dev/stdout")
182
+
183
+ return log_format_str, log_level_str, tuple(log_paths)
@@ -0,0 +1,123 @@
1
+ from typing import Dict, Generic, Iterable, Set, Tuple, TypeVar
2
+
3
+ KeyT = TypeVar("KeyT")
4
+ ValueT = TypeVar("ValueT")
5
+
6
+ LeftKeyT = TypeVar("LeftKeyT")
7
+ RightKeyT = TypeVar("RightKeyT")
8
+
9
+
10
+ class ManyToManyDict(Generic[LeftKeyT, RightKeyT]):
11
+ def __init__(self):
12
+ self._left_key_to_right_key_set: _KeyValueDictSet[LeftKeyT, RightKeyT] = _KeyValueDictSet()
13
+ self._right_key_to_left_key_set: _KeyValueDictSet[RightKeyT, LeftKeyT] = _KeyValueDictSet()
14
+
15
+ def left_keys(self):
16
+ return self._left_key_to_right_key_set.keys()
17
+
18
+ def right_keys(self):
19
+ return self._right_key_to_left_key_set.keys()
20
+
21
+ def add(self, left_key: LeftKeyT, right_key: RightKeyT):
22
+ self._left_key_to_right_key_set.add(left_key, right_key)
23
+ self._right_key_to_left_key_set.add(right_key, left_key)
24
+
25
+ def remove(self, left_key: LeftKeyT, right_key: RightKeyT):
26
+ self._left_key_to_right_key_set.remove_value(left_key, right_key)
27
+ self._right_key_to_left_key_set.remove_value(right_key, left_key)
28
+
29
+ def has_left_key(self, left_key: LeftKeyT) -> bool:
30
+ return left_key in self._left_key_to_right_key_set
31
+
32
+ def has_right_key(self, right_key: RightKeyT) -> bool:
33
+ return right_key in self._right_key_to_left_key_set
34
+
35
+ def has_key_pair(self, left_key: LeftKeyT, right_key: RightKeyT) -> bool:
36
+ return (
37
+ self.has_left_key(left_key)
38
+ and self.has_right_key(right_key)
39
+ and right_key in self._left_key_to_right_key_set.get_values(left_key)
40
+ and left_key in self._right_key_to_left_key_set.get_values(right_key)
41
+ )
42
+
43
+ def left_key_items(self) -> Iterable[Tuple[LeftKeyT, Set[RightKeyT]]]:
44
+ return self._left_key_to_right_key_set.items()
45
+
46
+ def right_key_items(self) -> Iterable[Tuple[RightKeyT, Set[LeftKeyT]]]:
47
+ return self._right_key_to_left_key_set.items()
48
+
49
+ def get_left_items(self, right_key: RightKeyT) -> Set[LeftKeyT]:
50
+ if right_key not in self._right_key_to_left_key_set:
51
+ raise ValueError(f"cannot find {right_key=} in ManyToManyDict")
52
+
53
+ return self._right_key_to_left_key_set.get_values(right_key)
54
+
55
+ def get_right_items(self, left_key: LeftKeyT) -> Set[RightKeyT]:
56
+ if left_key not in self._left_key_to_right_key_set:
57
+ raise ValueError(f"cannot find {left_key=} in ManyToManyDict")
58
+
59
+ return self._left_key_to_right_key_set.get_values(left_key)
60
+
61
+ def remove_left_key(self, left_key: LeftKeyT) -> Set[RightKeyT]:
62
+ if left_key not in self._left_key_to_right_key_set:
63
+ raise KeyError(f"cannot find {left_key=} in ManyToManyDict")
64
+
65
+ right_keys = self._left_key_to_right_key_set.remove_key(left_key)
66
+ for right_key in right_keys:
67
+ self._right_key_to_left_key_set.remove_value(right_key, left_key)
68
+
69
+ return right_keys
70
+
71
+ def remove_right_key(self, right_key: RightKeyT) -> Set[LeftKeyT]:
72
+ if right_key not in self._right_key_to_left_key_set:
73
+ raise ValueError(f"cannot find {right_key=} in ManyToManyDict")
74
+
75
+ left_keys = self._right_key_to_left_key_set.remove_key(right_key)
76
+ for left_key in left_keys:
77
+ self._left_key_to_right_key_set.remove_value(left_key, right_key)
78
+
79
+ return left_keys
80
+
81
+
82
+ class _KeyValueDictSet(Generic[KeyT, ValueT]):
83
+ def __init__(self):
84
+ self._key_to_value_set: Dict[KeyT, Set[ValueT]] = dict()
85
+
86
+ def __contains__(self, key) -> bool:
87
+ return key in self._key_to_value_set
88
+
89
+ def keys(self):
90
+ return self._key_to_value_set.keys()
91
+
92
+ def values(self):
93
+ return self._key_to_value_set.values()
94
+
95
+ def items(self):
96
+ return self._key_to_value_set.items()
97
+
98
+ def add(self, key: KeyT, value: ValueT):
99
+ if key not in self._key_to_value_set:
100
+ self._key_to_value_set[key] = set()
101
+
102
+ self._key_to_value_set[key].add(value)
103
+
104
+ def get_values(self, key: KeyT) -> Set[ValueT]:
105
+ if key not in self._key_to_value_set:
106
+ raise ValueError(f"cannot find {key=} in KeyValueSet")
107
+
108
+ return self._key_to_value_set[key]
109
+
110
+ def remove_key(self, key: KeyT) -> Set[ValueT]:
111
+ if key not in self._key_to_value_set:
112
+ raise KeyError(f"cannot find {key=} in KeyValueSet")
113
+
114
+ values = self._key_to_value_set.pop(key)
115
+ return values
116
+
117
+ def remove_value(self, key: KeyT, value: ValueT):
118
+ if key not in self._key_to_value_set:
119
+ raise KeyError(f"cannot find {key=} in KeyValueSet")
120
+
121
+ self._key_to_value_set[key].remove(value)
122
+ if not self._key_to_value_set[key]:
123
+ self._key_to_value_set.pop(key)
File without changes
@@ -0,0 +1,31 @@
1
+ import dataclasses
2
+ import struct
3
+ from typing import Optional
4
+
5
+ from scaler.protocol.python.message import TaskResult
6
+
7
+
8
+ @dataclasses.dataclass
9
+ class ProfileResult:
10
+ duration_s: float = dataclasses.field(default=0.0)
11
+ memory_peak: int = dataclasses.field(default=0)
12
+ cpu_time_s: float = dataclasses.field(default=0.0)
13
+
14
+ FORMAT = "!fQf" # duration, memory peak, CPU time
15
+
16
+ def serialize(self) -> bytes:
17
+ return struct.pack(self.FORMAT, self.duration_s, self.memory_peak, self.cpu_time_s)
18
+
19
+ @staticmethod
20
+ def deserialize(data: bytes) -> "ProfileResult":
21
+ return ProfileResult(*struct.unpack(ProfileResult.FORMAT, data))
22
+
23
+
24
+ def retrieve_profiling_result_from_task_result(task_result: TaskResult) -> Optional[ProfileResult]:
25
+ if task_result.metadata == b"":
26
+ return None
27
+
28
+ try:
29
+ return ProfileResult.deserialize(task_result.metadata)
30
+ except struct.error:
31
+ raise ValueError(f"unexpected metadata value (expected {ProfileResult.__name__}).")
@@ -0,0 +1,30 @@
1
+ import dataclasses
2
+ import struct
3
+
4
+ from scaler.protocol.python.message import Task
5
+
6
+
7
+ @dataclasses.dataclass
8
+ class TaskFlags:
9
+ profiling: bool = dataclasses.field(default=True)
10
+ priority: int = dataclasses.field(default=0)
11
+ stream_output: bool = dataclasses.field(default=False)
12
+
13
+ FORMAT = "!?i?"
14
+
15
+ def serialize(self) -> bytes:
16
+ return struct.pack(TaskFlags.FORMAT, self.profiling, self.priority, self.stream_output)
17
+
18
+ @staticmethod
19
+ def deserialize(data: bytes) -> "TaskFlags":
20
+ return TaskFlags(*struct.unpack(TaskFlags.FORMAT, data))
21
+
22
+
23
+ def retrieve_task_flags_from_task(task: Task) -> TaskFlags:
24
+ if task.metadata == b"":
25
+ return TaskFlags()
26
+
27
+ try:
28
+ return TaskFlags.deserialize(task.metadata)
29
+ except struct.error:
30
+ raise ValueError(f"unexpected metadata value (expected {TaskFlags.__name__}).")
@@ -0,0 +1,13 @@
1
+ import abc
2
+
3
+
4
+ class Looper(metaclass=abc.ABCMeta):
5
+ @abc.abstractmethod
6
+ async def routine(self):
7
+ raise NotImplementedError()
8
+
9
+
10
+ class Reporter(metaclass=abc.ABCMeta):
11
+ @abc.abstractmethod
12
+ def get_status(self):
13
+ raise NotImplementedError()
@@ -0,0 +1,7 @@
1
+ import socket
2
+
3
+
4
+ def get_available_tcp_port(hostname: str = "127.0.0.1") -> int:
5
+ with socket.socket(socket.AddressFamily.AF_INET, socket.SocketKind.SOCK_STREAM) as sock:
6
+ sock.bind((hostname, 0))
7
+ return sock.getsockname()[1]
@@ -0,0 +1,72 @@
1
+ from typing import Dict, Generic, Set, TypeVar
2
+
3
+ KeyT = TypeVar("KeyT")
4
+ ValueT = TypeVar("ValueT")
5
+
6
+
7
+ class OneToManyDict(Generic[KeyT, ValueT]):
8
+ def __init__(self):
9
+ self._key_to_value_set: Dict[KeyT, Set[ValueT]] = dict()
10
+ self._value_to_key: Dict[ValueT, KeyT] = dict()
11
+
12
+ def __contains__(self, key) -> bool:
13
+ return self.has_key(key)
14
+
15
+ def keys(self):
16
+ return self._key_to_value_set.keys()
17
+
18
+ def values(self):
19
+ return self._key_to_value_set.values()
20
+
21
+ def items(self):
22
+ return self._key_to_value_set.items()
23
+
24
+ def add(self, key: KeyT, value: ValueT):
25
+ if value in self._value_to_key and self._value_to_key[value] != key:
26
+ raise ValueError("value has to be unique in OneToManyDict")
27
+
28
+ self._value_to_key[value] = key
29
+
30
+ if key not in self._key_to_value_set:
31
+ self._key_to_value_set[key] = set()
32
+
33
+ self._key_to_value_set[key].add(value)
34
+
35
+ def has_key(self, key: KeyT) -> bool:
36
+ return key in self._key_to_value_set
37
+
38
+ def has_value(self, value: ValueT) -> bool:
39
+ return value in self._value_to_key
40
+
41
+ def get_key(self, value: ValueT) -> KeyT:
42
+ if value not in self._value_to_key:
43
+ raise ValueError(f"cannot find {value=} in OneToManyDict")
44
+
45
+ return self._value_to_key[value]
46
+
47
+ def get_values(self, key: KeyT) -> Set[ValueT]:
48
+ if key not in self._key_to_value_set:
49
+ raise ValueError(f"cannot find {key=} in OneToManyDict")
50
+
51
+ return self._key_to_value_set[key]
52
+
53
+ def remove_key(self, key: KeyT) -> Set[ValueT]:
54
+ if key not in self._key_to_value_set:
55
+ raise KeyError(f"cannot find {key=} in OneToManyDict")
56
+
57
+ values = self._key_to_value_set.pop(key)
58
+ for value in values:
59
+ self._value_to_key.pop(value)
60
+
61
+ return values
62
+
63
+ def remove_value(self, value: ValueT) -> KeyT:
64
+ if value not in self._value_to_key:
65
+ raise ValueError(f"cannot find {value=} in OneToManyDict")
66
+
67
+ key = self._value_to_key.pop(value)
68
+ self._key_to_value_set[key].remove(value)
69
+ if not self._key_to_value_set[key]:
70
+ self._key_to_value_set.pop(key)
71
+
72
+ return key
File without changes
@@ -0,0 +1,37 @@
1
+ from asyncio import Queue, QueueEmpty
2
+ from typing import Generic, TypeVar
3
+
4
+ from scaler.utility.queues.indexed_queue import IndexedQueue
5
+
6
+ ItemType = TypeVar("ItemType")
7
+
8
+
9
+ class AsyncIndexedQueue(Queue, Generic[ItemType]):
10
+ """This should have same set of features as asyncio.Queue, with additional methods like remove
11
+ - it behaves like regular async queue, except:
12
+ - all the items pushed to queue should be hashable
13
+ - those items should be unique in queue
14
+ - IndexedQueue.put(), IndexedQueue.get(), IndexedQueue.remove() should all take O(1) time complexity
15
+ """
16
+
17
+ def __contains__(self, item: ItemType):
18
+ return item in self._queue
19
+
20
+ def __len__(self):
21
+ return self._queue.__len__()
22
+
23
+ def _init(self, maxsize):
24
+ self._queue = IndexedQueue()
25
+
26
+ def _put(self, item: ItemType):
27
+ self._queue.put(item)
28
+
29
+ def _get(self):
30
+ try:
31
+ return self._queue.get()
32
+ except IndexError:
33
+ raise QueueEmpty(f"{self.__class__.__name__} queue empty")
34
+
35
+ def remove(self, item: ItemType):
36
+ """remove the item in the queue in O(1) time complexity"""
37
+ self._queue.remove(item)
@@ -0,0 +1,70 @@
1
+ import heapq
2
+ import sys
3
+ from asyncio import Queue
4
+ from typing import Any, Dict, List, Tuple, Union
5
+
6
+ PriorityType = Union[int, Tuple["PriorityType", ...]]
7
+
8
+
9
+ class AsyncPriorityQueue(Queue):
10
+ """A subclass of Queue; retrieves entries in priority order (lowest first).
11
+
12
+ Entries are typically list of the form: [priority, data].
13
+ """
14
+
15
+ def __len__(self):
16
+ return len(self._queue)
17
+
18
+ def _init(self, maxsize):
19
+ self._queue: List[List] = []
20
+ self._locator: Dict[bytes, List] = {}
21
+
22
+ def _put(self, item):
23
+ if not isinstance(item, list):
24
+ item = list(item)
25
+
26
+ heapq.heappush(self._queue, item)
27
+ self._locator[item[1]] = item
28
+
29
+ def _get(self):
30
+ priority, data = heapq.heappop(self._queue)
31
+ self._locator.pop(data)
32
+ return priority, data
33
+
34
+ def remove(self, data):
35
+ # this operation is O(n), first change priority to -1 and pop from top of the heap, mark it as invalid
36
+ # entry in the heap is not good idea as those invalid, entry will never get removed, so we used heapq internal
37
+ # function _siftdown to maintain min heap invariant
38
+ item = self._locator.pop(data)
39
+ i = self._queue.index(item) # O(n)
40
+ item[0] = self.__to_lowest_priority(item[0])
41
+ heapq._siftdown(self._queue, 0, i) # type: ignore[attr-defined]
42
+ assert heapq.heappop(self._queue) == item
43
+
44
+ def decrease_priority(self, data):
45
+ # this operation should be O(n), mark it as invalid entry in the heap is not good idea as those invalid
46
+ # entry will never get removed, so we used heapq internal function _siftdown to maintain min heap invariant
47
+ item = self._locator[data]
48
+ i = self._queue.index(item) # O(n)
49
+ item[0] = self.__to_lower_priority(item[0])
50
+ heapq._siftdown(self._queue, 0, i) # type: ignore[attr-defined]
51
+
52
+ def max_priority_item(self) -> Tuple[PriorityType, Any]:
53
+ """output the Tuple of top priority number and top priority item"""
54
+ item = heapq.heappop(self._queue)
55
+ heapq.heappush(self._queue, item)
56
+ return item[0], item[1]
57
+
58
+ @classmethod
59
+ def __to_lowest_priority(cls, original_priority: PriorityType) -> PriorityType:
60
+ if isinstance(original_priority, tuple):
61
+ return tuple(cls.__to_lowest_priority(value) for value in original_priority)
62
+ else:
63
+ return -sys.maxsize - 1
64
+
65
+ @classmethod
66
+ def __to_lower_priority(cls, original_priority: PriorityType) -> PriorityType:
67
+ if isinstance(original_priority, tuple):
68
+ return tuple(cls.__to_lower_priority(value) for value in original_priority)
69
+ else:
70
+ return original_priority - 1
@@ -0,0 +1,45 @@
1
+ from asyncio import Queue
2
+ from typing import Any, Dict
3
+
4
+ from scaler.utility.queues.async_priority_queue import AsyncPriorityQueue
5
+
6
+
7
+ class AsyncSortedPriorityQueue(Queue):
8
+ """A subclass of Queue; retrieves entries in priority order (lowest first), and then by adding order.
9
+
10
+ Entries are typically list of the form: [priority number, data].
11
+ """
12
+
13
+ def __len__(self):
14
+ return len(self._queue)
15
+
16
+ def _init(self, maxsize: int):
17
+ self._queue = AsyncPriorityQueue()
18
+
19
+ # Keeps an item count to assign monotonic integer to queued items, so to also keep the priority queue sorted by
20
+ # adding order.
21
+ # See https://docs.python.org/3/library/heapq.html#priority-queue-implementation-notes.
22
+ self._item_counter: int = 0
23
+ self._data_to_item_id: Dict[Any, int] = dict()
24
+
25
+ def _put(self, item) -> None:
26
+ priority, data = item
27
+
28
+ if data in self._data_to_item_id:
29
+ raise ValueError(f"item `{data}` already in the queue")
30
+
31
+ item_id = self._item_counter
32
+ self._item_counter += 1
33
+
34
+ self._queue._put([priority, (item_id, data)])
35
+ self._data_to_item_id[data] = item_id
36
+
37
+ def _get(self):
38
+ priority, (_, data) = self._queue._get()
39
+ self._data_to_item_id.pop(data)
40
+
41
+ return [priority, data]
42
+
43
+ def remove(self, data: Any) -> None:
44
+ item_id = self._data_to_item_id.pop(data)
45
+ self._queue.remove((item_id, data))