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,317 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
import typing
|
|
3
|
+
from typing import Any, Dict, Type, TypeVar
|
|
4
|
+
|
|
5
|
+
from configargparse import ArgParser, ArgumentDefaultsHelpFormatter, TomlConfigParser
|
|
6
|
+
|
|
7
|
+
from scaler.config.mixins import ConfigType
|
|
8
|
+
|
|
9
|
+
T = TypeVar("T", bound="ConfigClass")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ConfigClass:
|
|
13
|
+
"""
|
|
14
|
+
An abstract interface for dataclasses where the fields define command line options,
|
|
15
|
+
config file options, and environment variables.
|
|
16
|
+
|
|
17
|
+
Subclasses of `ConfigClass` must be dataclasses.
|
|
18
|
+
Subclasses of `ConfigClass` are called "config classes".
|
|
19
|
+
|
|
20
|
+
## Config Files
|
|
21
|
+
|
|
22
|
+
All options with a long name can be parsed from a TOML config file specified with `--config` or `-c`.
|
|
23
|
+
The section name is determined by the subclass' implementation of `.section_name()`.
|
|
24
|
+
|
|
25
|
+
## Environment Variables
|
|
26
|
+
|
|
27
|
+
Any parameter can be configured to read from an environment variable by adding `env="NAME"` to the field metadata.
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
# can be set as --my-field on the command line or in a config file, or using the environment variable `NAME`
|
|
31
|
+
my_field: int = dataclasses.field(metadata=dict(env="NAME"))
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Precedence
|
|
35
|
+
|
|
36
|
+
When a parameter is supplied in multiple ways, the following precedence is observed:
|
|
37
|
+
command line > environment variables > config file > defaults
|
|
38
|
+
|
|
39
|
+
## Customization
|
|
40
|
+
|
|
41
|
+
Any key values included in the field's metadata will be passed through to `.add_argument()`
|
|
42
|
+
and will always take precedence over derived values, except for `default`.
|
|
43
|
+
|
|
44
|
+
The value for `default` is taken from the field's default. Providing `default` in the field
|
|
45
|
+
metadata will result in a `TypeError`.
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
# passes through options
|
|
49
|
+
custom_field: int = dataclasses.field(
|
|
50
|
+
metadata=dict(
|
|
51
|
+
choices=["apples", "oranges"],
|
|
52
|
+
help="choose a fruit",
|
|
53
|
+
)
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# TypeError!
|
|
57
|
+
bad: int = dataclasses.field(metadata=dict(default=0))
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Naming Fields
|
|
61
|
+
|
|
62
|
+
The name of the dataclass fields (replacing underscores with hyphens) is used
|
|
63
|
+
as the long option name for command line and config file parameters.
|
|
64
|
+
A short name for the argument can be provided in the field metadata using the `short` key.
|
|
65
|
+
The value is the short option name and must include the hyphen, e.g. `-n`.
|
|
66
|
+
|
|
67
|
+
There is one restriction: `--config` and `-c` are reserved for the config file.
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
# this will have long name --field-one, and no short name
|
|
71
|
+
field_one: int = 5
|
|
72
|
+
|
|
73
|
+
# sets a short name
|
|
74
|
+
field_two: int = dataclasses.field(default=5, metadata=dict(short="-f2"))
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Default Values
|
|
78
|
+
|
|
79
|
+
The default value of the field is also used as the default for the argument parser.
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
lang: str = "en"
|
|
83
|
+
name: str = dataclasses.field(default="Arthur")
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Positional Parameters
|
|
87
|
+
|
|
88
|
+
You can set `positional=True` in the metadata dict to make an argument positional.
|
|
89
|
+
In this case long and short names are ignored, use `name` to override the name of the option.
|
|
90
|
+
The position is dependent on field ordering.
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
# both of these are positional, and field one must be specified before field two
|
|
94
|
+
field_one: int = dataclasses.field(metadata=dict(positional=True))
|
|
95
|
+
field_two: int = dataclasses.field(metadata=dict(positional=True))
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Composition
|
|
99
|
+
|
|
100
|
+
Config classes can be composed. If a config class has fields that are config classes,
|
|
101
|
+
then the options of the child config class are inherited as if that child config class'
|
|
102
|
+
fields were added to the parent, for the purpose of parsing arguments. When the dataclass
|
|
103
|
+
is created, the structure is kept.
|
|
104
|
+
|
|
105
|
+
Care needs to be taken so that field names do not conflict with each other.
|
|
106
|
+
The name of fields in nested config classes are in the same namespace as those
|
|
107
|
+
in the parent and other nested config classes.
|
|
108
|
+
|
|
109
|
+
## Parameter Types
|
|
110
|
+
|
|
111
|
+
The type of a field is used as the type in argument parsing, meaning that it must be able
|
|
112
|
+
to be directly constructed from a string.
|
|
113
|
+
|
|
114
|
+
Special handling is implemented for several common types:
|
|
115
|
+
- `bool`: parses from "true" and "false", and all upper/lower case variants
|
|
116
|
+
- subclasses of `ConfigType`: uses the `.from_string()` method
|
|
117
|
+
- `Optional[T]`, `T | None`: parsed as `T` and sets `required=False`
|
|
118
|
+
- `List[T]`, `list[T]`: parsed as `T`, and sets `nargs="*"`
|
|
119
|
+
|
|
120
|
+
For generic types, the `T` is parsed recursively following the rules here.
|
|
121
|
+
For example, `Optional[T]` where `T` is a subclass of `ConfigType`
|
|
122
|
+
will still use `T.from_string()`.
|
|
123
|
+
|
|
124
|
+
as usual, all of these can be overriden by setting the option in the metadata.
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
# this provides a custom `type` to parse the input as hexadecimal
|
|
128
|
+
# `type` must be a callable that accepts a string
|
|
129
|
+
# refer to the argparse docs for more
|
|
130
|
+
hex: int = dataclasses.field(metadata=dict(type=lambda s: int(s, 16)))
|
|
131
|
+
|
|
132
|
+
class MyConfigType(ConfigType):
|
|
133
|
+
...
|
|
134
|
+
|
|
135
|
+
# this will work as expected
|
|
136
|
+
my_field: MyConfigType
|
|
137
|
+
|
|
138
|
+
# this requires special handling
|
|
139
|
+
tuples: Tuple[int, ...] = dataclasses.field(metadata=dict(type=int, nargs="*"))
|
|
140
|
+
|
|
141
|
+
# works automatically, defaults to `nargs="*"`
|
|
142
|
+
integers: List[int]
|
|
143
|
+
|
|
144
|
+
# ... but we can override that
|
|
145
|
+
integers2: List[int] = dataclasses.field(metadata=dict(nargs="+"))
|
|
146
|
+
|
|
147
|
+
# this will automatically have `required=False` set
|
|
148
|
+
maybe: Optional[str]
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
@classmethod
|
|
152
|
+
def configure_parser(cls: type, parser: ArgParser):
|
|
153
|
+
fields = dataclasses.fields(cls)
|
|
154
|
+
|
|
155
|
+
for field in fields:
|
|
156
|
+
if is_config_class(field.type):
|
|
157
|
+
field.type.configure_parser(parser) # type: ignore[union-attr]
|
|
158
|
+
continue
|
|
159
|
+
|
|
160
|
+
kwargs = dict(field.metadata)
|
|
161
|
+
|
|
162
|
+
# usually command line options use hyphens instead of underscores
|
|
163
|
+
|
|
164
|
+
if kwargs.pop("positional", False):
|
|
165
|
+
args = [kwargs.pop("name", field.name)]
|
|
166
|
+
else:
|
|
167
|
+
long_name = kwargs.pop("long", f"--{field.name.replace('_', '-')}")
|
|
168
|
+
if "short" in kwargs:
|
|
169
|
+
args = [long_name, kwargs.pop("short")]
|
|
170
|
+
else:
|
|
171
|
+
args = [long_name]
|
|
172
|
+
|
|
173
|
+
# this sets the key given back when args are parsed
|
|
174
|
+
kwargs["dest"] = field.name
|
|
175
|
+
|
|
176
|
+
if "default" in kwargs:
|
|
177
|
+
raise TypeError("'default' cannot be provided in field metadata")
|
|
178
|
+
|
|
179
|
+
if field.default != dataclasses.MISSING:
|
|
180
|
+
kwargs["default"] = field.default
|
|
181
|
+
|
|
182
|
+
if field.default_factory != dataclasses.MISSING:
|
|
183
|
+
kwargs["default"] = field.default_factory()
|
|
184
|
+
|
|
185
|
+
# when store true or store false is set, setting the type raises a type error
|
|
186
|
+
if kwargs.get("action") not in ("store_true", "store_false"):
|
|
187
|
+
|
|
188
|
+
# sometimes the user will set the type manually
|
|
189
|
+
# this is required for types such as `Option[T]`, where they cannt be directly constructed from a string
|
|
190
|
+
if "type" not in kwargs:
|
|
191
|
+
opts = get_type_args(field.type)
|
|
192
|
+
|
|
193
|
+
# set all of the options, except where already set
|
|
194
|
+
for key, value in opts.items():
|
|
195
|
+
if key not in kwargs:
|
|
196
|
+
kwargs[key] = value
|
|
197
|
+
|
|
198
|
+
parser.add_argument(*args, **kwargs)
|
|
199
|
+
|
|
200
|
+
@classmethod
|
|
201
|
+
def parse(cls: Type[T], program_name: str, section: str) -> T:
|
|
202
|
+
parser = ArgParser(
|
|
203
|
+
program_name,
|
|
204
|
+
formatter_class=ArgumentDefaultsHelpFormatter,
|
|
205
|
+
config_file_parser_class=TomlConfigParser(sections=[section]),
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
parser.add_argument("--config", "-c", is_config_file=True, help="Path to the TOML configuration file.")
|
|
209
|
+
cls.configure_parser(parser)
|
|
210
|
+
|
|
211
|
+
kwargs = vars(parser.parse_args())
|
|
212
|
+
|
|
213
|
+
# remove this from the args
|
|
214
|
+
kwargs.pop("config")
|
|
215
|
+
|
|
216
|
+
# we need to manually handle any ConfigClass fields
|
|
217
|
+
for field in dataclasses.fields(cls): # type: ignore[arg-type]
|
|
218
|
+
if is_config_class(field.type):
|
|
219
|
+
|
|
220
|
+
# steal arguments for the config class
|
|
221
|
+
inner_kwargs = {}
|
|
222
|
+
for f in dataclasses.fields(field.type): # type: ignore[arg-type]
|
|
223
|
+
if f.name in kwargs:
|
|
224
|
+
inner_kwargs[f.name] = kwargs.pop(f.name)
|
|
225
|
+
|
|
226
|
+
# instantiate and update the args
|
|
227
|
+
kwargs[field.name] = field.type(**inner_kwargs) # type: ignore[operator]
|
|
228
|
+
|
|
229
|
+
return cls(**kwargs)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def parse_bool(s: str) -> bool:
|
|
233
|
+
"""parse a bool from a conventional string representation"""
|
|
234
|
+
|
|
235
|
+
lower = s.lower()
|
|
236
|
+
if lower == "true":
|
|
237
|
+
return True
|
|
238
|
+
if lower == "false":
|
|
239
|
+
return False
|
|
240
|
+
|
|
241
|
+
raise TypeError(f"[{s}] is not a valid bool")
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def is_optional(ty: Any) -> bool:
|
|
245
|
+
"""determines if `ty` is typing.Optional"""
|
|
246
|
+
return typing.get_origin(ty) is typing.Union and typing.get_args(ty)[1] is type(None)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def get_optional_type(ty: Any) -> type:
|
|
250
|
+
"""get the `T` from a typing.Optional[T]"""
|
|
251
|
+
return typing.get_args(ty)[0]
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def is_list(ty: Any) -> bool:
|
|
255
|
+
"""determines if `ty` is typing.List or list"""
|
|
256
|
+
return typing.get_origin(ty) is list or ty is list
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def get_list_type(ty: Any) -> type:
|
|
260
|
+
"""get the generic type of a typing.List[T] or list[T]"""
|
|
261
|
+
return typing.get_args(ty)[0]
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def is_config_type(ty: Any) -> bool:
|
|
265
|
+
"""determines if ty is a subclass of ConfigType"""
|
|
266
|
+
try:
|
|
267
|
+
return issubclass(ty, ConfigType)
|
|
268
|
+
except TypeError:
|
|
269
|
+
return False
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def is_config_class(ty: Any) -> bool:
|
|
273
|
+
"""determines if ty is a subclass of ConfigClass"""
|
|
274
|
+
try:
|
|
275
|
+
return issubclass(ty, ConfigClass)
|
|
276
|
+
except TypeError:
|
|
277
|
+
return False
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def get_type_args(ty: Any) -> Dict[str, Any]:
|
|
281
|
+
"""
|
|
282
|
+
The type of a field implies several options for its argument parsing,
|
|
283
|
+
such as `type`, `nargs`, and `required`
|
|
284
|
+
|
|
285
|
+
For example a parameter of type Option[T] is parsed as `T`,
|
|
286
|
+
has no implication on `nargs`, and is not required
|
|
287
|
+
|
|
288
|
+
Similarly a parameter of List[T] is also parsed as `T`,
|
|
289
|
+
might have `nargs="*"`, and has no implication on `required`
|
|
290
|
+
|
|
291
|
+
This function determines these settings based upon a given type.
|
|
292
|
+
"""
|
|
293
|
+
|
|
294
|
+
# bools have special parsing so that they behave as users expect
|
|
295
|
+
if ty is bool:
|
|
296
|
+
return {"type": parse_bool}
|
|
297
|
+
|
|
298
|
+
# for subclasses of ConfigType, we use the .from_string() method
|
|
299
|
+
if is_config_type(ty):
|
|
300
|
+
return {"type": ty.from_string}
|
|
301
|
+
|
|
302
|
+
# recursing handles the case where e.g. the inner type is a bool
|
|
303
|
+
# or a subclass of ConfigType, both of which need special handling
|
|
304
|
+
#
|
|
305
|
+
# parameters with this type are optional so we set `required=False`
|
|
306
|
+
if is_optional(ty):
|
|
307
|
+
opts = get_type_args(get_optional_type(ty))
|
|
308
|
+
return {"type": opts["type"], "required": False}
|
|
309
|
+
|
|
310
|
+
# `nargs="*"` is a reasonable default for lists that be overriden by the user
|
|
311
|
+
# if other behaviour is desired
|
|
312
|
+
if is_list(ty):
|
|
313
|
+
opts = get_type_args(get_list_type(ty))
|
|
314
|
+
return {"type": opts["type"], "nargs": "*"}
|
|
315
|
+
|
|
316
|
+
# the default, just use the type as-is
|
|
317
|
+
return {"type": ty}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from scaler.config.types.network_backend import NetworkBackend
|
|
4
|
+
|
|
5
|
+
# ==============
|
|
6
|
+
# SYSTEM OPTIONS
|
|
7
|
+
|
|
8
|
+
# object clean up time interval
|
|
9
|
+
CLEANUP_INTERVAL_SECONDS = 1
|
|
10
|
+
|
|
11
|
+
# status report interval, used by poke or scaled monitor
|
|
12
|
+
STATUS_REPORT_INTERVAL_SECONDS = 1
|
|
13
|
+
|
|
14
|
+
# number of seconds for profiling
|
|
15
|
+
PROFILING_INTERVAL_SECONDS = 1
|
|
16
|
+
|
|
17
|
+
# cap'n proto only allow Data/Text/Blob size to be as big as 500MB
|
|
18
|
+
CAPNP_DATA_SIZE_LIMIT = 2**29 - 1
|
|
19
|
+
|
|
20
|
+
# message size limitation, max can be 2**64
|
|
21
|
+
CAPNP_MESSAGE_SIZE_LIMIT = 2**64 - 1
|
|
22
|
+
|
|
23
|
+
# ==========================
|
|
24
|
+
# SCHEDULER SPECIFIC OPTIONS
|
|
25
|
+
|
|
26
|
+
# number of threads for zmq socket to handle
|
|
27
|
+
DEFAULT_IO_THREADS = 1
|
|
28
|
+
|
|
29
|
+
# if all workers are full and busy working, this option determine how many additional tasks scheduler can receive and
|
|
30
|
+
# queued, if additional number of tasks received exceeded this number, scheduler will reject tasks
|
|
31
|
+
DEFAULT_MAX_NUMBER_OF_TASKS_WAITING = -1
|
|
32
|
+
|
|
33
|
+
# if didn't receive heartbeat for following seconds, then scheduler will treat worker as dead and reschedule unfinished
|
|
34
|
+
# tasks for this worker
|
|
35
|
+
DEFAULT_WORKER_TIMEOUT_SECONDS = 60
|
|
36
|
+
|
|
37
|
+
# if didn't receive heartbeat for following seconds, then scheduler will treat client as dead and cancel remaining
|
|
38
|
+
# tasks for this client
|
|
39
|
+
DEFAULT_CLIENT_TIMEOUT_SECONDS = 60
|
|
40
|
+
|
|
41
|
+
# number of seconds for load balance, if value is -1 means disable load balance
|
|
42
|
+
DEFAULT_LOAD_BALANCE_SECONDS = 1
|
|
43
|
+
|
|
44
|
+
# when load balance advice happened repeatedly and always be the same, we issue load balance request when exact repeated
|
|
45
|
+
# times happened
|
|
46
|
+
DEFAULT_LOAD_BALANCE_TRIGGER_TIMES = 2
|
|
47
|
+
|
|
48
|
+
# number of tasks can be queued to each worker on scheduler side
|
|
49
|
+
DEFAULT_PER_WORKER_QUEUE_SIZE = 1000
|
|
50
|
+
|
|
51
|
+
# =======================
|
|
52
|
+
# WORKER SPECIFIC OPTIONS
|
|
53
|
+
|
|
54
|
+
# number of workers, echo worker use 1 process
|
|
55
|
+
DEFAULT_NUMBER_OF_WORKER = os.cpu_count() - 1
|
|
56
|
+
|
|
57
|
+
# number of seconds that worker agent send heartbeat to scheduler
|
|
58
|
+
DEFAULT_HEARTBEAT_INTERVAL_SECONDS = 2
|
|
59
|
+
|
|
60
|
+
# number of seconds the object cache kept in worker's memory
|
|
61
|
+
DEFAULT_OBJECT_RETENTION_SECONDS = 60
|
|
62
|
+
|
|
63
|
+
# number of seconds worker doing garbage collection
|
|
64
|
+
DEFAULT_GARBAGE_COLLECT_INTERVAL_SECONDS = 30
|
|
65
|
+
|
|
66
|
+
# number of bytes threshold for worker process that trigger deep garbage collection
|
|
67
|
+
DEFAULT_TRIM_MEMORY_THRESHOLD_BYTES = 1024 * 1024 * 1024
|
|
68
|
+
|
|
69
|
+
# default task timeout seconds, 0 means never timeout
|
|
70
|
+
DEFAULT_TASK_TIMEOUT_SECONDS = 0
|
|
71
|
+
|
|
72
|
+
# number of seconds that worker agent wait for processor to finish before killing it
|
|
73
|
+
DEFAULT_PROCESSOR_KILL_DELAY_SECONDS = 3
|
|
74
|
+
|
|
75
|
+
# number of seconds without scheduler contact before worker shuts down
|
|
76
|
+
DEFAULT_WORKER_DEATH_TIMEOUT = 5 * 60
|
|
77
|
+
|
|
78
|
+
# if true, suspended worker's processors will be actively suspended with a SIGTSTP signal, otherwise a synchronization
|
|
79
|
+
# event will be used.
|
|
80
|
+
DEFAULT_HARD_PROCESSOR_SUSPEND = False
|
|
81
|
+
|
|
82
|
+
# =======================
|
|
83
|
+
# LOGGING SPECIFIC OPTIONS
|
|
84
|
+
|
|
85
|
+
# default logging level
|
|
86
|
+
DEFAULT_LOGGING_LEVEL = "INFO"
|
|
87
|
+
|
|
88
|
+
# default logging paths
|
|
89
|
+
DEFAULT_LOGGING_PATHS = ("/dev/stdout",)
|
|
90
|
+
|
|
91
|
+
# =======================
|
|
92
|
+
# SCALER NETWORK BACKEND SPECIFIC OPTIONS
|
|
93
|
+
|
|
94
|
+
SCALER_NETWORK_BACKEND = NetworkBackend.tcp_zmq
|
scaler/config/mixins.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
if sys.version_info >= (3, 11):
|
|
5
|
+
from typing import Self
|
|
6
|
+
else:
|
|
7
|
+
from typing_extensions import Self
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ConfigType(metaclass=abc.ABCMeta):
|
|
11
|
+
"""A base class for composite config values that can be parsed and serialized from/to a string."""
|
|
12
|
+
|
|
13
|
+
@classmethod
|
|
14
|
+
@abc.abstractmethod
|
|
15
|
+
def from_string(cls, value: str) -> Self:
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
@abc.abstractmethod
|
|
19
|
+
def __str__(self) -> str:
|
|
20
|
+
pass
|
|
File without changes
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
import socket
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from scaler.config import defaults
|
|
6
|
+
from scaler.config.common.logging import LoggingConfig
|
|
7
|
+
from scaler.config.common.worker import WorkerConfig
|
|
8
|
+
from scaler.config.config_class import ConfigClass
|
|
9
|
+
from scaler.config.types.object_storage_server import ObjectStorageAddressConfig
|
|
10
|
+
from scaler.config.types.worker import WorkerNames
|
|
11
|
+
from scaler.config.types.zmq import ZMQConfig
|
|
12
|
+
from scaler.utility.event_loop import EventLoopType
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclasses.dataclass
|
|
16
|
+
class ClusterConfig(ConfigClass):
|
|
17
|
+
scheduler_address: ZMQConfig = dataclasses.field(
|
|
18
|
+
metadata=dict(positional=True, help="the scheduler address to connect to")
|
|
19
|
+
)
|
|
20
|
+
object_storage_address: Optional[ObjectStorageAddressConfig] = dataclasses.field(
|
|
21
|
+
default=None,
|
|
22
|
+
metadata=dict(
|
|
23
|
+
short="-osa",
|
|
24
|
+
help=(
|
|
25
|
+
"the object storage server address, e.g. tcp://localhost:2346. "
|
|
26
|
+
"if not specified, uses the address provided by the scheduler"
|
|
27
|
+
),
|
|
28
|
+
),
|
|
29
|
+
)
|
|
30
|
+
preload: Optional[str] = dataclasses.field(
|
|
31
|
+
default=None,
|
|
32
|
+
metadata=dict(
|
|
33
|
+
help='optional module init in the form "pkg.mod:func(arg1, arg2)" executed in each processor before tasks'
|
|
34
|
+
),
|
|
35
|
+
)
|
|
36
|
+
worker_names: WorkerNames = dataclasses.field(
|
|
37
|
+
default_factory=WorkerNames,
|
|
38
|
+
metadata=dict(
|
|
39
|
+
short="-wn", help="a comma-separated list of worker names to replace default worker names (host names)"
|
|
40
|
+
),
|
|
41
|
+
)
|
|
42
|
+
num_of_workers: int = dataclasses.field(
|
|
43
|
+
default=defaults.DEFAULT_NUMBER_OF_WORKER, metadata=dict(short="-n", help="the number of workers in cluster")
|
|
44
|
+
)
|
|
45
|
+
event_loop: str = dataclasses.field(
|
|
46
|
+
default="builtin",
|
|
47
|
+
metadata=dict(short="-el", choices=EventLoopType.allowed_types(), help="select the event loop type"),
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
worker_io_threads: int = dataclasses.field(
|
|
51
|
+
default=defaults.DEFAULT_IO_THREADS,
|
|
52
|
+
metadata=dict(short="-wit", help="set the number of io threads for io backend per worker"),
|
|
53
|
+
)
|
|
54
|
+
worker_config: WorkerConfig = dataclasses.field(default_factory=WorkerConfig)
|
|
55
|
+
logging_config: LoggingConfig = dataclasses.field(default_factory=LoggingConfig)
|
|
56
|
+
|
|
57
|
+
def __post_init__(self):
|
|
58
|
+
if self.worker_names.names and len(self.worker_names.names) != self.num_of_workers:
|
|
59
|
+
raise ValueError(
|
|
60
|
+
f"the number of worker_names ({len(self.worker_names.names)}) "
|
|
61
|
+
"must match num_of_workers ({self.num_of_workers})."
|
|
62
|
+
)
|
|
63
|
+
if not self.worker_names.names:
|
|
64
|
+
self.worker_names.names = [f"{socket.gethostname().split('.')[0]}" for _ in range(self.num_of_workers)]
|
|
65
|
+
if self.worker_io_threads <= 0:
|
|
66
|
+
raise ValueError("worker_io_threads must be a positive integer.")
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
from typing import List, Optional
|
|
3
|
+
|
|
4
|
+
from scaler.config import defaults
|
|
5
|
+
from scaler.config.common.logging import LoggingConfig
|
|
6
|
+
from scaler.config.common.web import WebConfig
|
|
7
|
+
from scaler.config.common.worker import WorkerConfig
|
|
8
|
+
from scaler.config.common.worker_adapter import WorkerAdapterConfig
|
|
9
|
+
from scaler.config.config_class import ConfigClass
|
|
10
|
+
from scaler.utility.event_loop import EventLoopType
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclasses.dataclass
|
|
14
|
+
class ECSWorkerAdapterConfig(ConfigClass):
|
|
15
|
+
web_config: WebConfig
|
|
16
|
+
worker_adapter_config: WorkerAdapterConfig
|
|
17
|
+
worker_config: WorkerConfig = dataclasses.field(default_factory=WorkerConfig)
|
|
18
|
+
logging_config: LoggingConfig = dataclasses.field(default_factory=LoggingConfig)
|
|
19
|
+
event_loop: str = dataclasses.field(
|
|
20
|
+
default="builtin",
|
|
21
|
+
metadata=dict(short="-el", choices=EventLoopType.allowed_types(), help="select the event loop type"),
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
worker_io_threads: int = dataclasses.field(
|
|
25
|
+
default=defaults.DEFAULT_IO_THREADS,
|
|
26
|
+
metadata=dict(short="-wit", help="set the number of io threads for io backend per worker"),
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# AWS / ECS specific configuration
|
|
30
|
+
aws_access_key_id: Optional[str] = dataclasses.field(
|
|
31
|
+
default=None, metadata=dict(env_var="AWS_ACCESS_KEY_ID", help="AWS access key id")
|
|
32
|
+
)
|
|
33
|
+
aws_secret_access_key: Optional[str] = dataclasses.field(
|
|
34
|
+
default=None, metadata=dict(env_var="AWS_SECRET_ACCESS_KEY", help="AWS secret access key")
|
|
35
|
+
)
|
|
36
|
+
aws_region: str = dataclasses.field(default="us-east-1", metadata=dict(help="AWS region for ECS cluster"))
|
|
37
|
+
ecs_subnets: List[str] = dataclasses.field(
|
|
38
|
+
default_factory=list,
|
|
39
|
+
metadata=dict(
|
|
40
|
+
type=lambda s: [x for x in s.split(",") if x],
|
|
41
|
+
required=True,
|
|
42
|
+
help="Comma-separated list of AWS subnet IDs for ECS tasks",
|
|
43
|
+
),
|
|
44
|
+
)
|
|
45
|
+
ecs_cluster: str = dataclasses.field(default="scaler-cluster", metadata=dict(help="ECS cluster name"))
|
|
46
|
+
ecs_task_image: str = dataclasses.field(
|
|
47
|
+
default="public.ecr.aws/v4u8j8r6/scaler:latest", metadata=dict(help="Container image used for ECS tasks")
|
|
48
|
+
)
|
|
49
|
+
ecs_python_requirements: str = dataclasses.field(
|
|
50
|
+
default="tomli;pargraph;parfun;pandas", metadata=dict(help="Python requirements string passed to the ECS task")
|
|
51
|
+
)
|
|
52
|
+
ecs_python_version: str = dataclasses.field(default="3.12.11", metadata=dict(help="Python version for ECS task"))
|
|
53
|
+
ecs_task_definition: str = dataclasses.field(
|
|
54
|
+
default="scaler-task-definition", metadata=dict(help="ECS task definition")
|
|
55
|
+
)
|
|
56
|
+
ecs_task_cpu: int = dataclasses.field(
|
|
57
|
+
default=4, metadata=dict(help="Number of vCPUs for task (used to derive worker count)")
|
|
58
|
+
)
|
|
59
|
+
ecs_task_memory: int = dataclasses.field(default=30, metadata=dict(help="Task memory in GB for Fargate"))
|
|
60
|
+
|
|
61
|
+
def __post_init__(self):
|
|
62
|
+
# Validate numeric and collection values
|
|
63
|
+
if self.ecs_task_cpu <= 0:
|
|
64
|
+
raise ValueError("ecs_task_cpu must be a positive integer.")
|
|
65
|
+
if self.ecs_task_memory <= 0:
|
|
66
|
+
raise ValueError("ecs_task_memory must be a positive integer.")
|
|
67
|
+
if not isinstance(self.ecs_subnets, list) or len(self.ecs_subnets) == 0:
|
|
68
|
+
raise ValueError("ecs_subnets must be a non-empty list of subnet ids.")
|
|
69
|
+
|
|
70
|
+
# Validate required strings
|
|
71
|
+
if not self.ecs_cluster:
|
|
72
|
+
raise ValueError("ecs_cluster cannot be an empty string.")
|
|
73
|
+
if not self.ecs_task_definition:
|
|
74
|
+
raise ValueError("ecs_task_definition cannot be an empty string.")
|
|
75
|
+
if not self.ecs_task_image:
|
|
76
|
+
raise ValueError("ecs_task_image cannot be an empty string.")
|
|
77
|
+
if self.worker_io_threads <= 0:
|
|
78
|
+
raise ValueError("worker_io_threads must be a positive integer.")
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
|
|
3
|
+
from scaler.config import defaults
|
|
4
|
+
from scaler.config.common.logging import LoggingConfig
|
|
5
|
+
from scaler.config.common.web import WebConfig
|
|
6
|
+
from scaler.config.common.worker import WorkerConfig
|
|
7
|
+
from scaler.config.common.worker_adapter import WorkerAdapterConfig
|
|
8
|
+
from scaler.config.config_class import ConfigClass
|
|
9
|
+
from scaler.utility.event_loop import EventLoopType
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclasses.dataclass
|
|
13
|
+
class NativeWorkerAdapterConfig(ConfigClass):
|
|
14
|
+
web_config: WebConfig
|
|
15
|
+
worker_adapter_config: WorkerAdapterConfig
|
|
16
|
+
worker_config: WorkerConfig = dataclasses.field(default_factory=WorkerConfig)
|
|
17
|
+
logging_config: LoggingConfig = dataclasses.field(default_factory=LoggingConfig)
|
|
18
|
+
event_loop: str = dataclasses.field(
|
|
19
|
+
default="builtin",
|
|
20
|
+
metadata=dict(short="-el", choices=EventLoopType.allowed_types(), help="select the event loop type"),
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
worker_io_threads: int = dataclasses.field(
|
|
24
|
+
default=defaults.DEFAULT_IO_THREADS,
|
|
25
|
+
metadata=dict(short="-wit", help="set the number of io threads for io backend per worker"),
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
def __post_init__(self) -> None:
|
|
29
|
+
if self.worker_io_threads <= 0:
|
|
30
|
+
raise ValueError("worker_io_threads must be a positive integer.")
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
|
|
3
|
+
from scaler.config.config_class import ConfigClass
|
|
4
|
+
from scaler.config.types.object_storage_server import ObjectStorageAddressConfig
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclasses.dataclass
|
|
8
|
+
class ObjectStorageServerConfig(ConfigClass):
|
|
9
|
+
object_storage_address: ObjectStorageAddressConfig = dataclasses.field(
|
|
10
|
+
metadata=dict(
|
|
11
|
+
positional=True, help="specify the object storage server address to listen to, e.g. tcp://localhost:2345."
|
|
12
|
+
)
|
|
13
|
+
)
|