flwr 1.24.0__py3-none-any.whl → 1.26.0__py3-none-any.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.
- flwr/__init__.py +1 -1
- flwr/app/__init__.py +4 -1
- flwr/app/message_type.py +29 -0
- flwr/app/metadata.py +5 -2
- flwr/app/user_config.py +19 -0
- flwr/cli/app.py +37 -19
- flwr/cli/app_cmd/publish.py +25 -75
- flwr/cli/app_cmd/review.py +25 -66
- flwr/cli/auth_plugin/auth_plugin.py +5 -10
- flwr/cli/auth_plugin/noop_auth_plugin.py +1 -2
- flwr/cli/auth_plugin/oidc_cli_plugin.py +38 -38
- flwr/cli/build.py +15 -28
- flwr/cli/config/__init__.py +21 -0
- flwr/cli/config/ls.py +71 -0
- flwr/cli/config_migration.py +297 -0
- flwr/cli/config_utils.py +63 -156
- flwr/cli/constant.py +71 -0
- flwr/cli/federation/__init__.py +0 -2
- flwr/cli/federation/ls.py +256 -64
- flwr/cli/flower_config.py +429 -0
- flwr/cli/install.py +23 -62
- flwr/cli/log.py +23 -37
- flwr/cli/login/login.py +29 -63
- flwr/cli/ls.py +72 -61
- flwr/cli/new/new.py +98 -309
- flwr/cli/pull.py +19 -37
- flwr/cli/run/run.py +87 -100
- flwr/cli/run_utils.py +23 -5
- flwr/cli/stop.py +33 -74
- flwr/cli/supernode/ls.py +35 -62
- flwr/cli/supernode/register.py +31 -80
- flwr/cli/supernode/unregister.py +24 -70
- flwr/cli/typing.py +200 -0
- flwr/cli/utils.py +160 -412
- flwr/client/grpc_adapter_client/connection.py +2 -2
- flwr/client/grpc_rere_client/connection.py +9 -6
- flwr/client/grpc_rere_client/grpc_adapter.py +1 -1
- flwr/client/message_handler/message_handler.py +2 -1
- flwr/client/mod/centraldp_mods.py +1 -1
- flwr/client/mod/localdp_mod.py +1 -1
- flwr/client/mod/secure_aggregation/secaggplus_mod.py +1 -1
- flwr/client/rest_client/connection.py +6 -4
- flwr/client/run_info_store.py +2 -1
- flwr/clientapp/client_app.py +2 -1
- flwr/common/__init__.py +3 -2
- flwr/common/args.py +5 -5
- flwr/common/config.py +12 -17
- flwr/common/constant.py +3 -16
- flwr/common/context.py +2 -1
- flwr/common/exit/exit.py +4 -4
- flwr/common/exit/exit_code.py +6 -0
- flwr/common/grpc.py +2 -1
- flwr/common/logger.py +1 -1
- flwr/common/message.py +1 -1
- flwr/common/retry_invoker.py +13 -5
- flwr/common/secure_aggregation/ndarrays_arithmetic.py +5 -2
- flwr/common/serde.py +13 -5
- flwr/common/telemetry.py +1 -1
- flwr/common/typing.py +10 -3
- flwr/compat/client/app.py +6 -9
- flwr/compat/client/grpc_client/connection.py +2 -1
- flwr/compat/common/constant.py +29 -0
- flwr/compat/server/app.py +1 -1
- flwr/proto/clientappio_pb2.py +2 -2
- flwr/proto/clientappio_pb2_grpc.py +104 -88
- flwr/proto/clientappio_pb2_grpc.pyi +140 -80
- flwr/proto/federation_pb2.py +5 -3
- flwr/proto/federation_pb2.pyi +32 -2
- flwr/proto/fleet_pb2.py +10 -10
- flwr/proto/fleet_pb2.pyi +5 -1
- flwr/proto/run_pb2.py +18 -26
- flwr/proto/run_pb2.pyi +10 -58
- flwr/proto/serverappio_pb2.py +2 -2
- flwr/proto/serverappio_pb2_grpc.py +138 -207
- flwr/proto/serverappio_pb2_grpc.pyi +189 -155
- flwr/proto/simulationio_pb2.py +2 -2
- flwr/proto/simulationio_pb2_grpc.py +62 -90
- flwr/proto/simulationio_pb2_grpc.pyi +95 -55
- flwr/server/app.py +7 -13
- flwr/server/compat/grid_client_proxy.py +2 -1
- flwr/server/grid/grpc_grid.py +5 -5
- flwr/server/serverapp/app.py +11 -4
- flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +1 -1
- flwr/server/superlink/fleet/grpc_rere/node_auth_server_interceptor.py +13 -12
- flwr/server/superlink/fleet/message_handler/message_handler.py +42 -2
- flwr/server/superlink/linkstate/__init__.py +2 -2
- flwr/server/superlink/linkstate/in_memory_linkstate.py +36 -10
- flwr/server/superlink/linkstate/linkstate.py +34 -21
- flwr/server/superlink/linkstate/linkstate_factory.py +16 -8
- flwr/server/superlink/linkstate/{sqlite_linkstate.py → sql_linkstate.py} +471 -516
- flwr/server/superlink/linkstate/utils.py +49 -2
- flwr/server/superlink/serverappio/serverappio_servicer.py +1 -33
- flwr/server/superlink/simulation/simulationio_servicer.py +0 -19
- flwr/server/utils/validator.py +1 -1
- flwr/server/workflow/default_workflows.py +2 -1
- flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +1 -1
- flwr/serverapp/strategy/bulyan.py +7 -1
- flwr/serverapp/strategy/dp_fixed_clipping.py +9 -1
- flwr/serverapp/strategy/fedavg.py +1 -1
- flwr/serverapp/strategy/fedxgb_cyclic.py +1 -1
- flwr/simulation/ray_transport/ray_client_proxy.py +2 -6
- flwr/simulation/run_simulation.py +3 -12
- flwr/simulation/simulationio_connection.py +3 -3
- flwr/{common → supercore}/address.py +7 -33
- flwr/supercore/app_utils.py +2 -1
- flwr/supercore/constant.py +27 -2
- flwr/supercore/corestate/{sqlite_corestate.py → sql_corestate.py} +19 -23
- flwr/supercore/credential_store/__init__.py +33 -0
- flwr/supercore/credential_store/credential_store.py +34 -0
- flwr/supercore/credential_store/file_credential_store.py +76 -0
- flwr/{common → supercore}/date.py +0 -11
- flwr/supercore/ffs/disk_ffs.py +1 -1
- flwr/supercore/object_store/object_store_factory.py +14 -6
- flwr/supercore/object_store/{sqlite_object_store.py → sql_object_store.py} +115 -117
- flwr/supercore/sql_mixin.py +315 -0
- flwr/{cli/new/templates → supercore/state}/__init__.py +2 -2
- flwr/{cli/new/templates/app/code/flwr_tune → supercore/state/alembic}/__init__.py +2 -2
- flwr/supercore/state/alembic/env.py +103 -0
- flwr/supercore/state/alembic/script.py.mako +43 -0
- flwr/supercore/state/alembic/utils.py +239 -0
- flwr/{cli/new/templates/app → supercore/state/alembic/versions}/__init__.py +2 -2
- flwr/supercore/state/alembic/versions/rev_2026_01_28_initialize_migration_of_state_tables.py +200 -0
- flwr/supercore/state/schema/README.md +121 -0
- flwr/{cli/new/templates/app/code → supercore/state/schema}/__init__.py +2 -2
- flwr/supercore/state/schema/corestate_tables.py +36 -0
- flwr/supercore/state/schema/linkstate_tables.py +152 -0
- flwr/supercore/state/schema/objectstore_tables.py +90 -0
- flwr/supercore/superexec/run_superexec.py +2 -2
- flwr/supercore/utils.py +225 -0
- flwr/superlink/federation/federation_manager.py +2 -2
- flwr/superlink/federation/noop_federation_manager.py +8 -6
- flwr/superlink/servicer/control/control_grpc.py +2 -0
- flwr/superlink/servicer/control/control_servicer.py +106 -21
- flwr/supernode/cli/flower_supernode.py +2 -1
- flwr/supernode/nodestate/in_memory_nodestate.py +62 -1
- flwr/supernode/nodestate/nodestate.py +45 -0
- flwr/supernode/runtime/run_clientapp.py +14 -14
- flwr/supernode/servicer/clientappio/clientappio_servicer.py +13 -5
- flwr/supernode/start_client_internal.py +17 -10
- {flwr-1.24.0.dist-info → flwr-1.26.0.dist-info}/METADATA +8 -8
- {flwr-1.24.0.dist-info → flwr-1.26.0.dist-info}/RECORD +144 -184
- flwr/cli/federation/show.py +0 -317
- flwr/cli/new/templates/app/.gitignore.tpl +0 -163
- flwr/cli/new/templates/app/LICENSE.tpl +0 -202
- flwr/cli/new/templates/app/README.baseline.md.tpl +0 -127
- flwr/cli/new/templates/app/README.flowertune.md.tpl +0 -68
- flwr/cli/new/templates/app/README.md.tpl +0 -37
- flwr/cli/new/templates/app/code/__init__.baseline.py.tpl +0 -1
- flwr/cli/new/templates/app/code/__init__.py.tpl +0 -1
- flwr/cli/new/templates/app/code/__init__.pytorch_legacy_api.py.tpl +0 -1
- flwr/cli/new/templates/app/code/client.baseline.py.tpl +0 -75
- flwr/cli/new/templates/app/code/client.huggingface.py.tpl +0 -93
- flwr/cli/new/templates/app/code/client.jax.py.tpl +0 -71
- flwr/cli/new/templates/app/code/client.mlx.py.tpl +0 -102
- flwr/cli/new/templates/app/code/client.numpy.py.tpl +0 -46
- flwr/cli/new/templates/app/code/client.pytorch.py.tpl +0 -80
- flwr/cli/new/templates/app/code/client.pytorch_legacy_api.py.tpl +0 -55
- flwr/cli/new/templates/app/code/client.sklearn.py.tpl +0 -108
- flwr/cli/new/templates/app/code/client.tensorflow.py.tpl +0 -82
- flwr/cli/new/templates/app/code/client.xgboost.py.tpl +0 -110
- flwr/cli/new/templates/app/code/dataset.baseline.py.tpl +0 -36
- flwr/cli/new/templates/app/code/flwr_tune/client_app.py.tpl +0 -92
- flwr/cli/new/templates/app/code/flwr_tune/dataset.py.tpl +0 -87
- flwr/cli/new/templates/app/code/flwr_tune/models.py.tpl +0 -56
- flwr/cli/new/templates/app/code/flwr_tune/server_app.py.tpl +0 -73
- flwr/cli/new/templates/app/code/flwr_tune/strategy.py.tpl +0 -78
- flwr/cli/new/templates/app/code/model.baseline.py.tpl +0 -66
- flwr/cli/new/templates/app/code/server.baseline.py.tpl +0 -43
- flwr/cli/new/templates/app/code/server.huggingface.py.tpl +0 -42
- flwr/cli/new/templates/app/code/server.jax.py.tpl +0 -39
- flwr/cli/new/templates/app/code/server.mlx.py.tpl +0 -41
- flwr/cli/new/templates/app/code/server.numpy.py.tpl +0 -38
- flwr/cli/new/templates/app/code/server.pytorch.py.tpl +0 -41
- flwr/cli/new/templates/app/code/server.pytorch_legacy_api.py.tpl +0 -31
- flwr/cli/new/templates/app/code/server.sklearn.py.tpl +0 -44
- flwr/cli/new/templates/app/code/server.tensorflow.py.tpl +0 -38
- flwr/cli/new/templates/app/code/server.xgboost.py.tpl +0 -56
- flwr/cli/new/templates/app/code/strategy.baseline.py.tpl +0 -1
- flwr/cli/new/templates/app/code/task.huggingface.py.tpl +0 -98
- flwr/cli/new/templates/app/code/task.jax.py.tpl +0 -57
- flwr/cli/new/templates/app/code/task.mlx.py.tpl +0 -102
- flwr/cli/new/templates/app/code/task.numpy.py.tpl +0 -7
- flwr/cli/new/templates/app/code/task.pytorch.py.tpl +0 -99
- flwr/cli/new/templates/app/code/task.pytorch_legacy_api.py.tpl +0 -111
- flwr/cli/new/templates/app/code/task.sklearn.py.tpl +0 -67
- flwr/cli/new/templates/app/code/task.tensorflow.py.tpl +0 -52
- flwr/cli/new/templates/app/code/task.xgboost.py.tpl +0 -67
- flwr/cli/new/templates/app/code/utils.baseline.py.tpl +0 -1
- flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +0 -146
- flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +0 -80
- flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +0 -65
- flwr/cli/new/templates/app/pyproject.jax.toml.tpl +0 -52
- flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +0 -56
- flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +0 -49
- flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +0 -53
- flwr/cli/new/templates/app/pyproject.pytorch_legacy_api.toml.tpl +0 -53
- flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +0 -52
- flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +0 -53
- flwr/cli/new/templates/app/pyproject.xgboost.toml.tpl +0 -61
- flwr/common/pyproject.py +0 -42
- flwr/supercore/sqlite_mixin.py +0 -159
- /flwr/{common → supercore}/version.py +0 -0
- {flwr-1.24.0.dist-info → flwr-1.26.0.dist-info}/WHEEL +0 -0
- {flwr-1.24.0.dist-info → flwr-1.26.0.dist-info}/entry_points.txt +0 -0
flwr/server/grid/grpc_grid.py
CHANGED
|
@@ -28,7 +28,6 @@ from flwr.common.constant import (
|
|
|
28
28
|
SERVERAPPIO_API_DEFAULT_CLIENT_ADDRESS,
|
|
29
29
|
SUPERLINK_NODE_ID,
|
|
30
30
|
ErrorCode,
|
|
31
|
-
MessageType,
|
|
32
31
|
)
|
|
33
32
|
from flwr.common.grpc import create_channel, on_channel_state_change
|
|
34
33
|
from flwr.common.inflatable import (
|
|
@@ -50,7 +49,7 @@ from flwr.common.inflatable_utils import (
|
|
|
50
49
|
)
|
|
51
50
|
from flwr.common.logger import log, warn_deprecated_feature
|
|
52
51
|
from flwr.common.message import make_message, remove_content_from_message
|
|
53
|
-
from flwr.common.retry_invoker import
|
|
52
|
+
from flwr.common.retry_invoker import make_simple_grpc_retry_invoker, wrap_stub
|
|
54
53
|
from flwr.common.serde import message_to_proto, run_from_proto
|
|
55
54
|
from flwr.common.typing import Run
|
|
56
55
|
from flwr.proto.appio_pb2 import ( # pylint: disable=E0611
|
|
@@ -69,6 +68,7 @@ from flwr.proto.serverappio_pb2 import ( # pylint: disable=E0611
|
|
|
69
68
|
GetNodesResponse,
|
|
70
69
|
)
|
|
71
70
|
from flwr.proto.serverappio_pb2_grpc import ServerAppIoStub # pylint: disable=E0611
|
|
71
|
+
from flwr.supercore.constant import SYSTEM_MESSAGE_TYPE
|
|
72
72
|
|
|
73
73
|
from .grid import Grid
|
|
74
74
|
|
|
@@ -127,7 +127,7 @@ class GrpcGrid(Grid):
|
|
|
127
127
|
self._grpc_stub: ServerAppIoStub | None = None
|
|
128
128
|
self._channel: grpc.Channel | None = None
|
|
129
129
|
self.node = Node(node_id=SUPERLINK_NODE_ID)
|
|
130
|
-
self._retry_invoker =
|
|
130
|
+
self._retry_invoker = make_simple_grpc_retry_invoker()
|
|
131
131
|
super().__init__()
|
|
132
132
|
|
|
133
133
|
@property
|
|
@@ -150,7 +150,7 @@ class GrpcGrid(Grid):
|
|
|
150
150
|
)
|
|
151
151
|
self._channel.subscribe(on_channel_state_change)
|
|
152
152
|
self._grpc_stub = ServerAppIoStub(self._channel)
|
|
153
|
-
|
|
153
|
+
wrap_stub(self._grpc_stub, self._retry_invoker)
|
|
154
154
|
log(DEBUG, "[flwr-serverapp] Connected to %s", self._addr)
|
|
155
155
|
|
|
156
156
|
def _disconnect(self) -> None:
|
|
@@ -341,7 +341,7 @@ class GrpcGrid(Grid):
|
|
|
341
341
|
message_id="",
|
|
342
342
|
src_node_id=self.node.node_id,
|
|
343
343
|
dst_node_id=self.node.node_id,
|
|
344
|
-
message_type=
|
|
344
|
+
message_type=SYSTEM_MESSAGE_TYPE,
|
|
345
345
|
group_id="",
|
|
346
346
|
ttl=0,
|
|
347
347
|
reply_to_message_id=msg_proto.metadata.reply_to_message_id,
|
flwr/server/serverapp/app.py
CHANGED
|
@@ -144,6 +144,10 @@ def run_serverapp( # pylint: disable=R0913, R0914, R0915, R0917, W0212
|
|
|
144
144
|
exit_code = ExitCode.SUCCESS
|
|
145
145
|
|
|
146
146
|
def on_exit() -> None:
|
|
147
|
+
# Set Grpc max retries to 1 to avoid blocking on exit
|
|
148
|
+
if grid:
|
|
149
|
+
grid._retry_invoker.max_tries = 1
|
|
150
|
+
|
|
147
151
|
# Stop heartbeat sender
|
|
148
152
|
if heartbeat_sender and heartbeat_sender.is_running:
|
|
149
153
|
heartbeat_sender.stop()
|
|
@@ -154,10 +158,13 @@ def run_serverapp( # pylint: disable=R0913, R0914, R0915, R0917, W0212
|
|
|
154
158
|
|
|
155
159
|
# Update run status
|
|
156
160
|
if run and run_status and grid:
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
+
try:
|
|
162
|
+
req = UpdateRunStatusRequest(
|
|
163
|
+
run_id=run.run_id, run_status=run_status_to_proto(run_status)
|
|
164
|
+
)
|
|
165
|
+
grid._stub.UpdateRunStatus(req)
|
|
166
|
+
except grpc.RpcError:
|
|
167
|
+
pass
|
|
161
168
|
|
|
162
169
|
# Close the Grpc connection
|
|
163
170
|
if grid:
|
|
@@ -30,7 +30,6 @@ from flwr.common.constant import (
|
|
|
30
30
|
GRPC_ADAPTER_METADATA_MESSAGE_QUALNAME_KEY,
|
|
31
31
|
)
|
|
32
32
|
from flwr.common.logger import log
|
|
33
|
-
from flwr.common.version import package_name, package_version
|
|
34
33
|
from flwr.proto import grpcadapter_pb2_grpc # pylint: disable=E0611
|
|
35
34
|
from flwr.proto.fab_pb2 import GetFabRequest # pylint: disable=E0611
|
|
36
35
|
from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611
|
|
@@ -49,6 +48,7 @@ from flwr.proto.message_pb2 import ( # pylint: disable=E0611
|
|
|
49
48
|
PushObjectRequest,
|
|
50
49
|
)
|
|
51
50
|
from flwr.proto.run_pb2 import GetRunRequest # pylint: disable=E0611
|
|
51
|
+
from flwr.supercore.version import package_name, package_version
|
|
52
52
|
|
|
53
53
|
from ..grpc_rere.fleet_servicer import FleetServicer
|
|
54
54
|
|
|
@@ -108,7 +108,7 @@ class NodeAuthServerInterceptor(grpc.ServerInterceptor): # type: ignore
|
|
|
108
108
|
def _wrap_method_handler(
|
|
109
109
|
self,
|
|
110
110
|
method_handler: grpc.RpcMethodHandler,
|
|
111
|
-
|
|
111
|
+
received_public_key: bytes,
|
|
112
112
|
) -> grpc.RpcMethodHandler:
|
|
113
113
|
def _generic_method_handler(
|
|
114
114
|
request: GrpcMessage,
|
|
@@ -117,21 +117,22 @@ class NodeAuthServerInterceptor(grpc.ServerInterceptor): # type: ignore
|
|
|
117
117
|
# Note: This function runs in a different thread
|
|
118
118
|
# than the `intercept_service` function.
|
|
119
119
|
|
|
120
|
-
#
|
|
121
|
-
if isinstance(request, (RegisterNodeFleetRequest
|
|
122
|
-
|
|
123
|
-
else:
|
|
120
|
+
# Skip registration and activation requests
|
|
121
|
+
if not isinstance(request, (RegisterNodeFleetRequest, ActivateNodeRequest)):
|
|
122
|
+
# Retrieve the node ID from the request
|
|
124
123
|
if hasattr(request, "node"):
|
|
125
|
-
|
|
124
|
+
received_node_id = request.node.node_id
|
|
126
125
|
else:
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
126
|
+
received_node_id = request.node_id # type: ignore[attr-defined]
|
|
127
|
+
|
|
128
|
+
# Get the actual node ID based on the received public key
|
|
129
|
+
node_id = self.state_factory.state().get_node_id_by_public_key(
|
|
130
|
+
received_public_key
|
|
130
131
|
)
|
|
131
132
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
133
|
+
# Verify that the received node ID matches the actual node ID
|
|
134
|
+
if received_node_id != node_id:
|
|
135
|
+
context.abort(grpc.StatusCode.UNAUTHENTICATED, "Invalid node ID")
|
|
135
136
|
|
|
136
137
|
response: GrpcMessage = method_handler.unary_unary(request, context)
|
|
137
138
|
return response
|
|
@@ -127,7 +127,7 @@ def send_node_heartbeat(
|
|
|
127
127
|
return SendNodeHeartbeatResponse(success=res)
|
|
128
128
|
|
|
129
129
|
|
|
130
|
-
def pull_messages(
|
|
130
|
+
def pull_messages( # pylint: disable=too-many-locals
|
|
131
131
|
request: PullMessagesRequest,
|
|
132
132
|
state: LinkState,
|
|
133
133
|
store: ObjectStore,
|
|
@@ -143,6 +143,8 @@ def pull_messages(
|
|
|
143
143
|
# Convert to Messages
|
|
144
144
|
msg_proto = []
|
|
145
145
|
trees = []
|
|
146
|
+
run_id_to_record: int | None = None
|
|
147
|
+
|
|
146
148
|
for msg in message_list:
|
|
147
149
|
try:
|
|
148
150
|
# Retrieve Message object tree from ObjectStore
|
|
@@ -152,12 +154,30 @@ def pull_messages(
|
|
|
152
154
|
# Add Message and its object tree to the response
|
|
153
155
|
msg_proto.append(message_to_proto(msg))
|
|
154
156
|
trees.append(obj_tree)
|
|
157
|
+
|
|
158
|
+
# Track run_id for traffic recording
|
|
159
|
+
run_id_to_record = msg.metadata.run_id
|
|
160
|
+
|
|
155
161
|
except NoObjectInStoreError as e:
|
|
156
162
|
log(ERROR, e.message)
|
|
157
163
|
# Delete message ins from state
|
|
158
164
|
state.delete_messages(message_ins_ids={msg_object_id})
|
|
159
165
|
|
|
160
|
-
|
|
166
|
+
response = PullMessagesResponse(messages_list=msg_proto, message_object_trees=trees)
|
|
167
|
+
|
|
168
|
+
# Record incoming traffic size
|
|
169
|
+
bytes_recv = request.ByteSize()
|
|
170
|
+
|
|
171
|
+
# Record traffic only if message was successfully processed
|
|
172
|
+
# All messages in this request are assumed to belong to the same run
|
|
173
|
+
if run_id_to_record is not None:
|
|
174
|
+
# Record outgoing traffic size
|
|
175
|
+
bytes_sent = response.ByteSize()
|
|
176
|
+
state.store_traffic(
|
|
177
|
+
run_id_to_record, bytes_sent=bytes_sent, bytes_recv=bytes_recv
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
return response
|
|
161
181
|
|
|
162
182
|
|
|
163
183
|
def push_messages(
|
|
@@ -170,6 +190,9 @@ def push_messages(
|
|
|
170
190
|
msg = message_from_proto(message_proto=request.messages_list[0])
|
|
171
191
|
run_id = msg.metadata.run_id
|
|
172
192
|
|
|
193
|
+
# Record incoming traffic size
|
|
194
|
+
bytes_recv = request.ByteSize()
|
|
195
|
+
|
|
173
196
|
# Abort if the run is not running
|
|
174
197
|
abort_msg = check_abort(
|
|
175
198
|
run_id,
|
|
@@ -193,6 +216,16 @@ def push_messages(
|
|
|
193
216
|
results={str(message_id): 0},
|
|
194
217
|
objects_to_push=objects_to_push,
|
|
195
218
|
)
|
|
219
|
+
|
|
220
|
+
# Record outgoing traffic size
|
|
221
|
+
bytes_sent = response.ByteSize()
|
|
222
|
+
|
|
223
|
+
# Record traffic only if message was successfully processed
|
|
224
|
+
# Only one message is processed per request
|
|
225
|
+
state.store_traffic(run_id, bytes_sent=bytes_sent, bytes_recv=bytes_recv)
|
|
226
|
+
if request.clientapp_runtime_list:
|
|
227
|
+
state.add_clientapp_runtime(run_id, request.clientapp_runtime_list[0])
|
|
228
|
+
|
|
196
229
|
return response
|
|
197
230
|
|
|
198
231
|
|
|
@@ -257,6 +290,10 @@ def push_object(
|
|
|
257
290
|
try:
|
|
258
291
|
store.put(request.object_id, request.object_content)
|
|
259
292
|
stored = True
|
|
293
|
+
# Record bytes traffic pushed from SuperNode
|
|
294
|
+
state.store_traffic(
|
|
295
|
+
request.run_id, bytes_sent=0, bytes_recv=len(request.object_content)
|
|
296
|
+
)
|
|
260
297
|
except (NoObjectInStoreError, ValueError) as e:
|
|
261
298
|
log(ERROR, str(e))
|
|
262
299
|
except UnexpectedObjectContentError as e:
|
|
@@ -283,6 +320,9 @@ def pull_object(
|
|
|
283
320
|
content = store.get(request.object_id)
|
|
284
321
|
if content is not None:
|
|
285
322
|
object_available = content != b""
|
|
323
|
+
# Record bytes traffic pulled by SuperNode
|
|
324
|
+
if object_available:
|
|
325
|
+
state.store_traffic(request.run_id, bytes_sent=len(content), bytes_recv=0)
|
|
286
326
|
return PullObjectResponse(
|
|
287
327
|
object_found=True,
|
|
288
328
|
object_available=object_available,
|
|
@@ -18,11 +18,11 @@
|
|
|
18
18
|
from .in_memory_linkstate import InMemoryLinkState as InMemoryLinkState
|
|
19
19
|
from .linkstate import LinkState as LinkState
|
|
20
20
|
from .linkstate_factory import LinkStateFactory as LinkStateFactory
|
|
21
|
-
from .
|
|
21
|
+
from .sql_linkstate import SqlLinkState as SqlLinkState
|
|
22
22
|
|
|
23
23
|
__all__ = [
|
|
24
24
|
"InMemoryLinkState",
|
|
25
25
|
"LinkState",
|
|
26
26
|
"LinkStateFactory",
|
|
27
|
-
"
|
|
27
|
+
"SqlLinkState",
|
|
28
28
|
]
|
|
@@ -23,6 +23,7 @@ from dataclasses import dataclass, field
|
|
|
23
23
|
from datetime import datetime, timezone
|
|
24
24
|
from logging import ERROR, WARNING
|
|
25
25
|
|
|
26
|
+
from flwr.app.user_config import UserConfig
|
|
26
27
|
from flwr.common import Context, Message, log, now
|
|
27
28
|
from flwr.common.constant import (
|
|
28
29
|
HEARTBEAT_PATIENCE,
|
|
@@ -35,7 +36,7 @@ from flwr.common.constant import (
|
|
|
35
36
|
SubStatus,
|
|
36
37
|
)
|
|
37
38
|
from flwr.common.record import ConfigRecord
|
|
38
|
-
from flwr.common.typing import Run, RunStatus
|
|
39
|
+
from flwr.common.typing import Run, RunStatus
|
|
39
40
|
from flwr.proto.node_pb2 import NodeInfo # pylint: disable=E0611
|
|
40
41
|
from flwr.server.superlink.linkstate.linkstate import LinkState
|
|
41
42
|
from flwr.server.utils import validate_message
|
|
@@ -518,15 +519,6 @@ class InMemoryLinkState(LinkState, InMemoryCoreState): # pylint: disable=R0902,
|
|
|
518
519
|
node.online_until, tz=timezone.utc
|
|
519
520
|
).isoformat()
|
|
520
521
|
|
|
521
|
-
def get_node_public_key(self, node_id: int) -> bytes:
|
|
522
|
-
"""Get `public_key` for the specified `node_id`."""
|
|
523
|
-
with self.lock:
|
|
524
|
-
if (
|
|
525
|
-
node := self.nodes.get(node_id)
|
|
526
|
-
) is None or node.status == NodeStatus.UNREGISTERED:
|
|
527
|
-
raise ValueError(f"Node ID {node_id} not found")
|
|
528
|
-
return node.public_key
|
|
529
|
-
|
|
530
522
|
def get_node_id_by_public_key(self, public_key: bytes) -> int | None:
|
|
531
523
|
"""Get `node_id` for the specified `public_key` if it exists and is not
|
|
532
524
|
deleted."""
|
|
@@ -576,6 +568,9 @@ class InMemoryLinkState(LinkState, InMemoryCoreState): # pylint: disable=R0902,
|
|
|
576
568
|
),
|
|
577
569
|
flwr_aid=flwr_aid if flwr_aid else "",
|
|
578
570
|
federation=federation,
|
|
571
|
+
bytes_sent=0,
|
|
572
|
+
bytes_recv=0,
|
|
573
|
+
clientapp_runtime=0.0,
|
|
579
574
|
),
|
|
580
575
|
)
|
|
581
576
|
self.run_ids[run_id] = run_record
|
|
@@ -771,3 +766,34 @@ class InMemoryLinkState(LinkState, InMemoryCoreState): # pylint: disable=R0902,
|
|
|
771
766
|
index = bisect_right(run.logs, (after_timestamp, ""))
|
|
772
767
|
latest_timestamp = run.logs[-1][0] if index < len(run.logs) else 0.0
|
|
773
768
|
return "".join(log for _, log in run.logs[index:]), latest_timestamp
|
|
769
|
+
|
|
770
|
+
def store_traffic(self, run_id: int, *, bytes_sent: int, bytes_recv: int) -> None:
|
|
771
|
+
"""Store traffic data for the specified `run_id`."""
|
|
772
|
+
# Validate non-negative values
|
|
773
|
+
if bytes_sent < 0 or bytes_recv < 0:
|
|
774
|
+
raise ValueError(
|
|
775
|
+
f"Negative traffic values for run {run_id}: "
|
|
776
|
+
f"bytes_sent={bytes_sent}, bytes_recv={bytes_recv}"
|
|
777
|
+
)
|
|
778
|
+
|
|
779
|
+
if bytes_sent == 0 and bytes_recv == 0:
|
|
780
|
+
raise ValueError(
|
|
781
|
+
f"Both bytes_sent and bytes_recv cannot be zero for run {run_id}"
|
|
782
|
+
)
|
|
783
|
+
|
|
784
|
+
with self.lock:
|
|
785
|
+
if run_id not in self.run_ids:
|
|
786
|
+
raise ValueError(f"Run {run_id} not found")
|
|
787
|
+
run_record = self.run_ids[run_id]
|
|
788
|
+
|
|
789
|
+
with run_record.lock:
|
|
790
|
+
run = run_record.run
|
|
791
|
+
run.bytes_sent += bytes_sent
|
|
792
|
+
run.bytes_recv += bytes_recv
|
|
793
|
+
|
|
794
|
+
def add_clientapp_runtime(self, run_id: int, runtime: float) -> None:
|
|
795
|
+
"""Add ClientApp runtime to the cumulative total for the specified `run_id`."""
|
|
796
|
+
with self.lock:
|
|
797
|
+
if run_id not in self.run_ids:
|
|
798
|
+
raise ValueError(f"Run {run_id} not found")
|
|
799
|
+
self.run_ids[run_id].run.clientapp_runtime += runtime
|
|
@@ -18,9 +18,10 @@
|
|
|
18
18
|
import abc
|
|
19
19
|
from collections.abc import Sequence
|
|
20
20
|
|
|
21
|
+
from flwr.app.user_config import UserConfig
|
|
21
22
|
from flwr.common import Context, Message
|
|
22
23
|
from flwr.common.record import ConfigRecord
|
|
23
|
-
from flwr.common.typing import Run, RunStatus
|
|
24
|
+
from flwr.common.typing import Run, RunStatus
|
|
24
25
|
from flwr.proto.node_pb2 import NodeInfo # pylint: disable=E0611
|
|
25
26
|
from flwr.supercore.corestate import CoreState
|
|
26
27
|
from flwr.superlink.federation import FederationManager
|
|
@@ -245,26 +246,6 @@ class LinkState(CoreState): # pylint: disable=R0904
|
|
|
245
246
|
the specified filters.
|
|
246
247
|
"""
|
|
247
248
|
|
|
248
|
-
@abc.abstractmethod
|
|
249
|
-
def get_node_public_key(self, node_id: int) -> bytes:
|
|
250
|
-
"""Get `public_key` for the specified `node_id`.
|
|
251
|
-
|
|
252
|
-
Parameters
|
|
253
|
-
----------
|
|
254
|
-
node_id : int
|
|
255
|
-
The identifier of the node whose public key is to be retrieved.
|
|
256
|
-
|
|
257
|
-
Returns
|
|
258
|
-
-------
|
|
259
|
-
bytes
|
|
260
|
-
The public key associated with the specified `node_id`.
|
|
261
|
-
|
|
262
|
-
Raises
|
|
263
|
-
------
|
|
264
|
-
ValueError
|
|
265
|
-
If the specified `node_id` does not exist in the link state.
|
|
266
|
-
"""
|
|
267
|
-
|
|
268
249
|
@abc.abstractmethod
|
|
269
250
|
def create_run( # pylint: disable=too-many-arguments,too-many-positional-arguments
|
|
270
251
|
self,
|
|
@@ -480,3 +461,35 @@ class LinkState(CoreState): # pylint: disable=R0904
|
|
|
480
461
|
- The timestamp of the latest log entry in the returned logs.
|
|
481
462
|
Returns `0` if no logs are returned.
|
|
482
463
|
"""
|
|
464
|
+
|
|
465
|
+
@abc.abstractmethod
|
|
466
|
+
def store_traffic(self, run_id: int, *, bytes_sent: int, bytes_recv: int) -> None:
|
|
467
|
+
"""Store traffic data for the specified `run_id`.
|
|
468
|
+
|
|
469
|
+
Parameters
|
|
470
|
+
----------
|
|
471
|
+
run_id : int
|
|
472
|
+
The identifier of the run for which to store traffic data.
|
|
473
|
+
bytes_sent : int
|
|
474
|
+
The number of bytes pulled by SuperNodes from the SuperLink to add to the
|
|
475
|
+
run's total.
|
|
476
|
+
bytes_recv : int
|
|
477
|
+
The number of bytes received by SuperLink from SuperNodes to add to the
|
|
478
|
+
run's total.
|
|
479
|
+
"""
|
|
480
|
+
|
|
481
|
+
@abc.abstractmethod
|
|
482
|
+
def add_clientapp_runtime(self, run_id: int, runtime: float) -> None:
|
|
483
|
+
"""Add ClientApp runtime to the cumulative total for the specified `run_id`.
|
|
484
|
+
|
|
485
|
+
This method accumulates the runtime by adding the provided value to the
|
|
486
|
+
existing total runtime for the run. Multiple ClientApps can contribute
|
|
487
|
+
to the same run's total runtime.
|
|
488
|
+
|
|
489
|
+
Parameters
|
|
490
|
+
----------
|
|
491
|
+
run_id : int
|
|
492
|
+
The identifier of the run for which to store each ClientApp's runtime.
|
|
493
|
+
runtime : float
|
|
494
|
+
The runtime in seconds to add to the `run_id`'s cumulative total.
|
|
495
|
+
"""
|
|
@@ -24,7 +24,7 @@ from flwr.superlink.federation import FederationManager
|
|
|
24
24
|
|
|
25
25
|
from .in_memory_linkstate import InMemoryLinkState
|
|
26
26
|
from .linkstate import LinkState
|
|
27
|
-
from .
|
|
27
|
+
from .sql_linkstate import SqlLinkState
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
class LinkStateFactory:
|
|
@@ -56,20 +56,28 @@ class LinkStateFactory:
|
|
|
56
56
|
|
|
57
57
|
def state(self) -> LinkState:
|
|
58
58
|
"""Return a State instance and create it, if necessary."""
|
|
59
|
+
# Return cached state if it exists
|
|
60
|
+
if self.state_instance is not None:
|
|
61
|
+
if self.database == FLWR_IN_MEMORY_DB_NAME:
|
|
62
|
+
log(DEBUG, "Using InMemoryState")
|
|
63
|
+
else:
|
|
64
|
+
log(DEBUG, "Using SqlLinkState")
|
|
65
|
+
return self.state_instance
|
|
66
|
+
|
|
59
67
|
# Get the ObjectStore instance
|
|
60
68
|
object_store = self.objectstore_factory.store()
|
|
61
69
|
|
|
62
70
|
# InMemoryState
|
|
63
71
|
if self.database == FLWR_IN_MEMORY_DB_NAME:
|
|
64
|
-
|
|
65
|
-
self.
|
|
66
|
-
|
|
67
|
-
)
|
|
72
|
+
self.state_instance = InMemoryLinkState(
|
|
73
|
+
self.federation_manager, object_store
|
|
74
|
+
)
|
|
68
75
|
log(DEBUG, "Using InMemoryState")
|
|
69
76
|
return self.state_instance
|
|
70
77
|
|
|
71
|
-
#
|
|
72
|
-
state =
|
|
78
|
+
# SqlLinkState
|
|
79
|
+
state = SqlLinkState(self.database, self.federation_manager, object_store)
|
|
73
80
|
state.initialize()
|
|
74
|
-
|
|
81
|
+
self.state_instance = state
|
|
82
|
+
log(DEBUG, "Using SqlLinkState")
|
|
75
83
|
return state
|