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.
- opengris_scaler-1.12.37.dist-info/METADATA +730 -0
- opengris_scaler-1.12.37.dist-info/RECORD +196 -0
- opengris_scaler-1.12.37.dist-info/WHEEL +5 -0
- opengris_scaler-1.12.37.dist-info/entry_points.txt +10 -0
- opengris_scaler-1.12.37.dist-info/licenses/LICENSE +201 -0
- opengris_scaler-1.12.37.dist-info/licenses/LICENSE.spdx +7 -0
- opengris_scaler-1.12.37.dist-info/licenses/NOTICE +8 -0
- opengris_scaler.libs/libcapnp-1-e88d5415.0.1.so +0 -0
- opengris_scaler.libs/libgcc_s-2298274a.so.1 +0 -0
- opengris_scaler.libs/libkj-1-9bebd8ac.0.1.so +0 -0
- opengris_scaler.libs/libstdc++-08d5c7eb.so.6.0.33 +0 -0
- scaler/__init__.py +14 -0
- scaler/about.py +5 -0
- scaler/client/__init__.py +0 -0
- scaler/client/agent/__init__.py +0 -0
- scaler/client/agent/client_agent.py +218 -0
- scaler/client/agent/disconnect_manager.py +27 -0
- scaler/client/agent/future_manager.py +112 -0
- scaler/client/agent/heartbeat_manager.py +74 -0
- scaler/client/agent/mixins.py +89 -0
- scaler/client/agent/object_manager.py +98 -0
- scaler/client/agent/task_manager.py +64 -0
- scaler/client/client.py +672 -0
- scaler/client/future.py +252 -0
- scaler/client/object_buffer.py +129 -0
- scaler/client/object_reference.py +25 -0
- scaler/client/serializer/__init__.py +0 -0
- scaler/client/serializer/default.py +16 -0
- scaler/client/serializer/mixins.py +38 -0
- scaler/cluster/__init__.py +0 -0
- scaler/cluster/cluster.py +95 -0
- scaler/cluster/combo.py +157 -0
- scaler/cluster/object_storage_server.py +45 -0
- scaler/cluster/scheduler.py +86 -0
- scaler/config/__init__.py +0 -0
- scaler/config/common/__init__.py +0 -0
- scaler/config/common/logging.py +41 -0
- scaler/config/common/web.py +18 -0
- scaler/config/common/worker.py +65 -0
- scaler/config/common/worker_adapter.py +28 -0
- scaler/config/config_class.py +317 -0
- scaler/config/defaults.py +94 -0
- scaler/config/mixins.py +20 -0
- scaler/config/section/__init__.py +0 -0
- scaler/config/section/cluster.py +66 -0
- scaler/config/section/ecs_worker_adapter.py +78 -0
- scaler/config/section/native_worker_adapter.py +30 -0
- scaler/config/section/object_storage_server.py +13 -0
- scaler/config/section/scheduler.py +126 -0
- scaler/config/section/symphony_worker_adapter.py +35 -0
- scaler/config/section/top.py +16 -0
- scaler/config/section/webui.py +16 -0
- scaler/config/types/__init__.py +0 -0
- scaler/config/types/network_backend.py +12 -0
- scaler/config/types/object_storage_server.py +45 -0
- scaler/config/types/worker.py +67 -0
- scaler/config/types/zmq.py +83 -0
- scaler/entry_points/__init__.py +0 -0
- scaler/entry_points/cluster.py +10 -0
- scaler/entry_points/object_storage_server.py +26 -0
- scaler/entry_points/scheduler.py +51 -0
- scaler/entry_points/top.py +272 -0
- scaler/entry_points/webui.py +6 -0
- scaler/entry_points/worker_adapter_ecs.py +22 -0
- scaler/entry_points/worker_adapter_native.py +31 -0
- scaler/entry_points/worker_adapter_symphony.py +26 -0
- scaler/io/__init__.py +0 -0
- scaler/io/async_binder.py +89 -0
- scaler/io/async_connector.py +95 -0
- scaler/io/async_object_storage_connector.py +225 -0
- scaler/io/mixins.py +154 -0
- scaler/io/sync_connector.py +68 -0
- scaler/io/sync_object_storage_connector.py +249 -0
- scaler/io/sync_subscriber.py +83 -0
- scaler/io/utility.py +80 -0
- scaler/io/ymq/__init__.py +0 -0
- scaler/io/ymq/_ymq.pyi +95 -0
- scaler/io/ymq/_ymq.so +0 -0
- scaler/io/ymq/ymq.py +138 -0
- scaler/io/ymq_async_object_storage_connector.py +184 -0
- scaler/io/ymq_sync_object_storage_connector.py +184 -0
- scaler/object_storage/__init__.py +0 -0
- scaler/object_storage/object_storage_server.so +0 -0
- scaler/protocol/__init__.py +0 -0
- scaler/protocol/capnp/__init__.py +0 -0
- scaler/protocol/capnp/_python.py +6 -0
- scaler/protocol/capnp/common.capnp +68 -0
- scaler/protocol/capnp/message.capnp +218 -0
- scaler/protocol/capnp/object_storage.capnp +57 -0
- scaler/protocol/capnp/status.capnp +73 -0
- scaler/protocol/introduction.md +105 -0
- scaler/protocol/python/__init__.py +0 -0
- scaler/protocol/python/common.py +140 -0
- scaler/protocol/python/message.py +751 -0
- scaler/protocol/python/mixins.py +13 -0
- scaler/protocol/python/object_storage.py +118 -0
- scaler/protocol/python/status.py +279 -0
- scaler/protocol/worker.md +228 -0
- scaler/scheduler/__init__.py +0 -0
- scaler/scheduler/allocate_policy/__init__.py +0 -0
- scaler/scheduler/allocate_policy/allocate_policy.py +9 -0
- scaler/scheduler/allocate_policy/capability_allocate_policy.py +280 -0
- scaler/scheduler/allocate_policy/even_load_allocate_policy.py +159 -0
- scaler/scheduler/allocate_policy/mixins.py +55 -0
- scaler/scheduler/controllers/__init__.py +0 -0
- scaler/scheduler/controllers/balance_controller.py +65 -0
- scaler/scheduler/controllers/client_controller.py +131 -0
- scaler/scheduler/controllers/config_controller.py +31 -0
- scaler/scheduler/controllers/graph_controller.py +424 -0
- scaler/scheduler/controllers/information_controller.py +81 -0
- scaler/scheduler/controllers/mixins.py +194 -0
- scaler/scheduler/controllers/object_controller.py +147 -0
- scaler/scheduler/controllers/scaling_policies/__init__.py +0 -0
- scaler/scheduler/controllers/scaling_policies/fixed_elastic.py +145 -0
- scaler/scheduler/controllers/scaling_policies/mixins.py +10 -0
- scaler/scheduler/controllers/scaling_policies/null.py +14 -0
- scaler/scheduler/controllers/scaling_policies/types.py +9 -0
- scaler/scheduler/controllers/scaling_policies/utility.py +20 -0
- scaler/scheduler/controllers/scaling_policies/vanilla.py +95 -0
- scaler/scheduler/controllers/task_controller.py +376 -0
- scaler/scheduler/controllers/worker_controller.py +169 -0
- scaler/scheduler/object_usage/__init__.py +0 -0
- scaler/scheduler/object_usage/object_tracker.py +131 -0
- scaler/scheduler/scheduler.py +251 -0
- scaler/scheduler/task/__init__.py +0 -0
- scaler/scheduler/task/task_state_machine.py +92 -0
- scaler/scheduler/task/task_state_manager.py +61 -0
- scaler/ui/__init__.py +0 -0
- scaler/ui/common/__init__.py +0 -0
- scaler/ui/common/constants.py +9 -0
- scaler/ui/common/live_display.py +147 -0
- scaler/ui/common/memory_window.py +146 -0
- scaler/ui/common/setting_page.py +40 -0
- scaler/ui/common/task_graph.py +840 -0
- scaler/ui/common/task_log.py +111 -0
- scaler/ui/common/utility.py +66 -0
- scaler/ui/common/webui.py +80 -0
- scaler/ui/common/worker_processors.py +104 -0
- scaler/ui/v1.py +76 -0
- scaler/ui/v2.py +102 -0
- scaler/ui/webui.py +21 -0
- scaler/utility/__init__.py +0 -0
- scaler/utility/debug.py +19 -0
- scaler/utility/event_list.py +63 -0
- scaler/utility/event_loop.py +58 -0
- scaler/utility/exceptions.py +42 -0
- scaler/utility/formatter.py +44 -0
- scaler/utility/graph/__init__.py +0 -0
- scaler/utility/graph/optimization.py +27 -0
- scaler/utility/graph/topological_sorter.py +11 -0
- scaler/utility/graph/topological_sorter_graphblas.py +174 -0
- scaler/utility/identifiers.py +107 -0
- scaler/utility/logging/__init__.py +0 -0
- scaler/utility/logging/decorators.py +25 -0
- scaler/utility/logging/scoped_logger.py +33 -0
- scaler/utility/logging/utility.py +183 -0
- scaler/utility/many_to_many_dict.py +123 -0
- scaler/utility/metadata/__init__.py +0 -0
- scaler/utility/metadata/profile_result.py +31 -0
- scaler/utility/metadata/task_flags.py +30 -0
- scaler/utility/mixins.py +13 -0
- scaler/utility/network_util.py +7 -0
- scaler/utility/one_to_many_dict.py +72 -0
- scaler/utility/queues/__init__.py +0 -0
- scaler/utility/queues/async_indexed_queue.py +37 -0
- scaler/utility/queues/async_priority_queue.py +70 -0
- scaler/utility/queues/async_sorted_priority_queue.py +45 -0
- scaler/utility/queues/indexed_queue.py +114 -0
- scaler/utility/serialization.py +9 -0
- scaler/version.txt +1 -0
- scaler/worker/__init__.py +0 -0
- scaler/worker/agent/__init__.py +0 -0
- scaler/worker/agent/heartbeat_manager.py +110 -0
- scaler/worker/agent/mixins.py +137 -0
- scaler/worker/agent/processor/__init__.py +0 -0
- scaler/worker/agent/processor/object_cache.py +107 -0
- scaler/worker/agent/processor/processor.py +285 -0
- scaler/worker/agent/processor/streaming_buffer.py +28 -0
- scaler/worker/agent/processor_holder.py +147 -0
- scaler/worker/agent/processor_manager.py +369 -0
- scaler/worker/agent/profiling_manager.py +109 -0
- scaler/worker/agent/task_manager.py +150 -0
- scaler/worker/agent/timeout_manager.py +19 -0
- scaler/worker/preload.py +84 -0
- scaler/worker/worker.py +265 -0
- scaler/worker_adapter/__init__.py +0 -0
- scaler/worker_adapter/common.py +26 -0
- scaler/worker_adapter/ecs.py +241 -0
- scaler/worker_adapter/native.py +138 -0
- scaler/worker_adapter/symphony/__init__.py +0 -0
- scaler/worker_adapter/symphony/callback.py +45 -0
- scaler/worker_adapter/symphony/heartbeat_manager.py +82 -0
- scaler/worker_adapter/symphony/message.py +24 -0
- scaler/worker_adapter/symphony/task_manager.py +289 -0
- scaler/worker_adapter/symphony/worker.py +204 -0
- 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__}).")
|
scaler/utility/mixins.py
ADDED
|
@@ -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,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))
|