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,272 @@
1
+ import curses
2
+ import functools
3
+ from typing import Dict, List, Literal, Union
4
+
5
+ from scaler.config.section.top import TopConfig
6
+ from scaler.io.sync_subscriber import ZMQSyncSubscriber
7
+ from scaler.protocol.python.message import StateScheduler
8
+ from scaler.protocol.python.mixins import Message
9
+ from scaler.utility.formatter import (
10
+ format_bytes,
11
+ format_integer,
12
+ format_microseconds,
13
+ format_percentage,
14
+ format_seconds,
15
+ )
16
+
17
+ SORT_BY_OPTIONS = {
18
+ ord("g"): "group",
19
+ ord("n"): "worker",
20
+ ord("C"): "agt_cpu",
21
+ ord("M"): "agt_rss",
22
+ ord("c"): "cpu",
23
+ ord("m"): "rss",
24
+ ord("F"): "rss_free",
25
+ ord("f"): "free",
26
+ ord("w"): "sent",
27
+ ord("d"): "queued",
28
+ ord("s"): "suspended",
29
+ ord("l"): "lag",
30
+ }
31
+
32
+ SORT_BY_STATE: Dict[str, Union[str, bool]] = {"sort_by": "cpu", "sort_by_previous": "cpu", "sort_reverse": True}
33
+
34
+
35
+ def main():
36
+ curses.wrapper(poke, TopConfig.parse("A Top-like Application for Scaler Monitoring", "top"))
37
+
38
+
39
+ def poke(screen, config: TopConfig):
40
+ screen.nodelay(1)
41
+
42
+ try:
43
+ subscriber = ZMQSyncSubscriber(
44
+ address=config.monitor_address,
45
+ callback=functools.partial(show_status, screen=screen),
46
+ topic=b"",
47
+ daemonic=False,
48
+ timeout_seconds=config.timeout,
49
+ )
50
+ subscriber.run()
51
+ except KeyboardInterrupt:
52
+ pass
53
+
54
+
55
+ def show_status(status: Message, screen):
56
+ if not isinstance(status, StateScheduler):
57
+ return
58
+
59
+ __change_option_state(screen.getch())
60
+
61
+ scheduler_table = __generate_keyword_data(
62
+ "scheduler",
63
+ {
64
+ "cpu": format_percentage(status.scheduler.cpu),
65
+ "rss": format_bytes(status.scheduler.rss),
66
+ "rss_free": format_bytes(status.rss_free),
67
+ },
68
+ )
69
+
70
+ task_manager_table = __generate_keyword_data(
71
+ "task_manager",
72
+ dict(sorted((k.name, v) for k, v in status.task_manager.state_to_count.items())),
73
+ format_integer_flag=True,
74
+ )
75
+ object_manager = __generate_keyword_data("object_manager", {"num_of_objs": status.object_manager.number_of_objects})
76
+ sent_table = __generate_keyword_data("scheduler_sent", status.binder.sent, format_integer_flag=True)
77
+ received_table = __generate_keyword_data("scheduler_received", status.binder.received, format_integer_flag=True)
78
+ client_table = __generate_keyword_data(
79
+ "client_manager", status.client_manager.client_to_num_of_tasks, key_col_length=18
80
+ )
81
+
82
+ worker_group_map = {}
83
+ if status.scaling_manager.worker_groups:
84
+ for worker_group_id, worker_ids in status.scaling_manager.worker_groups.items():
85
+ worker_group_id_str = worker_group_id.decode()
86
+ for worker_id in worker_ids:
87
+ worker_group_map[worker_id.decode()] = worker_group_id_str
88
+
89
+ # Include 'group' as the first column for each worker; empty if not found
90
+ worker_manager_table = __generate_worker_manager_table(
91
+ [
92
+ {
93
+ "group": worker_group_map.get(worker.worker_id.decode(), ""),
94
+ "worker": worker.worker_id.decode(),
95
+ "agt_cpu": worker.agent.cpu,
96
+ "agt_rss": worker.agent.rss,
97
+ "cpu": sum(p.resource.cpu for p in worker.processor_statuses),
98
+ "rss": sum(p.resource.rss for p in worker.processor_statuses),
99
+ "os_rss_free": worker.rss_free,
100
+ "free": worker.free,
101
+ "sent": worker.sent,
102
+ "queued": worker.queued,
103
+ "suspended": worker.suspended,
104
+ "lag": worker.lag_us,
105
+ "last": worker.last_s,
106
+ "ITL": worker.itl,
107
+ }
108
+ for worker in status.worker_manager.workers
109
+ ],
110
+ worker_group_length=10,
111
+ worker_length=20,
112
+ )
113
+
114
+ table1 = __merge_tables(scheduler_table, object_manager, padding="|")
115
+ table1 = __merge_tables(table1, task_manager_table, padding="|")
116
+ table1 = __merge_tables(table1, sent_table, padding="|")
117
+ table1 = __merge_tables(table1, received_table, padding="|")
118
+
119
+ table3 = __merge_tables(worker_manager_table, client_table, padding="|")
120
+
121
+ screen.clear()
122
+ try:
123
+ new_row, max_cols = __print_table(screen, 0, table1, padding=1)
124
+ except curses.error:
125
+ __print_too_small(screen)
126
+ return
127
+
128
+ try:
129
+ screen.addstr(new_row, 0, "-" * max_cols)
130
+ screen.addstr(new_row + 1, 0, "Shortcuts: " + " ".join([f"{v}[{chr(k)}]" for k, v in SORT_BY_OPTIONS.items()]))
131
+ screen.addstr(
132
+ new_row + 3,
133
+ 0,
134
+ f"Total {len(status.scaling_manager.worker_groups)} worker group(s) "
135
+ f"with {len(status.worker_manager.workers)} worker(s)",
136
+ )
137
+ _ = __print_table(screen, new_row + 4, table3)
138
+ except curses.error:
139
+ pass
140
+
141
+ screen.refresh()
142
+
143
+
144
+ def __generate_keyword_data(title, data, key_col_length: int = 0, format_integer_flag: bool = False):
145
+ table = [[title, ""]]
146
+
147
+ def format_integer_func(value):
148
+ if format_integer_flag:
149
+ return format_integer(value)
150
+
151
+ return value
152
+
153
+ table.extend([[__truncate(k, key_col_length), format_integer_func(v)] for k, v in data.items()])
154
+ return table
155
+
156
+
157
+ def __generate_worker_manager_table(
158
+ wm_data: List[Dict], worker_group_length: int, worker_length: int
159
+ ) -> List[List[str]]:
160
+ if not wm_data:
161
+ headers = [["No workers"]]
162
+ return headers
163
+
164
+ wm_data = sorted(
165
+ wm_data, key=lambda item: item[SORT_BY_STATE["sort_by"]], reverse=bool(SORT_BY_STATE["sort_reverse"])
166
+ )
167
+
168
+ for row in wm_data:
169
+ row["group"] = __truncate(row["group"], worker_group_length, how="left")
170
+ row["worker"] = __truncate(row["worker"], worker_length, how="left")
171
+ row["agt_cpu"] = format_percentage(row["agt_cpu"])
172
+ row["agt_rss"] = format_bytes(row["agt_rss"])
173
+ row["cpu"] = format_percentage(row["cpu"])
174
+ row["rss"] = format_bytes(row["rss"])
175
+ row["os_rss_free"] = format_bytes(row["os_rss_free"])
176
+
177
+ last = row.pop("last")
178
+ last = f"({format_seconds(last)}) " if last > 5 else ""
179
+ row["lag"] = last + format_microseconds(row["lag"])
180
+
181
+ worker_manager_table = [[f"[{v}]" if v == SORT_BY_STATE["sort_by"] else v for v in wm_data[0].keys()]]
182
+ worker_manager_table.extend([list(worker.values()) for worker in wm_data])
183
+ return worker_manager_table
184
+
185
+
186
+ def __print_table(screen, line_number, data, padding: int = 1):
187
+ if not data:
188
+ return
189
+
190
+ col_widths = [max(len(str(row[i])) for row in data) for i in range(len(data[0]))]
191
+
192
+ for i, header in enumerate(data[0]):
193
+ screen.addstr(line_number, sum(col_widths[:i]) + (padding * i), str(header).rjust(col_widths[i]))
194
+
195
+ for i, row in enumerate(data[1:], start=1):
196
+ for j, cell in enumerate(row):
197
+ screen.addstr(line_number + i, sum(col_widths[:j]) + (padding * j), str(cell).rjust(col_widths[j]))
198
+
199
+ return line_number + len(data), sum(col_widths) + (padding * len(col_widths))
200
+
201
+
202
+ def __merge_tables(left: List[List], right: List[List], padding: str = "") -> List[List]:
203
+ if not left:
204
+ return right
205
+
206
+ if not right:
207
+ return left
208
+
209
+ result = []
210
+ for i in range(max(len(left), len(right))):
211
+ if i < len(left):
212
+ left_row = left[i]
213
+ else:
214
+ left_row = [""] * len(left[0])
215
+
216
+ if i < len(right):
217
+ right_row = right[i]
218
+ else:
219
+ right_row = [""] * len(right[0])
220
+
221
+ if padding:
222
+ padding_column = [padding]
223
+ result.append(left_row + padding_column + right_row)
224
+ else:
225
+ result.append(left_row + right_row)
226
+
227
+ return result
228
+
229
+
230
+ def __concat_tables(up: List[List], down: List[List], padding: int = 1) -> List[List]:
231
+ max_cols = max([len(row) for row in up] + [len(row) for row in down])
232
+ for row in up:
233
+ row.extend([""] * (max_cols - len(row)))
234
+
235
+ padding_rows = [[""] * max_cols] * padding
236
+
237
+ for row in down:
238
+ row.extend([""] * (max_cols - len(row)))
239
+
240
+ return up + padding_rows + down
241
+
242
+
243
+ def __truncate(string: str, number: int, how: Literal["left", "right"] = "left") -> str:
244
+ if number <= 0:
245
+ return string
246
+
247
+ if len(string) <= number:
248
+ return string
249
+
250
+ if how == "left":
251
+ return f"{string[:number]}+"
252
+ else:
253
+ return f"+{string[-number:]}"
254
+
255
+
256
+ def __print_too_small(screen):
257
+ screen.clear()
258
+ screen.addstr(0, 0, "Your terminal is too small to show")
259
+ screen.refresh()
260
+
261
+
262
+ def __change_option_state(option: int):
263
+ if option not in SORT_BY_OPTIONS.keys():
264
+ return
265
+
266
+ SORT_BY_STATE["sort_by_previous"] = SORT_BY_STATE["sort_by"]
267
+ SORT_BY_STATE["sort_by"] = SORT_BY_OPTIONS[option]
268
+ if SORT_BY_STATE["sort_by"] != SORT_BY_STATE["sort_by_previous"]:
269
+ SORT_BY_STATE["sort_reverse"] = True
270
+ return
271
+
272
+ SORT_BY_STATE["sort_reverse"] = not SORT_BY_STATE["sort_reverse"]
@@ -0,0 +1,6 @@
1
+ from scaler.config.section.webui import WebUIConfig
2
+ from scaler.ui.webui import start_webui
3
+
4
+
5
+ def main():
6
+ start_webui(WebUIConfig.parse("Web UI for Scaler Monitoring", "webui"))
@@ -0,0 +1,22 @@
1
+ from aiohttp import web
2
+
3
+ from scaler.config.section.ecs_worker_adapter import ECSWorkerAdapterConfig
4
+ from scaler.utility.event_loop import register_event_loop
5
+ from scaler.utility.logging.utility import setup_logger
6
+ from scaler.worker_adapter.ecs import ECSWorkerAdapter
7
+
8
+
9
+ def main():
10
+ ecs_config = ECSWorkerAdapterConfig.parse("Scaler ECS Worker Adapter", "ecs_worker_adapter")
11
+ register_event_loop(ecs_config.event_loop)
12
+ setup_logger(
13
+ ecs_config.logging_config.paths, ecs_config.logging_config.config_file, ecs_config.logging_config.level
14
+ )
15
+ ecs_worker_adapter = ECSWorkerAdapter(ecs_config)
16
+
17
+ app = ecs_worker_adapter.create_app()
18
+ web.run_app(app, host=ecs_config.web_config.adapter_web_host, port=ecs_config.web_config.adapter_web_port)
19
+
20
+
21
+ if __name__ == "__main__":
22
+ main()
@@ -0,0 +1,31 @@
1
+ from aiohttp import web
2
+
3
+ from scaler.config.section.native_worker_adapter import NativeWorkerAdapterConfig
4
+ from scaler.utility.event_loop import register_event_loop
5
+ from scaler.utility.logging.utility import setup_logger
6
+ from scaler.worker_adapter.native import NativeWorkerAdapter
7
+
8
+
9
+ def main():
10
+ native_adapter_config = NativeWorkerAdapterConfig.parse("Scaler Native Worker Adapter", "native_worker_adapter")
11
+
12
+ register_event_loop(native_adapter_config.event_loop)
13
+
14
+ setup_logger(
15
+ native_adapter_config.logging_config.paths,
16
+ native_adapter_config.logging_config.config_file,
17
+ native_adapter_config.logging_config.level,
18
+ )
19
+
20
+ native_worker_adapter = NativeWorkerAdapter(native_adapter_config)
21
+
22
+ app = native_worker_adapter.create_app()
23
+ web.run_app(
24
+ app,
25
+ host=native_adapter_config.web_config.adapter_web_host,
26
+ port=native_adapter_config.web_config.adapter_web_port,
27
+ )
28
+
29
+
30
+ if __name__ == "__main__":
31
+ main()
@@ -0,0 +1,26 @@
1
+ from aiohttp import web
2
+
3
+ from scaler.config.section.symphony_worker_adapter import SymphonyWorkerConfig
4
+ from scaler.utility.event_loop import register_event_loop
5
+ from scaler.utility.logging.utility import setup_logger
6
+ from scaler.worker_adapter.symphony.worker_adapter import SymphonyWorkerAdapter
7
+
8
+
9
+ def main():
10
+ symphony_config = SymphonyWorkerConfig.parse("Scaler Symphony Worker Adapter", "symphony_worker_adapter")
11
+ register_event_loop(symphony_config.event_loop)
12
+
13
+ setup_logger(
14
+ symphony_config.logging_config.paths,
15
+ symphony_config.logging_config.config_file,
16
+ symphony_config.logging_config.level,
17
+ )
18
+
19
+ symphony_worker_adapter = SymphonyWorkerAdapter(symphony_config)
20
+
21
+ app = symphony_worker_adapter.create_app()
22
+ web.run_app(app, host=symphony_config.web_config.adapter_web_host, port=symphony_config.web_config.adapter_web_port)
23
+
24
+
25
+ if __name__ == "__main__":
26
+ main()
scaler/io/__init__.py ADDED
File without changes
@@ -0,0 +1,89 @@
1
+ import logging
2
+ import os
3
+ import uuid
4
+ from collections import defaultdict
5
+ from typing import Awaitable, Callable, Dict, List, Optional
6
+
7
+ import zmq.asyncio
8
+ from zmq import Frame
9
+
10
+ from scaler.config.types.zmq import ZMQConfig
11
+ from scaler.io.mixins import AsyncBinder
12
+ from scaler.io.utility import deserialize, serialize
13
+ from scaler.protocol.python.mixins import Message
14
+ from scaler.protocol.python.status import BinderStatus
15
+
16
+
17
+ class ZMQAsyncBinder(AsyncBinder):
18
+ def __init__(self, context: zmq.asyncio.Context, name: str, address: ZMQConfig, identity: Optional[bytes] = None):
19
+ self._address = address
20
+
21
+ if identity is None:
22
+ identity = f"{os.getpid()}|{name}|{uuid.uuid4()}".encode()
23
+ self._identity = identity
24
+
25
+ self._context = context
26
+ self._socket = self._context.socket(zmq.ROUTER)
27
+ self.__set_socket_options()
28
+ self._socket.bind(self._address.to_address())
29
+
30
+ self._callback: Optional[Callable[[bytes, Message], Awaitable[None]]] = None
31
+
32
+ self._received: Dict[str, int] = defaultdict(lambda: 0)
33
+ self._sent: Dict[str, int] = defaultdict(lambda: 0)
34
+
35
+ @property
36
+ def identity(self):
37
+ return self._identity
38
+
39
+ def destroy(self):
40
+ self._context.destroy(linger=0)
41
+
42
+ def register(self, callback: Callable[[bytes, Message], Awaitable[None]]):
43
+ self._callback = callback
44
+
45
+ async def routine(self):
46
+ frames: List[Frame] = await self._socket.recv_multipart(copy=False)
47
+ if not self.__is_valid_message(frames):
48
+ return
49
+
50
+ source, payload = frames
51
+ try:
52
+ message: Optional[Message] = deserialize(payload.bytes)
53
+ if message is None:
54
+ logging.error(f"received unknown message from {source.bytes!r}: {payload!r}")
55
+ return
56
+ except Exception as e:
57
+ logging.error(f"{self.__get_prefix()} failed to deserialize message from {source.bytes!r}: {e}")
58
+ return
59
+
60
+ self.__count_received(message.__class__.__name__)
61
+ await self._callback(source.bytes, message)
62
+
63
+ async def send(self, to: bytes, message: Message):
64
+ self.__count_sent(message.__class__.__name__)
65
+ await self._socket.send_multipart([to, serialize(message)], copy=False)
66
+
67
+ def get_status(self) -> BinderStatus:
68
+ return BinderStatus.new_msg(received=self._received, sent=self._sent)
69
+
70
+ def __set_socket_options(self):
71
+ self._socket.setsockopt(zmq.IDENTITY, self._identity)
72
+ self._socket.setsockopt(zmq.SNDHWM, 0)
73
+ self._socket.setsockopt(zmq.RCVHWM, 0)
74
+
75
+ def __is_valid_message(self, frames: List[Frame]) -> bool:
76
+ if len(frames) != 2:
77
+ logging.error(f"{self.__get_prefix()} received unexpected frames {frames}")
78
+ return False
79
+
80
+ return True
81
+
82
+ def __count_received(self, message_type: str):
83
+ self._received[message_type] += 1
84
+
85
+ def __count_sent(self, message_type: str):
86
+ self._sent[message_type] += 1
87
+
88
+ def __get_prefix(self):
89
+ return f"{self.__class__.__name__}[{self._identity.decode()}]:"
@@ -0,0 +1,95 @@
1
+ import logging
2
+ import os
3
+ import uuid
4
+ from typing import Awaitable, Callable, Literal, Optional
5
+
6
+ import zmq.asyncio
7
+
8
+ from scaler.config.types.zmq import ZMQConfig
9
+ from scaler.io.mixins import AsyncConnector
10
+ from scaler.io.utility import deserialize, serialize
11
+ from scaler.protocol.python.mixins import Message
12
+
13
+
14
+ class ZMQAsyncConnector(AsyncConnector):
15
+ def __init__(
16
+ self,
17
+ context: zmq.asyncio.Context,
18
+ name: str,
19
+ socket_type: int,
20
+ address: ZMQConfig,
21
+ bind_or_connect: Literal["bind", "connect"],
22
+ callback: Optional[Callable[[Message], Awaitable[None]]],
23
+ identity: Optional[bytes],
24
+ ):
25
+ self._address = address
26
+
27
+ self._context = context
28
+ self._socket = self._context.socket(socket_type)
29
+
30
+ if identity is None:
31
+ identity = f"{os.getpid()}|{name}|{uuid.uuid4().bytes.hex()}".encode()
32
+ self._identity = identity
33
+
34
+ # set socket option
35
+ self._socket.setsockopt(zmq.IDENTITY, self._identity)
36
+ self._socket.setsockopt(zmq.SNDHWM, 0)
37
+ self._socket.setsockopt(zmq.RCVHWM, 0)
38
+
39
+ if bind_or_connect == "bind":
40
+ self._socket.bind(self.address)
41
+ elif bind_or_connect == "connect":
42
+ self._socket.connect(self.address)
43
+ else:
44
+ raise TypeError("bind_or_connect has to be 'bind' or 'connect'")
45
+
46
+ self._callback: Optional[Callable[[Message], Awaitable[None]]] = callback
47
+
48
+ def __del__(self):
49
+ self.destroy()
50
+
51
+ def destroy(self):
52
+ if self._socket.closed:
53
+ return
54
+
55
+ self._socket.close(linger=1)
56
+
57
+ @property
58
+ def identity(self) -> bytes:
59
+ return self._identity
60
+
61
+ @property
62
+ def socket(self) -> zmq.asyncio.Socket:
63
+ return self._socket
64
+
65
+ @property
66
+ def address(self) -> str:
67
+ return self._address.to_address()
68
+
69
+ async def routine(self):
70
+ if self._callback is None:
71
+ return
72
+
73
+ message: Optional[Message] = await self.receive()
74
+ if message is None:
75
+ return
76
+
77
+ await self._callback(message)
78
+
79
+ async def receive(self) -> Optional[Message]:
80
+ if self._context.closed:
81
+ return None
82
+
83
+ if self._socket.closed:
84
+ return None
85
+
86
+ payload = await self._socket.recv(copy=False)
87
+ result: Optional[Message] = deserialize(payload.bytes)
88
+ if result is None:
89
+ logging.error(f"received unknown message: {payload.bytes!r}")
90
+ return None
91
+
92
+ return result
93
+
94
+ async def send(self, message: Message):
95
+ await self._socket.send(serialize(message), copy=False)