diff --git a/benchmarking/locust/common/ateapi_pb2.py b/benchmarking/locust/common/ateapi_pb2.py index cc000a075..c055c7036 100644 --- a/benchmarking/locust/common/ateapi_pb2.py +++ b/benchmarking/locust/common/ateapi_pb2.py @@ -16,7 +16,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: ateapi.proto -# Protobuf Python Version: 6.31.1 +# Protobuf Python Version: 6.33.5 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -26,8 +26,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 31, - 1, + 33, + 5, '', 'ateapi.proto' ) @@ -38,7 +38,7 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0c\x61teapi.proto\x12\x06\x61teapi\"\x87\x03\n\x05\x41\x63tor\x12\x10\n\x08\x61\x63tor_id\x18\x01 \x01(\t\x12\x0f\n\x07version\x18\x02 \x01(\x03\x12 \n\x18\x61\x63tor_template_namespace\x18\x03 \x01(\t\x12\x1b\n\x13\x61\x63tor_template_name\x18\x04 \x01(\t\x12$\n\x06status\x18\x05 \x01(\x0e\x32\x14.ateapi.Actor.Status\x12\x1b\n\x13\x61teom_pod_namespace\x18\x06 \x01(\t\x12\x16\n\x0e\x61teom_pod_name\x18\x07 \x01(\t\x12\x14\n\x0c\x61teom_pod_ip\x18\x08 \x01(\t\x12\x15\n\rlast_snapshot\x18\t \x01(\t\x12\x1c\n\x14in_progress_snapshot\x18\n \x01(\t\"v\n\x06Status\x12\x16\n\x12STATUS_UNSPECIFIED\x10\x00\x12\x13\n\x0fSTATUS_RESUMING\x10\x01\x12\x12\n\x0eSTATUS_RUNNING\x10\x02\x12\x15\n\x11STATUS_SUSPENDING\x10\x03\x12\x14\n\x10STATUS_SUSPENDED\x10\x04\"#\n\x0fGetActorRequest\x12\x10\n\x08\x61\x63tor_id\x18\x01 \x01(\t\"0\n\x10GetActorResponse\x12\x1c\n\x05\x61\x63tor\x18\x01 \x01(\x0b\x32\r.ateapi.Actor\"e\n\x12\x43reateActorRequest\x12\x10\n\x08\x61\x63tor_id\x18\x01 \x01(\t\x12 \n\x18\x61\x63tor_template_namespace\x18\x02 \x01(\t\x12\x1b\n\x13\x61\x63tor_template_name\x18\x03 \x01(\t\"3\n\x13\x43reateActorResponse\x12\x1c\n\x05\x61\x63tor\x18\x01 \x01(\x0b\x32\r.ateapi.Actor\"\'\n\x13SuspendActorRequest\x12\x10\n\x08\x61\x63tor_id\x18\x01 \x01(\t\"4\n\x14SuspendActorResponse\x12\x1c\n\x05\x61\x63tor\x18\x01 \x01(\x0b\x32\r.ateapi.Actor\"4\n\x12ResumeActorRequest\x12\x10\n\x08\x61\x63tor_id\x18\x01 \x01(\t\x12\x0c\n\x04\x62oot\x18\x02 \x01(\x08\"3\n\x13ResumeActorResponse\x12\x1c\n\x05\x61\x63tor\x18\x01 \x01(\x0b\x32\r.ateapi.Actor\"&\n\x12\x44\x65leteActorRequest\x12\x10\n\x08\x61\x63tor_id\x18\x01 \x01(\t\"\x15\n\x13\x44\x65leteActorResponse\"\x14\n\x12ListWorkersRequest\"6\n\x13ListWorkersResponse\x12\x1f\n\x07workers\x18\x01 \x03(\x0b\x32\x0e.ateapi.Worker\"\x13\n\x11ListActorsRequest\"3\n\x12ListActorsResponse\x12\x1d\n\x06\x61\x63tors\x18\x01 \x03(\x0b\x32\r.ateapi.Actor\"\xab\x01\n\x06Worker\x12\x18\n\x10worker_namespace\x18\x01 \x01(\t\x12\x13\n\x0bworker_pool\x18\x02 \x01(\t\x12\x12\n\nworker_pod\x18\x03 \x01(\t\x12\x17\n\x0f\x61\x63tor_namespace\x18\x04 \x01(\t\x12\x16\n\x0e\x61\x63tor_template\x18\x05 \x01(\t\x12\x10\n\x08\x61\x63tor_id\x18\x06 \x01(\t\x12\n\n\x02ip\x18\x07 \x01(\t\x12\x0f\n\x07version\x18\x08 \x01(\x03\"\x13\n\x11\x44\x65\x62ugClearRequest\"\x14\n\x12\x44\x65\x62ugClearResponse\"W\n\x0eMintJWTRequest\x12\x10\n\x08\x61udience\x18\x01 \x03(\t\x12\x0e\n\x06\x61pp_id\x18\x02 \x01(\t\x12\x0f\n\x07user_id\x18\x03 \x01(\t\x12\x12\n\nsession_id\x18\x04 \x01(\t\"&\n\x0fMintJWTResponse\x12\x13\n\x0bsession_jwt\x18\x01 \x01(\t\"k\n\x0fMintCertRequest\x12\x0e\n\x06\x61pp_id\x18\x01 \x01(\t\x12\x0f\n\x07user_id\x18\x02 \x01(\t\x12\x12\n\nsession_id\x18\x03 \x01(\t\x12#\n\x1b\x63\x65rtificate_signing_request\x18\x04 \x01(\x0c\"0\n\x10MintCertResponse\x12\x1c\n\x14session_certificates\x18\x01 \x03(\x0c\x32\xcd\x04\n\x07\x43ontrol\x12?\n\x08GetActor\x12\x17.ateapi.GetActorRequest\x1a\x18.ateapi.GetActorResponse\"\x00\x12H\n\x0b\x43reateActor\x12\x1a.ateapi.CreateActorRequest\x1a\x1b.ateapi.CreateActorResponse\"\x00\x12K\n\x0cSuspendActor\x12\x1b.ateapi.SuspendActorRequest\x1a\x1c.ateapi.SuspendActorResponse\"\x00\x12H\n\x0bResumeActor\x12\x1a.ateapi.ResumeActorRequest\x1a\x1b.ateapi.ResumeActorResponse\"\x00\x12H\n\x0b\x44\x65leteActor\x12\x1a.ateapi.DeleteActorRequest\x1a\x1b.ateapi.DeleteActorResponse\"\x00\x12H\n\x0bListWorkers\x12\x1a.ateapi.ListWorkersRequest\x1a\x1b.ateapi.ListWorkersResponse\"\x00\x12\x45\n\nListActors\x12\x19.ateapi.ListActorsRequest\x1a\x1a.ateapi.ListActorsResponse\"\x00\x12\x45\n\nDebugClear\x12\x19.ateapi.DebugClearRequest\x1a\x1a.ateapi.DebugClearResponse\"\x00\x32\x8c\x01\n\x0fSessionIdentity\x12:\n\x07MintJWT\x12\x16.ateapi.MintJWTRequest\x1a\x17.ateapi.MintJWTResponse\x12=\n\x08MintCert\x12\x17.ateapi.MintCertRequest\x1a\x18.ateapi.MintCertResponseB9Z7github.com/agent-substrate/substrate/pkg/proto/ateapipbb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0c\x61teapi.proto\x12\x06\x61teapi\"\x9e\x03\n\x05\x41\x63tor\x12\x10\n\x08\x61\x63tor_id\x18\x01 \x01(\t\x12\x0f\n\x07version\x18\x02 \x01(\x03\x12 \n\x18\x61\x63tor_template_namespace\x18\x03 \x01(\t\x12\x1b\n\x13\x61\x63tor_template_name\x18\x04 \x01(\t\x12$\n\x06status\x18\x05 \x01(\x0e\x32\x14.ateapi.Actor.Status\x12\x1b\n\x13\x61teom_pod_namespace\x18\x06 \x01(\t\x12\x16\n\x0e\x61teom_pod_name\x18\x07 \x01(\t\x12\x14\n\x0c\x61teom_pod_ip\x18\x08 \x01(\t\x12\x15\n\rlast_snapshot\x18\t \x01(\t\x12\x1c\n\x14in_progress_snapshot\x18\n \x01(\t\x12\x15\n\rateom_pod_uid\x18\x0b \x01(\t\"v\n\x06Status\x12\x16\n\x12STATUS_UNSPECIFIED\x10\x00\x12\x13\n\x0fSTATUS_RESUMING\x10\x01\x12\x12\n\x0eSTATUS_RUNNING\x10\x02\x12\x15\n\x11STATUS_SUSPENDING\x10\x03\x12\x14\n\x10STATUS_SUSPENDED\x10\x04\"#\n\x0fGetActorRequest\x12\x10\n\x08\x61\x63tor_id\x18\x01 \x01(\t\"0\n\x10GetActorResponse\x12\x1c\n\x05\x61\x63tor\x18\x01 \x01(\x0b\x32\r.ateapi.Actor\"e\n\x12\x43reateActorRequest\x12\x10\n\x08\x61\x63tor_id\x18\x01 \x01(\t\x12 \n\x18\x61\x63tor_template_namespace\x18\x02 \x01(\t\x12\x1b\n\x13\x61\x63tor_template_name\x18\x03 \x01(\t\"3\n\x13\x43reateActorResponse\x12\x1c\n\x05\x61\x63tor\x18\x01 \x01(\x0b\x32\r.ateapi.Actor\"\'\n\x13SuspendActorRequest\x12\x10\n\x08\x61\x63tor_id\x18\x01 \x01(\t\"4\n\x14SuspendActorResponse\x12\x1c\n\x05\x61\x63tor\x18\x01 \x01(\x0b\x32\r.ateapi.Actor\"4\n\x12ResumeActorRequest\x12\x10\n\x08\x61\x63tor_id\x18\x01 \x01(\t\x12\x0c\n\x04\x62oot\x18\x02 \x01(\x08\"3\n\x13ResumeActorResponse\x12\x1c\n\x05\x61\x63tor\x18\x01 \x01(\x0b\x32\r.ateapi.Actor\"&\n\x12\x44\x65leteActorRequest\x12\x10\n\x08\x61\x63tor_id\x18\x01 \x01(\t\"\x15\n\x13\x44\x65leteActorResponse\"\x14\n\x12ListWorkersRequest\"6\n\x13ListWorkersResponse\x12\x1f\n\x07workers\x18\x01 \x03(\x0b\x32\x0e.ateapi.Worker\":\n\x11ListActorsRequest\x12\x11\n\tpage_size\x18\x01 \x01(\x05\x12\x12\n\npage_token\x18\x02 \x01(\t\"L\n\x12ListActorsResponse\x12\x1d\n\x06\x61\x63tors\x18\x01 \x03(\x0b\x32\r.ateapi.Actor\x12\x17\n\x0fnext_page_token\x18\x02 \x01(\t\"\xc3\x01\n\x06Worker\x12\x18\n\x10worker_namespace\x18\x01 \x01(\t\x12\x13\n\x0bworker_pool\x18\x02 \x01(\t\x12\x12\n\nworker_pod\x18\x03 \x01(\t\x12\x17\n\x0f\x61\x63tor_namespace\x18\x04 \x01(\t\x12\x16\n\x0e\x61\x63tor_template\x18\x05 \x01(\t\x12\x10\n\x08\x61\x63tor_id\x18\x06 \x01(\t\x12\n\n\x02ip\x18\x07 \x01(\t\x12\x0f\n\x07version\x18\x08 \x01(\x03\x12\x16\n\x0eworker_pod_uid\x18\t \x01(\t\"\x13\n\x11\x44\x65\x62ugClearRequest\"\x14\n\x12\x44\x65\x62ugClearResponse\"W\n\x0eMintJWTRequest\x12\x10\n\x08\x61udience\x18\x01 \x03(\t\x12\x0e\n\x06\x61pp_id\x18\x02 \x01(\t\x12\x0f\n\x07user_id\x18\x03 \x01(\t\x12\x12\n\nsession_id\x18\x04 \x01(\t\"&\n\x0fMintJWTResponse\x12\x13\n\x0bsession_jwt\x18\x01 \x01(\t\"k\n\x0fMintCertRequest\x12\x0e\n\x06\x61pp_id\x18\x01 \x01(\t\x12\x0f\n\x07user_id\x18\x02 \x01(\t\x12\x12\n\nsession_id\x18\x03 \x01(\t\x12#\n\x1b\x63\x65rtificate_signing_request\x18\x04 \x01(\x0c\"0\n\x10MintCertResponse\x12\x1c\n\x14session_certificates\x18\x01 \x03(\x0c\x32\xcd\x04\n\x07\x43ontrol\x12?\n\x08GetActor\x12\x17.ateapi.GetActorRequest\x1a\x18.ateapi.GetActorResponse\"\x00\x12H\n\x0b\x43reateActor\x12\x1a.ateapi.CreateActorRequest\x1a\x1b.ateapi.CreateActorResponse\"\x00\x12K\n\x0cSuspendActor\x12\x1b.ateapi.SuspendActorRequest\x1a\x1c.ateapi.SuspendActorResponse\"\x00\x12H\n\x0bResumeActor\x12\x1a.ateapi.ResumeActorRequest\x1a\x1b.ateapi.ResumeActorResponse\"\x00\x12H\n\x0b\x44\x65leteActor\x12\x1a.ateapi.DeleteActorRequest\x1a\x1b.ateapi.DeleteActorResponse\"\x00\x12H\n\x0bListWorkers\x12\x1a.ateapi.ListWorkersRequest\x1a\x1b.ateapi.ListWorkersResponse\"\x00\x12\x45\n\nListActors\x12\x19.ateapi.ListActorsRequest\x1a\x1a.ateapi.ListActorsResponse\"\x00\x12\x45\n\nDebugClear\x12\x19.ateapi.DebugClearRequest\x1a\x1a.ateapi.DebugClearResponse\"\x00\x32\x8c\x01\n\x0fSessionIdentity\x12:\n\x07MintJWT\x12\x16.ateapi.MintJWTRequest\x1a\x17.ateapi.MintJWTResponse\x12=\n\x08MintCert\x12\x17.ateapi.MintCertRequest\x1a\x18.ateapi.MintCertResponseB9Z7github.com/agent-substrate/substrate/pkg/proto/ateapipbb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -47,53 +47,53 @@ _globals['DESCRIPTOR']._loaded_options = None _globals['DESCRIPTOR']._serialized_options = b'Z7github.com/agent-substrate/substrate/pkg/proto/ateapipb' _globals['_ACTOR']._serialized_start=25 - _globals['_ACTOR']._serialized_end=416 - _globals['_ACTOR_STATUS']._serialized_start=298 - _globals['_ACTOR_STATUS']._serialized_end=416 - _globals['_GETACTORREQUEST']._serialized_start=418 - _globals['_GETACTORREQUEST']._serialized_end=453 - _globals['_GETACTORRESPONSE']._serialized_start=455 - _globals['_GETACTORRESPONSE']._serialized_end=503 - _globals['_CREATEACTORREQUEST']._serialized_start=505 - _globals['_CREATEACTORREQUEST']._serialized_end=606 - _globals['_CREATEACTORRESPONSE']._serialized_start=608 - _globals['_CREATEACTORRESPONSE']._serialized_end=659 - _globals['_SUSPENDACTORREQUEST']._serialized_start=661 - _globals['_SUSPENDACTORREQUEST']._serialized_end=700 - _globals['_SUSPENDACTORRESPONSE']._serialized_start=702 - _globals['_SUSPENDACTORRESPONSE']._serialized_end=754 - _globals['_RESUMEACTORREQUEST']._serialized_start=756 - _globals['_RESUMEACTORREQUEST']._serialized_end=808 - _globals['_RESUMEACTORRESPONSE']._serialized_start=810 - _globals['_RESUMEACTORRESPONSE']._serialized_end=861 - _globals['_DELETEACTORREQUEST']._serialized_start=863 - _globals['_DELETEACTORREQUEST']._serialized_end=901 - _globals['_DELETEACTORRESPONSE']._serialized_start=903 - _globals['_DELETEACTORRESPONSE']._serialized_end=924 - _globals['_LISTWORKERSREQUEST']._serialized_start=926 - _globals['_LISTWORKERSREQUEST']._serialized_end=946 - _globals['_LISTWORKERSRESPONSE']._serialized_start=948 - _globals['_LISTWORKERSRESPONSE']._serialized_end=1002 - _globals['_LISTACTORSREQUEST']._serialized_start=1004 - _globals['_LISTACTORSREQUEST']._serialized_end=1023 - _globals['_LISTACTORSRESPONSE']._serialized_start=1025 - _globals['_LISTACTORSRESPONSE']._serialized_end=1076 - _globals['_WORKER']._serialized_start=1079 - _globals['_WORKER']._serialized_end=1250 - _globals['_DEBUGCLEARREQUEST']._serialized_start=1252 - _globals['_DEBUGCLEARREQUEST']._serialized_end=1271 - _globals['_DEBUGCLEARRESPONSE']._serialized_start=1273 - _globals['_DEBUGCLEARRESPONSE']._serialized_end=1293 - _globals['_MINTJWTREQUEST']._serialized_start=1295 - _globals['_MINTJWTREQUEST']._serialized_end=1382 - _globals['_MINTJWTRESPONSE']._serialized_start=1384 - _globals['_MINTJWTRESPONSE']._serialized_end=1422 - _globals['_MINTCERTREQUEST']._serialized_start=1424 - _globals['_MINTCERTREQUEST']._serialized_end=1531 - _globals['_MINTCERTRESPONSE']._serialized_start=1533 - _globals['_MINTCERTRESPONSE']._serialized_end=1581 - _globals['_CONTROL']._serialized_start=1584 - _globals['_CONTROL']._serialized_end=2173 - _globals['_SESSIONIDENTITY']._serialized_start=2176 - _globals['_SESSIONIDENTITY']._serialized_end=2316 + _globals['_ACTOR']._serialized_end=439 + _globals['_ACTOR_STATUS']._serialized_start=321 + _globals['_ACTOR_STATUS']._serialized_end=439 + _globals['_GETACTORREQUEST']._serialized_start=441 + _globals['_GETACTORREQUEST']._serialized_end=476 + _globals['_GETACTORRESPONSE']._serialized_start=478 + _globals['_GETACTORRESPONSE']._serialized_end=526 + _globals['_CREATEACTORREQUEST']._serialized_start=528 + _globals['_CREATEACTORREQUEST']._serialized_end=629 + _globals['_CREATEACTORRESPONSE']._serialized_start=631 + _globals['_CREATEACTORRESPONSE']._serialized_end=682 + _globals['_SUSPENDACTORREQUEST']._serialized_start=684 + _globals['_SUSPENDACTORREQUEST']._serialized_end=723 + _globals['_SUSPENDACTORRESPONSE']._serialized_start=725 + _globals['_SUSPENDACTORRESPONSE']._serialized_end=777 + _globals['_RESUMEACTORREQUEST']._serialized_start=779 + _globals['_RESUMEACTORREQUEST']._serialized_end=831 + _globals['_RESUMEACTORRESPONSE']._serialized_start=833 + _globals['_RESUMEACTORRESPONSE']._serialized_end=884 + _globals['_DELETEACTORREQUEST']._serialized_start=886 + _globals['_DELETEACTORREQUEST']._serialized_end=924 + _globals['_DELETEACTORRESPONSE']._serialized_start=926 + _globals['_DELETEACTORRESPONSE']._serialized_end=947 + _globals['_LISTWORKERSREQUEST']._serialized_start=949 + _globals['_LISTWORKERSREQUEST']._serialized_end=969 + _globals['_LISTWORKERSRESPONSE']._serialized_start=971 + _globals['_LISTWORKERSRESPONSE']._serialized_end=1025 + _globals['_LISTACTORSREQUEST']._serialized_start=1027 + _globals['_LISTACTORSREQUEST']._serialized_end=1085 + _globals['_LISTACTORSRESPONSE']._serialized_start=1087 + _globals['_LISTACTORSRESPONSE']._serialized_end=1163 + _globals['_WORKER']._serialized_start=1166 + _globals['_WORKER']._serialized_end=1361 + _globals['_DEBUGCLEARREQUEST']._serialized_start=1363 + _globals['_DEBUGCLEARREQUEST']._serialized_end=1382 + _globals['_DEBUGCLEARRESPONSE']._serialized_start=1384 + _globals['_DEBUGCLEARRESPONSE']._serialized_end=1404 + _globals['_MINTJWTREQUEST']._serialized_start=1406 + _globals['_MINTJWTREQUEST']._serialized_end=1493 + _globals['_MINTJWTRESPONSE']._serialized_start=1495 + _globals['_MINTJWTRESPONSE']._serialized_end=1533 + _globals['_MINTCERTREQUEST']._serialized_start=1535 + _globals['_MINTCERTREQUEST']._serialized_end=1642 + _globals['_MINTCERTRESPONSE']._serialized_start=1644 + _globals['_MINTCERTRESPONSE']._serialized_end=1692 + _globals['_CONTROL']._serialized_start=1695 + _globals['_CONTROL']._serialized_end=2284 + _globals['_SESSIONIDENTITY']._serialized_start=2287 + _globals['_SESSIONIDENTITY']._serialized_end=2427 # @@protoc_insertion_point(module_scope) diff --git a/benchmarking/locust/common/ateapi_pb2_grpc.py b/benchmarking/locust/common/ateapi_pb2_grpc.py index 5b4b3db01..edab9795c 100644 --- a/benchmarking/locust/common/ateapi_pb2_grpc.py +++ b/benchmarking/locust/common/ateapi_pb2_grpc.py @@ -19,7 +19,7 @@ from . import ateapi_pb2 as ateapi__pb2 -GRPC_GENERATED_VERSION = '1.80.0' +GRPC_GENERATED_VERSION = '1.81.1' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -39,7 +39,7 @@ ) -class ControlStub(object): +class ControlStub: """Control is the primary RPC interface for Agentic Substrate. """ @@ -91,7 +91,7 @@ def __init__(self, channel): _registered_method=True) -class ControlServicer(object): +class ControlServicer: """Control is the primary RPC interface for Agentic Substrate. """ @@ -202,7 +202,7 @@ def add_ControlServicer_to_server(servicer, server): # This class is part of an EXPERIMENTAL API. -class Control(object): +class Control: """Control is the primary RPC interface for Agentic Substrate. """ @@ -423,7 +423,7 @@ def DebugClear(request, _registered_method=True) -class SessionIdentityStub(object): +class SessionIdentityStub: """SessionIdentity allows substrate workloads to exchange their infrastructure-level credentials (k8s service account token, etc.) for a substrate session-level credential. A given substrate session might migrate @@ -460,7 +460,7 @@ def __init__(self, channel): _registered_method=True) -class SessionIdentityServicer(object): +class SessionIdentityServicer: """SessionIdentity allows substrate workloads to exchange their infrastructure-level credentials (k8s service account token, etc.) for a substrate session-level credential. A given substrate session might migrate @@ -518,7 +518,7 @@ def add_SessionIdentityServicer_to_server(servicer, server): # This class is part of an EXPERIMENTAL API. -class SessionIdentity(object): +class SessionIdentity: """SessionIdentity allows substrate workloads to exchange their infrastructure-level credentials (k8s service account token, etc.) for a substrate session-level credential. A given substrate session might migrate diff --git a/benchmarking/locust/common/glutton_pb2.py b/benchmarking/locust/common/glutton_pb2.py new file mode 100644 index 000000000..9cf0f37ba --- /dev/null +++ b/benchmarking/locust/common/glutton_pb2.py @@ -0,0 +1,75 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: glutton.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'glutton.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\rglutton.proto\x12\x07glutton\"T\n\x0fWriteRAMRequest\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x0c\n\x04size\x18\x02 \x01(\x05\x12&\n\nwrite_mode\x18\x03 \x01(\x0e\x32\x12.glutton.WriteMode\"\x12\n\x10WriteRAMResponse\"U\n\x10WriteDiskRequest\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x0c\n\x04size\x18\x02 \x01(\x05\x12&\n\nwrite_mode\x18\x03 \x01(\x0e\x32\x12.glutton.WriteMode\"\x13\n\x11WriteDiskResponse\"\x1e\n\rOpenFDRequest\x12\r\n\x05\x63ount\x18\x01 \x01(\x05\"\x10\n\x0eOpenFDResponse\"\x1e\n\x0bPingRequest\x12\x0f\n\x07message\x18\x01 \x01(\t\"\x1f\n\x0cPingResponse\x12\x0f\n\x07message\x18\x01 \x01(\t\"-\n\rGossipRequest\x12\x1c\n\x05peers\x18\x01 \x03(\x0b\x32\r.glutton.Peer\"\x10\n\x0eGossipResponse\"&\n\x04Peer\x12\x0c\n\x04host\x18\x01 \x01(\t\x12\x10\n\x08\x64\x65lay_ms\x18\x02 \x01(\x05*>\n\tWriteMode\x12\x17\n\x13WRITE_MODE_TRUNCATE\x10\x00\x12\x18\n\x14WRITE_MODE_OVERWRITE\x10\x01\x32\xc3\x02\n\x07Glutton\x12\x41\n\x08WriteRAM\x12\x18.glutton.WriteRAMRequest\x1a\x19.glutton.WriteRAMResponse\"\x00\x12\x44\n\tWriteDisk\x12\x19.glutton.WriteDiskRequest\x1a\x1a.glutton.WriteDiskResponse\"\x00\x12;\n\x06OpenFD\x12\x16.glutton.OpenFDRequest\x1a\x17.glutton.OpenFDResponse\"\x00\x12\x35\n\x04Ping\x12\x14.glutton.PingRequest\x1a\x15.glutton.PingResponse\"\x00\x12;\n\x06Gossip\x12\x16.glutton.GossipRequest\x1a\x17.glutton.GossipResponse\"\x00\x42=Z;github.com/agent-substrate/substrate/internal/proto/gluttonb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'glutton_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'Z;github.com/agent-substrate/substrate/internal/proto/glutton' + _globals['_WRITEMODE']._serialized_start=460 + _globals['_WRITEMODE']._serialized_end=522 + _globals['_WRITERAMREQUEST']._serialized_start=26 + _globals['_WRITERAMREQUEST']._serialized_end=110 + _globals['_WRITERAMRESPONSE']._serialized_start=112 + _globals['_WRITERAMRESPONSE']._serialized_end=130 + _globals['_WRITEDISKREQUEST']._serialized_start=132 + _globals['_WRITEDISKREQUEST']._serialized_end=217 + _globals['_WRITEDISKRESPONSE']._serialized_start=219 + _globals['_WRITEDISKRESPONSE']._serialized_end=238 + _globals['_OPENFDREQUEST']._serialized_start=240 + _globals['_OPENFDREQUEST']._serialized_end=270 + _globals['_OPENFDRESPONSE']._serialized_start=272 + _globals['_OPENFDRESPONSE']._serialized_end=288 + _globals['_PINGREQUEST']._serialized_start=290 + _globals['_PINGREQUEST']._serialized_end=320 + _globals['_PINGRESPONSE']._serialized_start=322 + _globals['_PINGRESPONSE']._serialized_end=353 + _globals['_GOSSIPREQUEST']._serialized_start=355 + _globals['_GOSSIPREQUEST']._serialized_end=400 + _globals['_GOSSIPRESPONSE']._serialized_start=402 + _globals['_GOSSIPRESPONSE']._serialized_end=418 + _globals['_PEER']._serialized_start=420 + _globals['_PEER']._serialized_end=458 + _globals['_GLUTTON']._serialized_start=525 + _globals['_GLUTTON']._serialized_end=848 +# @@protoc_insertion_point(module_scope) diff --git a/benchmarking/locust/common/glutton_pb2_grpc.py b/benchmarking/locust/common/glutton_pb2_grpc.py new file mode 100644 index 000000000..60fecd3a8 --- /dev/null +++ b/benchmarking/locust/common/glutton_pb2_grpc.py @@ -0,0 +1,289 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + +from . import glutton_pb2 as glutton__pb2 + +GRPC_GENERATED_VERSION = '1.81.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in glutton_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) + + +class GluttonStub: + """A glutton is a single small piece of a larger benchmarking suite. It is designed to be instantiated + multiple times, with each piece configured to do different units of work. + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.WriteRAM = channel.unary_unary( + '/glutton.Glutton/WriteRAM', + request_serializer=glutton__pb2.WriteRAMRequest.SerializeToString, + response_deserializer=glutton__pb2.WriteRAMResponse.FromString, + _registered_method=True) + self.WriteDisk = channel.unary_unary( + '/glutton.Glutton/WriteDisk', + request_serializer=glutton__pb2.WriteDiskRequest.SerializeToString, + response_deserializer=glutton__pb2.WriteDiskResponse.FromString, + _registered_method=True) + self.OpenFD = channel.unary_unary( + '/glutton.Glutton/OpenFD', + request_serializer=glutton__pb2.OpenFDRequest.SerializeToString, + response_deserializer=glutton__pb2.OpenFDResponse.FromString, + _registered_method=True) + self.Ping = channel.unary_unary( + '/glutton.Glutton/Ping', + request_serializer=glutton__pb2.PingRequest.SerializeToString, + response_deserializer=glutton__pb2.PingResponse.FromString, + _registered_method=True) + self.Gossip = channel.unary_unary( + '/glutton.Glutton/Gossip', + request_serializer=glutton__pb2.GossipRequest.SerializeToString, + response_deserializer=glutton__pb2.GossipResponse.FromString, + _registered_method=True) + + +class GluttonServicer: + """A glutton is a single small piece of a larger benchmarking suite. It is designed to be instantiated + multiple times, with each piece configured to do different units of work. + """ + + def WriteRAM(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def WriteDisk(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def OpenFD(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Ping(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Gossip(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_GluttonServicer_to_server(servicer, server): + rpc_method_handlers = { + 'WriteRAM': grpc.unary_unary_rpc_method_handler( + servicer.WriteRAM, + request_deserializer=glutton__pb2.WriteRAMRequest.FromString, + response_serializer=glutton__pb2.WriteRAMResponse.SerializeToString, + ), + 'WriteDisk': grpc.unary_unary_rpc_method_handler( + servicer.WriteDisk, + request_deserializer=glutton__pb2.WriteDiskRequest.FromString, + response_serializer=glutton__pb2.WriteDiskResponse.SerializeToString, + ), + 'OpenFD': grpc.unary_unary_rpc_method_handler( + servicer.OpenFD, + request_deserializer=glutton__pb2.OpenFDRequest.FromString, + response_serializer=glutton__pb2.OpenFDResponse.SerializeToString, + ), + 'Ping': grpc.unary_unary_rpc_method_handler( + servicer.Ping, + request_deserializer=glutton__pb2.PingRequest.FromString, + response_serializer=glutton__pb2.PingResponse.SerializeToString, + ), + 'Gossip': grpc.unary_unary_rpc_method_handler( + servicer.Gossip, + request_deserializer=glutton__pb2.GossipRequest.FromString, + response_serializer=glutton__pb2.GossipResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'glutton.Glutton', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('glutton.Glutton', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class Glutton: + """A glutton is a single small piece of a larger benchmarking suite. It is designed to be instantiated + multiple times, with each piece configured to do different units of work. + """ + + @staticmethod + def WriteRAM(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/glutton.Glutton/WriteRAM', + glutton__pb2.WriteRAMRequest.SerializeToString, + glutton__pb2.WriteRAMResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def WriteDisk(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/glutton.Glutton/WriteDisk', + glutton__pb2.WriteDiskRequest.SerializeToString, + glutton__pb2.WriteDiskResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def OpenFD(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/glutton.Glutton/OpenFD', + glutton__pb2.OpenFDRequest.SerializeToString, + glutton__pb2.OpenFDResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Ping(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/glutton.Glutton/Ping', + glutton__pb2.PingRequest.SerializeToString, + glutton__pb2.PingResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Gossip(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/glutton.Glutton/Gossip', + glutton__pb2.GossipRequest.SerializeToString, + glutton__pb2.GossipResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) diff --git a/benchmarking/locust/generate_protos.sh b/benchmarking/locust/generate_protos.sh index e4f827e85..e4a69ca5f 100755 --- a/benchmarking/locust/generate_protos.sh +++ b/benchmarking/locust/generate_protos.sh @@ -24,8 +24,7 @@ if [[ -f .ate-dev-env.sh ]]; then source .ate-dev-env.sh fi -PROTO_PATH="pkg/proto/ateapipb" -PROTO_FILE="$PROTO_PATH/ateapi.proto" +OUT_DIR="benchmarking/locust/common" # Create and activate virtual environment if it doesn't exist VENV_DIR="benchmarking/locust/venv" @@ -41,22 +40,43 @@ else source "$VENV_DIR/bin/activate" fi -echo "Generating Python proto clients from $PROTO_FILE..." +# generate_proto compiles a single .proto file into ${OUT_DIR}, prepends the +# project's license header, and rewrites the generated grpc file's intra-package +# import to a relative form so it resolves under the `common` package. +# +# Args: +# $1 Directory containing the .proto file (passed to protoc -I) +# $2 Base name of the .proto (e.g. "ateapi" for ateapi.proto) +generate_proto() { + local proto_path="$1" + local proto_base="$2" + local proto_file="${proto_path}/${proto_base}.proto" + + echo "Generating Python proto clients from ${proto_file}..." + python3 -m grpc_tools.protoc \ + -I"${proto_path}" \ + --python_out="${OUT_DIR}/" \ + --grpc_python_out="${OUT_DIR}/" \ + "${proto_file}" -python3 -m grpc_tools.protoc -I"$PROTO_PATH" --python_out=benchmarking/locust/common/ --grpc_python_out=benchmarking/locust/common/ "$PROTO_FILE" + local pb_file="${OUT_DIR}/${proto_base}_pb2.py" + local grpc_file="${OUT_DIR}/${proto_base}_pb2_grpc.py" -# Prepend ASLv2 header to generated files -for file in benchmarking/locust/common/ateapi_pb2.py benchmarking/locust/common/ateapi_pb2_grpc.py; do - if [ -f "$file" ]; then - cat hack/boilerplate/sh.txt "$file" > "${file}.tmp" - mv "${file}.tmp" "$file" + for file in "${pb_file}" "${grpc_file}"; do + if [ -f "${file}" ]; then + cat hack/boilerplate/sh.txt "${file}" > "${file}.tmp" + mv "${file}.tmp" "${file}" + fi + done + + # protoc emits `import foo_pb2 as foo__pb2`, which doesn't resolve under our + # `common` package; rewrite to a relative import. + if [ -f "${grpc_file}" ]; then + sed -i "s/^import ${proto_base}_pb2 as ${proto_base}__pb2/from . import ${proto_base}_pb2 as ${proto_base}__pb2/" "${grpc_file}" fi -done +} -# Fix relative import in generated grpc file -GRPC_FILE="benchmarking/locust/common/ateapi_pb2_grpc.py" -if [ -f "$GRPC_FILE" ]; then - sed -i 's/^import ateapi_pb2 as ateapi__pb2/from . import ateapi_pb2 as ateapi__pb2/' "$GRPC_FILE" -fi +generate_proto "pkg/proto/ateapipb" "ateapi" +generate_proto "internal/proto/glutton" "glutton" echo "Done!" diff --git a/benchmarking/locust/tests/glutton.py b/benchmarking/locust/tests/glutton.py new file mode 100644 index 000000000..830681131 --- /dev/null +++ b/benchmarking/locust/tests/glutton.py @@ -0,0 +1,193 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from locust import User, task, events +from locust.exception import StopUser +import time +import uuid +import grpc +from common import ateapi_pb2 +from common import ateapi_pb2_grpc +from common import glutton_pb2 +from common import glutton_pb2_grpc +from common.metrics import init_metrics, update_user_count +from common.trace import init_tracing, get_tracer +from common.wait_time import init_wait_time, dynamic_wait_time +from opentelemetry.propagate import inject +import logging + +logger = logging.getLogger(__name__) + +init_tracing("locust-workloads") +init_metrics() +init_wait_time() + +tracer = get_tracer(__name__) + + +# The actor sandbox only has TCP/80 forwarded in from the worker pod +# (see cmd/ateom-gvisor/main.go), so the glutton ActorTemplate is +# configured with --grpc-listen-addr=:80 and we connect there. +GLUTTON_PORT = 80 + + +class GluttonUser(User): + """Spins up a single glutton actor on start, pings it repeatedly in + @task, and tears the actor down on stop.""" + + wait_time = dynamic_wait_time + # `host` is what locust shows in the web UI / --host flag; it can be + # overridden by the user at test start. Keep the api target in a + # separate attribute so it's not clobbered when host points elsewhere + # (e.g. when running with other user classes via --class-picker). + host = "api.ate-system.svc.cluster.local:443" + api_host = "api.ate-system.svc.cluster.local:443" + template_name = "glutton" + + def on_start(self): + update_user_count(1, self.__class__.__name__) + + target = self.api_host.replace("http://", "").replace("https://", "") + with open("/run/servicedns-ca/ca.crt", "rb") as f: + ca_cert = f.read() + options = [('grpc.ssl_target_name_override', 'api.ate-system.svc')] + self.api_channel = grpc.secure_channel( + target, + grpc.ssl_channel_credentials(root_certificates=ca_cert), + options=options, + ) + self.api_stub = ateapi_pb2_grpc.ControlStub(self.api_channel) + + self.actor_id = f"sb-{uuid.uuid4()}" + self.glutton_channel = None + self.glutton_stub = None + + try: + self.api_stub.CreateActor( + ateapi_pb2.CreateActorRequest( + actor_id=self.actor_id, + actor_template_namespace="benchmark-workloads", + actor_template_name=self.template_name, + ) + ) + except Exception as e: + logger.error(f"Failed to create glutton actor {self.actor_id}: {e}") + self.api_channel.close() + raise StopUser() + + # CreateActor leaves the actor SUSPENDED; resume it explicitly with + # boot=True since there's no golden snapshot for a fresh actor. + # ResumeActor is synchronous: by the time it returns, the actor is + # RUNNING on a worker pod and the returned Actor carries that pod's + # IP (see cmd/ateapi/internal/controlapi/workflow.go). + try: + resp = self.api_stub.ResumeActor( + ateapi_pb2.ResumeActorRequest(actor_id=self.actor_id, boot=True) + ) + except Exception as e: + logger.error(f"Failed to resume glutton actor {self.actor_id}: {e}") + self._teardown_actor() + self.api_channel.close() + raise StopUser() + + actor_ip = resp.actor.ateom_pod_ip + if not actor_ip: + logger.error( + f"Glutton actor {self.actor_id} resumed without an ateom_pod_ip; " + f"stopping user" + ) + self._teardown_actor() + self.api_channel.close() + raise StopUser() + + self.glutton_channel = grpc.insecure_channel(f"{actor_ip}:{GLUTTON_PORT}") + self.glutton_stub = glutton_pb2_grpc.GluttonStub(self.glutton_channel) + + def on_stop(self): + update_user_count(-1, self.__class__.__name__) + if self.glutton_channel is not None: + try: + self.glutton_channel.close() + except Exception as e: + logger.warning(f"Failed to close glutton channel: {e}") + self._teardown_actor() + self.api_channel.close() + + def _teardown_actor(self): + try: + self.api_stub.SuspendActor( + ateapi_pb2.SuspendActorRequest(actor_id=self.actor_id) + ) + except Exception as e: + logger.warning( + f"Failed to suspend glutton actor {self.actor_id} during teardown: {e}" + ) + try: + self.api_stub.DeleteActor( + ateapi_pb2.DeleteActorRequest(actor_id=self.actor_id) + ) + except Exception as e: + logger.warning( + f"Failed to delete glutton actor {self.actor_id} during teardown: {e}" + ) + + @task + def ping(self): + if self.glutton_stub is None: + return + + msg = uuid.uuid4().hex + start_time = time.time() + with tracer.start_as_current_span("GluttonPing") as span: + headers = {} + inject(headers) + metadata = list(headers.items()) + try: + resp = self.glutton_stub.Ping( + glutton_pb2.PingRequest(message=msg), + metadata=metadata, + ) + duration = (time.time() - start_time) * 1000 + if resp.message != msg: + raise RuntimeError( + f"Ping echo mismatch: sent={msg!r}, recv={resp.message!r}" + ) + events.request.fire( + request_type="grpc", + name="GluttonPing", + response_time=duration, + response_length=len(resp.message), + exception=None, + user_class=self.__class__.__name__, + ) + if span.get_span_context().trace_flags.sampled: + logger.info( + f"Traced GluttonPing: trace_id={span.get_span_context().trace_id:032x}, " + f"duration={duration:.2f}ms" + ) + except Exception as e: + duration = (time.time() - start_time) * 1000 + events.request.fire( + request_type="grpc", + name="GluttonPing", + response_time=duration, + response_length=0, + exception=e, + user_class=self.__class__.__name__, + ) + if span.get_span_context().trace_flags.sampled: + logger.info( + f"Traced GluttonPing (failed): trace_id={span.get_span_context().trace_id:032x}, " + f"duration={duration:.2f}ms" + ) diff --git a/benchmarking/locust/tests/kernelmem.py b/benchmarking/locust/tests/kernelmem.py index 6bd47f5c7..cedc04487 100644 --- a/benchmarking/locust/tests/kernelmem.py +++ b/benchmarking/locust/tests/kernelmem.py @@ -13,6 +13,7 @@ # limitations under the License. from locust import User, task, events +from locust.exception import StopUser import time import uuid import grpc @@ -60,7 +61,9 @@ def on_start(self): ) ) except Exception as e: - print(f"Failed to create actor {self.actor_id}: {e}") + logger.error(f"Failed to create actor {self.actor_id}: {e}") + self.channel.close() + raise StopUser() def on_stop(self): update_user_count(-1, self.__class__.__name__) @@ -70,7 +73,7 @@ def on_stop(self): ateapi_pb2.SuspendActorRequest(actor_id=self.actor_id) ) except Exception as e: - print(f"Failed to suspend actor {self.actor_id} during teardown: {e}") + logger.warning(f"Failed to suspend actor {self.actor_id} during teardown: {e}") # Delete actor try: @@ -78,7 +81,7 @@ def on_stop(self): ateapi_pb2.DeleteActorRequest(actor_id=self.actor_id) ) except Exception as e: - print(f"Failed to delete actor {self.actor_id}: {e}") + logger.warning(f"Failed to delete actor {self.actor_id}: {e}") self.channel.close() diff --git a/benchmarking/locust/tests/sleep.py b/benchmarking/locust/tests/sleep.py index f10b21742..98c78f5e2 100644 --- a/benchmarking/locust/tests/sleep.py +++ b/benchmarking/locust/tests/sleep.py @@ -13,6 +13,7 @@ # limitations under the License. from locust import User, task, events +from locust.exception import StopUser import time import uuid import grpc @@ -60,7 +61,9 @@ def on_start(self): ) ) except Exception as e: - print(f"Failed to create actor {self.actor_id}: {e}") + logger.error(f"Failed to create actor {self.actor_id}: {e}") + self.channel.close() + raise StopUser() def on_stop(self): update_user_count(-1, self.__class__.__name__) @@ -70,7 +73,7 @@ def on_stop(self): ateapi_pb2.SuspendActorRequest(actor_id=self.actor_id) ) except Exception as e: - print(f"Failed to suspend actor {self.actor_id} during teardown: {e}") + logger.warning(f"Failed to suspend actor {self.actor_id} during teardown: {e}") # Delete actor try: @@ -78,7 +81,7 @@ def on_stop(self): ateapi_pb2.DeleteActorRequest(actor_id=self.actor_id) ) except Exception as e: - print(f"Failed to delete actor {self.actor_id}: {e}") + logger.warning(f"Failed to delete actor {self.actor_id}: {e}") self.channel.close() diff --git a/benchmarking/locust/tests/usermem.py b/benchmarking/locust/tests/usermem.py index cad0ce1d8..25fcb6677 100644 --- a/benchmarking/locust/tests/usermem.py +++ b/benchmarking/locust/tests/usermem.py @@ -13,6 +13,7 @@ # limitations under the License. from locust import User, task, events +from locust.exception import StopUser import time import uuid import grpc @@ -60,7 +61,9 @@ def on_start(self): ) ) except Exception as e: - print(f"Failed to create actor {self.actor_id}: {e}") + logger.error(f"Failed to create actor {self.actor_id}: {e}") + self.channel.close() + raise StopUser() def on_stop(self): update_user_count(-1, self.__class__.__name__) @@ -70,7 +73,7 @@ def on_stop(self): ateapi_pb2.SuspendActorRequest(actor_id=self.actor_id) ) except Exception as e: - print(f"Failed to suspend actor {self.actor_id} during teardown: {e}") + logger.warning(f"Failed to suspend actor {self.actor_id} during teardown: {e}") # Delete actor try: @@ -78,7 +81,7 @@ def on_stop(self): ateapi_pb2.DeleteActorRequest(actor_id=self.actor_id) ) except Exception as e: - print(f"Failed to delete actor {self.actor_id}: {e}") + logger.warning(f"Failed to delete actor {self.actor_id}: {e}") self.channel.close() diff --git a/benchmarking/workloads/deploy.sh b/benchmarking/workloads/deploy.sh index 080740150..e9b5c8340 100755 --- a/benchmarking/workloads/deploy.sh +++ b/benchmarking/workloads/deploy.sh @@ -41,7 +41,7 @@ usage() { echo "" echo "Options:" echo " --deploy Substitute BUCKET_NAME and deploy workloads to the cluster using ko apply" - echo " --delete Substitute BUCKET_NAME and delete workloads from the cluster using kubectl delete" + echo " --delete Substitute BUCKET_NAME and delete workloads from the cluster" echo " -h, --help Show this help message" } @@ -53,8 +53,10 @@ deploy() { delete() { echo "Deleting workloads..." + # The template contains ko:// image references; route through `ko delete` + # so they get resolved before kubectl sees them. sed "s|\${BUCKET_NAME}|${BUCKET_NAME}|g" "${MANIFEST_TEMPLATE}" \ - | kubectl delete --ignore-not-found -f - + | hack/run-tool.sh ko delete --ignore-not-found -f - } if [[ "$#" -eq 0 ]]; then diff --git a/benchmarking/workloads/manifests/full_workloads.yaml.tmpl b/benchmarking/workloads/manifests/full_workloads.yaml.tmpl index 73f382840..9a4ceee71 100644 --- a/benchmarking/workloads/manifests/full_workloads.yaml.tmpl +++ b/benchmarking/workloads/manifests/full_workloads.yaml.tmpl @@ -113,3 +113,35 @@ spec: name: benchmark-ateom snapshotsConfig: location: gs://${BUCKET_NAME}/benchmark-workloads/kernelmem/ + +--- + +apiVersion: ate.dev/v1alpha1 +kind: ActorTemplate +metadata: + name: glutton + namespace: benchmark-workloads +spec: + runsc: + amd64: + url: "gs://gvisor/releases/nightly/2026-05-19/x86_64/runsc" + sha256Hash: "a397be1abc2420d26bce6c70e6e2ff96c73aaaab929756c56f5e2089ea842b63" + arm64: + url: "gs://gvisor/releases/nightly/2026-05-19/aarch64/runsc" + sha256Hash: "1ba2366ae2efceba166046f51a4104f9261c9cb72c6db8f5b3fe2dc57dea86b9" + pauseImage: "gcr.io/gke-release/pause@sha256:bcbd57ba5653580ec647b16d8163cdd1112df3609129b01f912a8032e48265da" + containers: + - name: glutton + image: ko://github.com/agent-substrate/substrate/cmd/testing/glutton + # Port 80 is the only inbound port nftables forwards from the worker pod + # into the actor sandbox (see cmd/ateom-gvisor/main.go). + command: + - "/ko-app/glutton" + - "--grpc-listen-addr=:80" + - "--metrics-listen-addr=:9090" + - "--data-dir=/tmp/glutton" + workerPoolRef: + namespace: benchmark-workloads + name: benchmark-ateom + snapshotsConfig: + location: gs://${BUCKET_NAME}/benchmark-workloads/glutton/ diff --git a/benchmarking/workloads/manifests/workloads.yaml.tmpl b/benchmarking/workloads/manifests/workloads.yaml.tmpl index 8e4faf5cd..723345c15 100644 --- a/benchmarking/workloads/manifests/workloads.yaml.tmpl +++ b/benchmarking/workloads/manifests/workloads.yaml.tmpl @@ -54,3 +54,34 @@ spec: snapshotsConfig: location: gs://${BUCKET_NAME}/benchmark-workloads/sleep/ +--- + +apiVersion: ate.dev/v1alpha1 +kind: ActorTemplate +metadata: + name: glutton + namespace: benchmark-workloads +spec: + runsc: + amd64: + url: "gs://gvisor/releases/nightly/2026-05-19/x86_64/runsc" + sha256Hash: "a397be1abc2420d26bce6c70e6e2ff96c73aaaab929756c56f5e2089ea842b63" + arm64: + url: "gs://gvisor/releases/nightly/2026-05-19/aarch64/runsc" + sha256Hash: "1ba2366ae2efceba166046f51a4104f9261c9cb72c6db8f5b3fe2dc57dea86b9" + pauseImage: "gcr.io/gke-release/pause@sha256:bcbd57ba5653580ec647b16d8163cdd1112df3609129b01f912a8032e48265da" + containers: + - name: glutton + image: ko://github.com/agent-substrate/substrate/cmd/testing/glutton + # Port 80 is the only inbound port nftables forwards from the worker pod + # into the actor sandbox (see cmd/ateom-gvisor/main.go). + command: + - "/ko-app/glutton" + - "--grpc-listen-addr=:80" + - "--metrics-listen-addr=:9090" + - "--data-dir=/tmp/glutton" + workerPoolRef: + namespace: benchmark-workloads + name: benchmark-ateom + snapshotsConfig: + location: gs://${BUCKET_NAME}/benchmark-workloads/glutton/ diff --git a/cmd/testing/glutton/main.go b/cmd/testing/glutton/main.go new file mode 100644 index 000000000..7e5c3e663 --- /dev/null +++ b/cmd/testing/glutton/main.go @@ -0,0 +1,488 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// glutton is a small benchmarking workload that exposes a gRPC API for +// consuming RAM, disk, and file descriptors, and for gossiping with +// other glutton instances. See internal/proto/glutton/glutton.proto. +package main + +import ( + "context" + "crypto/rand" + "errors" + "fmt" + "io" + "log/slog" + "net" + "os" + "path/filepath" + "regexp" + "sync" + "time" + + "github.com/agent-substrate/substrate/internal/proto/glutton" + "github.com/agent-substrate/substrate/internal/serverboot" + "github.com/agent-substrate/substrate/internal/version" + "github.com/google/uuid" + "github.com/spf13/pflag" + "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/reflection" + "google.golang.org/grpc/status" +) + +const meterName = "glutton" + +var ( + listenAddr = pflag.String("grpc-listen-addr", ":8080", "Address and port the gRPC server should listen on.") + metricsListenAddr = pflag.String("metrics-listen-addr", ":9090", "Address and port the Prometheus metrics server should listen on.") + dataDir = pflag.String("data-dir", "", "Directory under which WriteDisk files are stored. Required.") + + showVersion = pflag.Bool("version", false, "Print version and exit.") +) + +func main() { + pflag.Parse() + if *showVersion { + fmt.Println(version.String()) + return + } + if *dataDir == "" { + fmt.Fprintln(os.Stderr, "--data-dir is required") + os.Exit(2) + } + + ctx := context.Background() + serverboot.InitLogger() + + tp, err := serverboot.InitTracing(ctx, serverboot.TracingOptions{ + ServiceName: "glutton", + Sampler: sdktrace.ParentBased(sdktrace.NeverSample()), + }) + if err != nil { + serverboot.Fatal(ctx, "Failed to initialize tracing", err) + } + defer serverboot.ShutdownProvider("TracerProvider", tp.Shutdown) + + mp, err := serverboot.InitMetrics(ctx, "glutton") + if err != nil { + serverboot.Fatal(ctx, "Failed to initialize metrics", err) + } + defer serverboot.ShutdownProvider("MeterProvider", mp.Shutdown) + + if err := os.MkdirAll(*dataDir, 0o700); err != nil { + serverboot.Fatal(ctx, "Failed to create data directory", err) + } + + svc, err := newGluttonService(*dataDir) + if err != nil { + serverboot.Fatal(ctx, "Failed to construct glutton service", err) + } + defer svc.Close() + + lis, err := net.Listen("tcp", *listenAddr) + if err != nil { + serverboot.Fatal(ctx, "Failed to start listener", err) + } + + srv := grpc.NewServer( + grpc.StatsHandler(otelgrpc.NewServerHandler()), + ) + glutton.RegisterGluttonServer(srv, svc) + reflection.Register(srv) + + go serverboot.StartMetricsServer(ctx, serverboot.MetricsServerOptions{ + Addr: *metricsListenAddr, + EnableReadyz: true, + }) + + slog.InfoContext(ctx, "glutton starting", + slog.String("grpc-listen-addr", *listenAddr), + slog.String("metrics-listen-addr", *metricsListenAddr), + slog.String("data-dir", *dataDir), + ) + if err := srv.Serve(lis); err != nil { + serverboot.Fatal(ctx, "Failed to serve", err) + } +} + +// diskKeyRE rejects anything that could escape the data dir or hit a +// hidden file: only alphanumerics, underscore, and dash are permitted. +var diskKeyRE = regexp.MustCompile(`^[a-zA-Z0-9_-]+$`) + +type gluttonService struct { + glutton.UnimplementedGluttonServer + + dataDir string + + mu sync.Mutex + ram map[string][]byte + fds []*os.File + peers map[string]*peerGossip + + ramWriteBytes metric.Int64Counter + diskWriteBytes metric.Int64Counter + pingsReceived metric.Int64Counter + gossipSent metric.Int64Counter + gossipLatency metric.Float64Histogram +} + +type peerGossip struct { + host string + delayMs int32 + cancel context.CancelFunc + done chan struct{} +} + +func newGluttonService(dir string) (*gluttonService, error) { + s := &gluttonService{ + dataDir: dir, + ram: make(map[string][]byte), + peers: make(map[string]*peerGossip), + } + + m := otel.Meter(meterName) + + var err error + s.ramWriteBytes, err = m.Int64Counter( + "glutton.ram.write.bytes", + metric.WithUnit("By"), + metric.WithDescription("Total bytes written to RAM via WriteRAM over the process lifetime."), + ) + if err != nil { + return nil, fmt.Errorf("create glutton.ram.write.bytes counter: %w", err) + } + s.diskWriteBytes, err = m.Int64Counter( + "glutton.disk.write.bytes", + metric.WithUnit("By"), + metric.WithDescription("Total bytes written to disk via WriteDisk over the process lifetime."), + ) + if err != nil { + return nil, fmt.Errorf("create glutton.disk.write.bytes counter: %w", err) + } + s.pingsReceived, err = m.Int64Counter( + "glutton.ping.requests", + metric.WithDescription("Number of Ping requests received."), + ) + if err != nil { + return nil, fmt.Errorf("create glutton.ping.requests counter: %w", err) + } + s.gossipSent, err = m.Int64Counter( + "glutton.gossip.requests.sent", + metric.WithDescription("Number of gossip Ping requests sent per peer."), + ) + if err != nil { + return nil, fmt.Errorf("create glutton.gossip.requests.sent counter: %w", err) + } + s.gossipLatency, err = m.Float64Histogram( + "glutton.gossip.latency", + metric.WithUnit("s"), + metric.WithDescription("Latency of gossip Ping requests per peer."), + metric.WithExplicitBucketBoundaries( + 0.001, 0.0025, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, + ), + ) + if err != nil { + return nil, fmt.Errorf("create glutton.gossip.latency histogram: %w", err) + } + + fdsOpen, err := m.Int64ObservableGauge( + "glutton.fds.open", + metric.WithDescription("File descriptors currently held open by OpenFD."), + ) + if err != nil { + return nil, fmt.Errorf("create glutton.fds.open gauge: %w", err) + } + peerDelay, err := m.Int64ObservableGauge( + "glutton.gossip.delay", + metric.WithUnit("ms"), + metric.WithDescription("Configured gossip delay per peer."), + ) + if err != nil { + return nil, fmt.Errorf("create glutton.gossip.delay gauge: %w", err) + } + + if _, err := m.RegisterCallback(func(_ context.Context, o metric.Observer) error { + s.mu.Lock() + defer s.mu.Unlock() + o.ObserveInt64(fdsOpen, int64(len(s.fds))) + for host, p := range s.peers { + o.ObserveInt64(peerDelay, int64(p.delayMs), metric.WithAttributes(attribute.String("host", host))) + } + return nil + }, fdsOpen, peerDelay); err != nil { + return nil, fmt.Errorf("register glutton observable callback: %w", err) + } + + return s, nil +} + +// Close cancels every running gossip goroutine and waits for them to exit. +func (s *gluttonService) Close() { + s.mu.Lock() + peers := s.peers + s.peers = make(map[string]*peerGossip) + s.mu.Unlock() + for _, p := range peers { + p.cancel() + <-p.done + } +} + +func (s *gluttonService) WriteRAM(ctx context.Context, req *glutton.WriteRAMRequest) (*glutton.WriteRAMResponse, error) { + if req.GetKey() == "" { + return nil, status.Error(codes.InvalidArgument, "key is required") + } + if req.GetSize() < 0 { + return nil, status.Error(codes.InvalidArgument, "size must be non-negative") + } + buf, err := randomBytes(int(req.GetSize())) + if err != nil { + return nil, status.Errorf(codes.Internal, "generate random bytes: %v", err) + } + + s.mu.Lock() + switch req.GetWriteMode() { + case glutton.WriteMode_WRITE_MODE_TRUNCATE: + s.ram[req.GetKey()] = buf + case glutton.WriteMode_WRITE_MODE_OVERWRITE: + existing := s.ram[req.GetKey()] + if len(buf) > len(existing) { + merged := make([]byte, len(buf)) + copy(merged, buf) + s.ram[req.GetKey()] = merged + } else { + copy(existing, buf) + } + default: + s.mu.Unlock() + return nil, status.Errorf(codes.InvalidArgument, "unknown write_mode %v", req.GetWriteMode()) + } + s.mu.Unlock() + + s.ramWriteBytes.Add(ctx, int64(len(buf))) + return &glutton.WriteRAMResponse{}, nil +} + +func (s *gluttonService) WriteDisk(ctx context.Context, req *glutton.WriteDiskRequest) (*glutton.WriteDiskResponse, error) { + if !diskKeyRE.MatchString(req.GetKey()) { + return nil, status.Errorf(codes.InvalidArgument, "key %q must match %s", req.GetKey(), diskKeyRE) + } + if req.GetSize() < 0 { + return nil, status.Error(codes.InvalidArgument, "size must be non-negative") + } + + path := filepath.Join(s.dataDir, req.GetKey()) + + var flag int + switch req.GetWriteMode() { + case glutton.WriteMode_WRITE_MODE_TRUNCATE: + flag = os.O_WRONLY | os.O_CREATE | os.O_TRUNC + case glutton.WriteMode_WRITE_MODE_OVERWRITE: + // No O_TRUNC: writes go from offset 0 but any bytes beyond size remain. + flag = os.O_WRONLY | os.O_CREATE + default: + return nil, status.Errorf(codes.InvalidArgument, "unknown write_mode %v", req.GetWriteMode()) + } + + f, err := os.OpenFile(path, flag, 0o600) + if err != nil { + return nil, status.Errorf(codes.Internal, "open %s: %v", path, err) + } + defer f.Close() + + if err := streamRandomBytes(f, int64(req.GetSize())); err != nil { + return nil, status.Errorf(codes.Internal, "write %s: %v", path, err) + } + + s.diskWriteBytes.Add(ctx, int64(req.GetSize())) + return &glutton.WriteDiskResponse{}, nil +} + +func (s *gluttonService) OpenFD(_ context.Context, req *glutton.OpenFDRequest) (*glutton.OpenFDResponse, error) { + if req.GetCount() < 0 { + return nil, status.Error(codes.InvalidArgument, "count must be non-negative") + } + target := int(req.GetCount()) + + s.mu.Lock() + defer s.mu.Unlock() + + for len(s.fds) > target { + last := len(s.fds) - 1 + if err := s.fds[last].Close(); err != nil { + slog.Warn("Failed to close glutton fd", slog.Any("err", err)) + } + s.fds[last] = nil + s.fds = s.fds[:last] + } + for len(s.fds) < target { + f, err := os.Open(os.DevNull) + if err != nil { + return nil, status.Errorf(codes.Internal, "open %s: %v", os.DevNull, err) + } + s.fds = append(s.fds, f) + } + return &glutton.OpenFDResponse{}, nil +} + +func (s *gluttonService) Ping(ctx context.Context, req *glutton.PingRequest) (*glutton.PingResponse, error) { + s.pingsReceived.Add(ctx, 1) + return &glutton.PingResponse{Message: req.GetMessage()}, nil +} + +func (s *gluttonService) Gossip(_ context.Context, req *glutton.GossipRequest) (*glutton.GossipResponse, error) { + want := make(map[string]*glutton.Peer, len(req.GetPeers())) + for _, p := range req.GetPeers() { + if p.GetHost() == "" { + return nil, status.Error(codes.InvalidArgument, "peer host is required") + } + if p.GetDelayMs() <= 0 { + return nil, status.Errorf(codes.InvalidArgument, "peer %q delay_ms must be positive", p.GetHost()) + } + want[p.GetHost()] = p + } + + s.mu.Lock() + var toStop []*peerGossip + for host, existing := range s.peers { + w, ok := want[host] + if !ok || w.GetDelayMs() != existing.delayMs { + toStop = append(toStop, existing) + delete(s.peers, host) + } + } + var toStart []*glutton.Peer + for host, w := range want { + if _, ok := s.peers[host]; !ok { + toStart = append(toStart, w) + } + } + s.mu.Unlock() + + for _, p := range toStop { + p.cancel() + <-p.done + } + + for _, w := range toStart { + gctx, cancel := context.WithCancel(context.Background()) + pg := &peerGossip{ + host: w.GetHost(), + delayMs: w.GetDelayMs(), + cancel: cancel, + done: make(chan struct{}), + } + s.mu.Lock() + s.peers[w.GetHost()] = pg + s.mu.Unlock() + go s.runGossip(gctx, pg) + } + + return &glutton.GossipResponse{}, nil +} + +func (s *gluttonService) runGossip(ctx context.Context, pg *peerGossip) { + defer close(pg.done) + + // grpc.NewClient resolves and connects lazily; the first RPC surfaces + // any failure, so the peer doesn't have to be reachable at start time. + conn, err := grpc.NewClient(pg.host, + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithStatsHandler(otelgrpc.NewClientHandler()), + ) + if err != nil { + slog.ErrorContext(ctx, "Failed to dial gossip peer", slog.String("host", pg.host), slog.Any("err", err)) + return + } + defer conn.Close() + client := glutton.NewGluttonClient(conn) + + attrs := metric.WithAttributes(attribute.String("host", pg.host)) + ticker := time.NewTicker(time.Duration(pg.delayMs) * time.Millisecond) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + } + msg := uuid.NewString() + start := time.Now() + resp, err := client.Ping(ctx, &glutton.PingRequest{Message: msg}) + latency := time.Since(start).Seconds() + s.gossipSent.Add(ctx, 1, attrs) + s.gossipLatency.Record(ctx, latency, attrs) + if err != nil { + if errors.Is(ctx.Err(), context.Canceled) { + return + } + slog.WarnContext(ctx, "Gossip ping failed", slog.String("host", pg.host), slog.Any("err", err)) + continue + } + if resp.GetMessage() != msg { + slog.WarnContext(ctx, "Gossip ping returned unexpected message", + slog.String("host", pg.host), + slog.String("sent", msg), + slog.String("received", resp.GetMessage()), + ) + } + } +} + +func randomBytes(n int) ([]byte, error) { + buf := make([]byte, n) + if _, err := rand.Read(buf); err != nil { + return nil, err + } + return buf, nil +} + +// streamRandomBytesChunk caps per-syscall random fill and write size so a +// multi-gigabyte WriteDisk doesn't have to materialize in RAM. +const streamRandomBytesChunk = 1 << 20 // 1 MiB + +// streamRandomBytes writes total random bytes to w sequentially, in +// streamRandomBytesChunk-sized chunks. The caller is responsible for the +// file's open mode and starting offset; this writes from the current +// position forward. +func streamRandomBytes(w io.Writer, total int64) error { + if total <= 0 { + return nil + } + buf := make([]byte, streamRandomBytesChunk) + var written int64 + for written < total { + chunk := buf + if remaining := total - written; remaining < int64(len(chunk)) { + chunk = buf[:remaining] + } + if _, err := rand.Read(chunk); err != nil { + return fmt.Errorf("generate random bytes: %w", err) + } + n, err := w.Write(chunk) + if err != nil { + return err + } + written += int64(n) + } + return nil +} diff --git a/internal/proto/glutton/gen.go b/internal/proto/glutton/gen.go new file mode 100644 index 000000000..d6795c45a --- /dev/null +++ b/internal/proto/glutton/gen.go @@ -0,0 +1,17 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package glutton + +//go:generate bash -c "../../../hack/protoc.sh --plugin=protoc-gen-go=$(bash ../../../hack/run-tool.sh --print-bin-path protoc-gen-go) --plugin=protoc-gen-go-grpc=$(bash ../../../hack/run-tool.sh --print-bin-path protoc-gen-go-grpc) --go_out=paths=source_relative:. --go-grpc_out=paths=source_relative:. glutton.proto" diff --git a/internal/proto/glutton/glutton.pb.go b/internal/proto/glutton/glutton.pb.go new file mode 100644 index 000000000..b54b6663b --- /dev/null +++ b/internal/proto/glutton/glutton.pb.go @@ -0,0 +1,697 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11-devel +// protoc v4.25.3 +// source: glutton.proto + +package glutton + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type WriteMode int32 + +const ( + // Overwrite the destination entirely + WriteMode_WRITE_MODE_TRUNCATE WriteMode = 0 + // Write on top of the existing data + WriteMode_WRITE_MODE_OVERWRITE WriteMode = 1 +) + +// Enum value maps for WriteMode. +var ( + WriteMode_name = map[int32]string{ + 0: "WRITE_MODE_TRUNCATE", + 1: "WRITE_MODE_OVERWRITE", + } + WriteMode_value = map[string]int32{ + "WRITE_MODE_TRUNCATE": 0, + "WRITE_MODE_OVERWRITE": 1, + } +) + +func (x WriteMode) Enum() *WriteMode { + p := new(WriteMode) + *p = x + return p +} + +func (x WriteMode) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (WriteMode) Descriptor() protoreflect.EnumDescriptor { + return file_glutton_proto_enumTypes[0].Descriptor() +} + +func (WriteMode) Type() protoreflect.EnumType { + return &file_glutton_proto_enumTypes[0] +} + +func (x WriteMode) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use WriteMode.Descriptor instead. +func (WriteMode) EnumDescriptor() ([]byte, []int) { + return file_glutton_proto_rawDescGZIP(), []int{0} +} + +type WriteRAMRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // name of the array to be written to + Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + // size of bytes to be written + Size int32 `protobuf:"varint,2,opt,name=size,proto3" json:"size,omitempty"` + WriteMode WriteMode `protobuf:"varint,3,opt,name=write_mode,json=writeMode,proto3,enum=glutton.WriteMode" json:"write_mode,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WriteRAMRequest) Reset() { + *x = WriteRAMRequest{} + mi := &file_glutton_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WriteRAMRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WriteRAMRequest) ProtoMessage() {} + +func (x *WriteRAMRequest) ProtoReflect() protoreflect.Message { + mi := &file_glutton_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WriteRAMRequest.ProtoReflect.Descriptor instead. +func (*WriteRAMRequest) Descriptor() ([]byte, []int) { + return file_glutton_proto_rawDescGZIP(), []int{0} +} + +func (x *WriteRAMRequest) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *WriteRAMRequest) GetSize() int32 { + if x != nil { + return x.Size + } + return 0 +} + +func (x *WriteRAMRequest) GetWriteMode() WriteMode { + if x != nil { + return x.WriteMode + } + return WriteMode_WRITE_MODE_TRUNCATE +} + +type WriteRAMResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WriteRAMResponse) Reset() { + *x = WriteRAMResponse{} + mi := &file_glutton_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WriteRAMResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WriteRAMResponse) ProtoMessage() {} + +func (x *WriteRAMResponse) ProtoReflect() protoreflect.Message { + mi := &file_glutton_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WriteRAMResponse.ProtoReflect.Descriptor instead. +func (*WriteRAMResponse) Descriptor() ([]byte, []int) { + return file_glutton_proto_rawDescGZIP(), []int{1} +} + +type WriteDiskRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // name of the file to be written to + Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + // size of bytes to be written + Size int32 `protobuf:"varint,2,opt,name=size,proto3" json:"size,omitempty"` + WriteMode WriteMode `protobuf:"varint,3,opt,name=write_mode,json=writeMode,proto3,enum=glutton.WriteMode" json:"write_mode,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WriteDiskRequest) Reset() { + *x = WriteDiskRequest{} + mi := &file_glutton_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WriteDiskRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WriteDiskRequest) ProtoMessage() {} + +func (x *WriteDiskRequest) ProtoReflect() protoreflect.Message { + mi := &file_glutton_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WriteDiskRequest.ProtoReflect.Descriptor instead. +func (*WriteDiskRequest) Descriptor() ([]byte, []int) { + return file_glutton_proto_rawDescGZIP(), []int{2} +} + +func (x *WriteDiskRequest) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *WriteDiskRequest) GetSize() int32 { + if x != nil { + return x.Size + } + return 0 +} + +func (x *WriteDiskRequest) GetWriteMode() WriteMode { + if x != nil { + return x.WriteMode + } + return WriteMode_WRITE_MODE_TRUNCATE +} + +type WriteDiskResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WriteDiskResponse) Reset() { + *x = WriteDiskResponse{} + mi := &file_glutton_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WriteDiskResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WriteDiskResponse) ProtoMessage() {} + +func (x *WriteDiskResponse) ProtoReflect() protoreflect.Message { + mi := &file_glutton_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WriteDiskResponse.ProtoReflect.Descriptor instead. +func (*WriteDiskResponse) Descriptor() ([]byte, []int) { + return file_glutton_proto_rawDescGZIP(), []int{3} +} + +type OpenFDRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The total number of FDs for the glutton to open + Count int32 `protobuf:"varint,1,opt,name=count,proto3" json:"count,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *OpenFDRequest) Reset() { + *x = OpenFDRequest{} + mi := &file_glutton_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *OpenFDRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OpenFDRequest) ProtoMessage() {} + +func (x *OpenFDRequest) ProtoReflect() protoreflect.Message { + mi := &file_glutton_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OpenFDRequest.ProtoReflect.Descriptor instead. +func (*OpenFDRequest) Descriptor() ([]byte, []int) { + return file_glutton_proto_rawDescGZIP(), []int{4} +} + +func (x *OpenFDRequest) GetCount() int32 { + if x != nil { + return x.Count + } + return 0 +} + +type OpenFDResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *OpenFDResponse) Reset() { + *x = OpenFDResponse{} + mi := &file_glutton_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *OpenFDResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OpenFDResponse) ProtoMessage() {} + +func (x *OpenFDResponse) ProtoReflect() protoreflect.Message { + mi := &file_glutton_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OpenFDResponse.ProtoReflect.Descriptor instead. +func (*OpenFDResponse) Descriptor() ([]byte, []int) { + return file_glutton_proto_rawDescGZIP(), []int{5} +} + +type PingRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The message to be echoed back from the ping + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PingRequest) Reset() { + *x = PingRequest{} + mi := &file_glutton_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PingRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PingRequest) ProtoMessage() {} + +func (x *PingRequest) ProtoReflect() protoreflect.Message { + mi := &file_glutton_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PingRequest.ProtoReflect.Descriptor instead. +func (*PingRequest) Descriptor() ([]byte, []int) { + return file_glutton_proto_rawDescGZIP(), []int{6} +} + +func (x *PingRequest) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +type PingResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The message echoed back + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PingResponse) Reset() { + *x = PingResponse{} + mi := &file_glutton_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PingResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PingResponse) ProtoMessage() {} + +func (x *PingResponse) ProtoReflect() protoreflect.Message { + mi := &file_glutton_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PingResponse.ProtoReflect.Descriptor instead. +func (*PingResponse) Descriptor() ([]byte, []int) { + return file_glutton_proto_rawDescGZIP(), []int{7} +} + +func (x *PingResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +type GossipRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Peers []*Peer `protobuf:"bytes,1,rep,name=peers,proto3" json:"peers,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GossipRequest) Reset() { + *x = GossipRequest{} + mi := &file_glutton_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GossipRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GossipRequest) ProtoMessage() {} + +func (x *GossipRequest) ProtoReflect() protoreflect.Message { + mi := &file_glutton_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GossipRequest.ProtoReflect.Descriptor instead. +func (*GossipRequest) Descriptor() ([]byte, []int) { + return file_glutton_proto_rawDescGZIP(), []int{8} +} + +func (x *GossipRequest) GetPeers() []*Peer { + if x != nil { + return x.Peers + } + return nil +} + +type GossipResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GossipResponse) Reset() { + *x = GossipResponse{} + mi := &file_glutton_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GossipResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GossipResponse) ProtoMessage() {} + +func (x *GossipResponse) ProtoReflect() protoreflect.Message { + mi := &file_glutton_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GossipResponse.ProtoReflect.Descriptor instead. +func (*GossipResponse) Descriptor() ([]byte, []int) { + return file_glutton_proto_rawDescGZIP(), []int{9} +} + +type Peer struct { + state protoimpl.MessageState `protogen:"open.v1"` + Host string `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"` + DelayMs int32 `protobuf:"varint,2,opt,name=delay_ms,json=delayMs,proto3" json:"delay_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Peer) Reset() { + *x = Peer{} + mi := &file_glutton_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Peer) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Peer) ProtoMessage() {} + +func (x *Peer) ProtoReflect() protoreflect.Message { + mi := &file_glutton_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Peer.ProtoReflect.Descriptor instead. +func (*Peer) Descriptor() ([]byte, []int) { + return file_glutton_proto_rawDescGZIP(), []int{10} +} + +func (x *Peer) GetHost() string { + if x != nil { + return x.Host + } + return "" +} + +func (x *Peer) GetDelayMs() int32 { + if x != nil { + return x.DelayMs + } + return 0 +} + +var File_glutton_proto protoreflect.FileDescriptor + +const file_glutton_proto_rawDesc = "" + + "\n" + + "\rglutton.proto\x12\aglutton\"j\n" + + "\x0fWriteRAMRequest\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x12\n" + + "\x04size\x18\x02 \x01(\x05R\x04size\x121\n" + + "\n" + + "write_mode\x18\x03 \x01(\x0e2\x12.glutton.WriteModeR\twriteMode\"\x12\n" + + "\x10WriteRAMResponse\"k\n" + + "\x10WriteDiskRequest\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x12\n" + + "\x04size\x18\x02 \x01(\x05R\x04size\x121\n" + + "\n" + + "write_mode\x18\x03 \x01(\x0e2\x12.glutton.WriteModeR\twriteMode\"\x13\n" + + "\x11WriteDiskResponse\"%\n" + + "\rOpenFDRequest\x12\x14\n" + + "\x05count\x18\x01 \x01(\x05R\x05count\"\x10\n" + + "\x0eOpenFDResponse\"'\n" + + "\vPingRequest\x12\x18\n" + + "\amessage\x18\x01 \x01(\tR\amessage\"(\n" + + "\fPingResponse\x12\x18\n" + + "\amessage\x18\x01 \x01(\tR\amessage\"4\n" + + "\rGossipRequest\x12#\n" + + "\x05peers\x18\x01 \x03(\v2\r.glutton.PeerR\x05peers\"\x10\n" + + "\x0eGossipResponse\"5\n" + + "\x04Peer\x12\x12\n" + + "\x04host\x18\x01 \x01(\tR\x04host\x12\x19\n" + + "\bdelay_ms\x18\x02 \x01(\x05R\adelayMs*>\n" + + "\tWriteMode\x12\x17\n" + + "\x13WRITE_MODE_TRUNCATE\x10\x00\x12\x18\n" + + "\x14WRITE_MODE_OVERWRITE\x10\x012\xc3\x02\n" + + "\aGlutton\x12A\n" + + "\bWriteRAM\x12\x18.glutton.WriteRAMRequest\x1a\x19.glutton.WriteRAMResponse\"\x00\x12D\n" + + "\tWriteDisk\x12\x19.glutton.WriteDiskRequest\x1a\x1a.glutton.WriteDiskResponse\"\x00\x12;\n" + + "\x06OpenFD\x12\x16.glutton.OpenFDRequest\x1a\x17.glutton.OpenFDResponse\"\x00\x125\n" + + "\x04Ping\x12\x14.glutton.PingRequest\x1a\x15.glutton.PingResponse\"\x00\x12;\n" + + "\x06Gossip\x12\x16.glutton.GossipRequest\x1a\x17.glutton.GossipResponse\"\x00B=Z;github.com/agent-substrate/substrate/internal/proto/gluttonb\x06proto3" + +var ( + file_glutton_proto_rawDescOnce sync.Once + file_glutton_proto_rawDescData []byte +) + +func file_glutton_proto_rawDescGZIP() []byte { + file_glutton_proto_rawDescOnce.Do(func() { + file_glutton_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_glutton_proto_rawDesc), len(file_glutton_proto_rawDesc))) + }) + return file_glutton_proto_rawDescData +} + +var file_glutton_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_glutton_proto_msgTypes = make([]protoimpl.MessageInfo, 11) +var file_glutton_proto_goTypes = []any{ + (WriteMode)(0), // 0: glutton.WriteMode + (*WriteRAMRequest)(nil), // 1: glutton.WriteRAMRequest + (*WriteRAMResponse)(nil), // 2: glutton.WriteRAMResponse + (*WriteDiskRequest)(nil), // 3: glutton.WriteDiskRequest + (*WriteDiskResponse)(nil), // 4: glutton.WriteDiskResponse + (*OpenFDRequest)(nil), // 5: glutton.OpenFDRequest + (*OpenFDResponse)(nil), // 6: glutton.OpenFDResponse + (*PingRequest)(nil), // 7: glutton.PingRequest + (*PingResponse)(nil), // 8: glutton.PingResponse + (*GossipRequest)(nil), // 9: glutton.GossipRequest + (*GossipResponse)(nil), // 10: glutton.GossipResponse + (*Peer)(nil), // 11: glutton.Peer +} +var file_glutton_proto_depIdxs = []int32{ + 0, // 0: glutton.WriteRAMRequest.write_mode:type_name -> glutton.WriteMode + 0, // 1: glutton.WriteDiskRequest.write_mode:type_name -> glutton.WriteMode + 11, // 2: glutton.GossipRequest.peers:type_name -> glutton.Peer + 1, // 3: glutton.Glutton.WriteRAM:input_type -> glutton.WriteRAMRequest + 3, // 4: glutton.Glutton.WriteDisk:input_type -> glutton.WriteDiskRequest + 5, // 5: glutton.Glutton.OpenFD:input_type -> glutton.OpenFDRequest + 7, // 6: glutton.Glutton.Ping:input_type -> glutton.PingRequest + 9, // 7: glutton.Glutton.Gossip:input_type -> glutton.GossipRequest + 2, // 8: glutton.Glutton.WriteRAM:output_type -> glutton.WriteRAMResponse + 4, // 9: glutton.Glutton.WriteDisk:output_type -> glutton.WriteDiskResponse + 6, // 10: glutton.Glutton.OpenFD:output_type -> glutton.OpenFDResponse + 8, // 11: glutton.Glutton.Ping:output_type -> glutton.PingResponse + 10, // 12: glutton.Glutton.Gossip:output_type -> glutton.GossipResponse + 8, // [8:13] is the sub-list for method output_type + 3, // [3:8] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name +} + +func init() { file_glutton_proto_init() } +func file_glutton_proto_init() { + if File_glutton_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_glutton_proto_rawDesc), len(file_glutton_proto_rawDesc)), + NumEnums: 1, + NumMessages: 11, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_glutton_proto_goTypes, + DependencyIndexes: file_glutton_proto_depIdxs, + EnumInfos: file_glutton_proto_enumTypes, + MessageInfos: file_glutton_proto_msgTypes, + }.Build() + File_glutton_proto = out.File + file_glutton_proto_goTypes = nil + file_glutton_proto_depIdxs = nil +} diff --git a/internal/proto/glutton/glutton.proto b/internal/proto/glutton/glutton.proto new file mode 100644 index 000000000..11dadefa1 --- /dev/null +++ b/internal/proto/glutton/glutton.proto @@ -0,0 +1,97 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package glutton; + +option go_package = "github.com/agent-substrate/substrate/internal/proto/glutton"; + +// A glutton is a single small piece of a larger benchmarking suite. It is designed to be instantiated +// multiple times, with each piece configured to do different units of work. +service Glutton { + rpc WriteRAM(WriteRAMRequest) returns (WriteRAMResponse) {} + + rpc WriteDisk(WriteDiskRequest) returns (WriteDiskResponse) {} + + rpc OpenFD(OpenFDRequest) returns (OpenFDResponse) {} + + rpc Ping(PingRequest) returns (PingResponse) {} + + rpc Gossip(GossipRequest) returns (GossipResponse) {} +} + +enum WriteMode { + // Overwrite the destination entirely + WRITE_MODE_TRUNCATE = 0; + + // Write on top of the existing data + WRITE_MODE_OVERWRITE = 1; +} + +message WriteRAMRequest { + // name of the array to be written to + string key = 1; + + // size of bytes to be written + int32 size = 2; + + WriteMode write_mode = 3; +} + +message WriteRAMResponse { +} + +message WriteDiskRequest { + // name of the file to be written to + string key = 1; + + // size of bytes to be written + int32 size = 2; + + WriteMode write_mode = 3; +} + +message WriteDiskResponse { +} + +message OpenFDRequest { + // The total number of FDs for the glutton to open + int32 count = 1; +} + +message OpenFDResponse { + +} + +message PingRequest { + // The message to be echoed back from the ping + string message = 1; +} + +message PingResponse { + // The message echoed back + string message = 1; +} + +message GossipRequest { + repeated Peer peers = 1; +} + +message GossipResponse {} + +message Peer { + string host = 1; + int32 delay_ms = 2; +} \ No newline at end of file diff --git a/internal/proto/glutton/glutton_grpc.pb.go b/internal/proto/glutton/glutton_grpc.pb.go new file mode 100644 index 000000000..2132c61c5 --- /dev/null +++ b/internal/proto/glutton/glutton_grpc.pb.go @@ -0,0 +1,293 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.6.1 +// - protoc v4.25.3 +// source: glutton.proto + +package glutton + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + Glutton_WriteRAM_FullMethodName = "/glutton.Glutton/WriteRAM" + Glutton_WriteDisk_FullMethodName = "/glutton.Glutton/WriteDisk" + Glutton_OpenFD_FullMethodName = "/glutton.Glutton/OpenFD" + Glutton_Ping_FullMethodName = "/glutton.Glutton/Ping" + Glutton_Gossip_FullMethodName = "/glutton.Glutton/Gossip" +) + +// GluttonClient is the client API for Glutton service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +// +// A glutton is a single small piece of a larger benchmarking suite. It is designed to be instantiated +// multiple times, with each piece configured to do different units of work. +type GluttonClient interface { + WriteRAM(ctx context.Context, in *WriteRAMRequest, opts ...grpc.CallOption) (*WriteRAMResponse, error) + WriteDisk(ctx context.Context, in *WriteDiskRequest, opts ...grpc.CallOption) (*WriteDiskResponse, error) + OpenFD(ctx context.Context, in *OpenFDRequest, opts ...grpc.CallOption) (*OpenFDResponse, error) + Ping(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PingResponse, error) + Gossip(ctx context.Context, in *GossipRequest, opts ...grpc.CallOption) (*GossipResponse, error) +} + +type gluttonClient struct { + cc grpc.ClientConnInterface +} + +func NewGluttonClient(cc grpc.ClientConnInterface) GluttonClient { + return &gluttonClient{cc} +} + +func (c *gluttonClient) WriteRAM(ctx context.Context, in *WriteRAMRequest, opts ...grpc.CallOption) (*WriteRAMResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(WriteRAMResponse) + err := c.cc.Invoke(ctx, Glutton_WriteRAM_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *gluttonClient) WriteDisk(ctx context.Context, in *WriteDiskRequest, opts ...grpc.CallOption) (*WriteDiskResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(WriteDiskResponse) + err := c.cc.Invoke(ctx, Glutton_WriteDisk_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *gluttonClient) OpenFD(ctx context.Context, in *OpenFDRequest, opts ...grpc.CallOption) (*OpenFDResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(OpenFDResponse) + err := c.cc.Invoke(ctx, Glutton_OpenFD_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *gluttonClient) Ping(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PingResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(PingResponse) + err := c.cc.Invoke(ctx, Glutton_Ping_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *gluttonClient) Gossip(ctx context.Context, in *GossipRequest, opts ...grpc.CallOption) (*GossipResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GossipResponse) + err := c.cc.Invoke(ctx, Glutton_Gossip_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// GluttonServer is the server API for Glutton service. +// All implementations must embed UnimplementedGluttonServer +// for forward compatibility. +// +// A glutton is a single small piece of a larger benchmarking suite. It is designed to be instantiated +// multiple times, with each piece configured to do different units of work. +type GluttonServer interface { + WriteRAM(context.Context, *WriteRAMRequest) (*WriteRAMResponse, error) + WriteDisk(context.Context, *WriteDiskRequest) (*WriteDiskResponse, error) + OpenFD(context.Context, *OpenFDRequest) (*OpenFDResponse, error) + Ping(context.Context, *PingRequest) (*PingResponse, error) + Gossip(context.Context, *GossipRequest) (*GossipResponse, error) + mustEmbedUnimplementedGluttonServer() +} + +// UnimplementedGluttonServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedGluttonServer struct{} + +func (UnimplementedGluttonServer) WriteRAM(context.Context, *WriteRAMRequest) (*WriteRAMResponse, error) { + return nil, status.Error(codes.Unimplemented, "method WriteRAM not implemented") +} +func (UnimplementedGluttonServer) WriteDisk(context.Context, *WriteDiskRequest) (*WriteDiskResponse, error) { + return nil, status.Error(codes.Unimplemented, "method WriteDisk not implemented") +} +func (UnimplementedGluttonServer) OpenFD(context.Context, *OpenFDRequest) (*OpenFDResponse, error) { + return nil, status.Error(codes.Unimplemented, "method OpenFD not implemented") +} +func (UnimplementedGluttonServer) Ping(context.Context, *PingRequest) (*PingResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Ping not implemented") +} +func (UnimplementedGluttonServer) Gossip(context.Context, *GossipRequest) (*GossipResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Gossip not implemented") +} +func (UnimplementedGluttonServer) mustEmbedUnimplementedGluttonServer() {} +func (UnimplementedGluttonServer) testEmbeddedByValue() {} + +// UnsafeGluttonServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to GluttonServer will +// result in compilation errors. +type UnsafeGluttonServer interface { + mustEmbedUnimplementedGluttonServer() +} + +func RegisterGluttonServer(s grpc.ServiceRegistrar, srv GluttonServer) { + // If the following call panics, it indicates UnimplementedGluttonServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&Glutton_ServiceDesc, srv) +} + +func _Glutton_WriteRAM_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(WriteRAMRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GluttonServer).WriteRAM(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Glutton_WriteRAM_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GluttonServer).WriteRAM(ctx, req.(*WriteRAMRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Glutton_WriteDisk_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(WriteDiskRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GluttonServer).WriteDisk(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Glutton_WriteDisk_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GluttonServer).WriteDisk(ctx, req.(*WriteDiskRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Glutton_OpenFD_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(OpenFDRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GluttonServer).OpenFD(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Glutton_OpenFD_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GluttonServer).OpenFD(ctx, req.(*OpenFDRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Glutton_Ping_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PingRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GluttonServer).Ping(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Glutton_Ping_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GluttonServer).Ping(ctx, req.(*PingRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Glutton_Gossip_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GossipRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GluttonServer).Gossip(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Glutton_Gossip_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GluttonServer).Gossip(ctx, req.(*GossipRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// Glutton_ServiceDesc is the grpc.ServiceDesc for Glutton service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Glutton_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "glutton.Glutton", + HandlerType: (*GluttonServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "WriteRAM", + Handler: _Glutton_WriteRAM_Handler, + }, + { + MethodName: "WriteDisk", + Handler: _Glutton_WriteDisk_Handler, + }, + { + MethodName: "OpenFD", + Handler: _Glutton_OpenFD_Handler, + }, + { + MethodName: "Ping", + Handler: _Glutton_Ping_Handler, + }, + { + MethodName: "Gossip", + Handler: _Glutton_Gossip_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "glutton.proto", +}