From 01a98603fb98d6e7c596ba6de0722164e3b110f4 Mon Sep 17 00:00:00 2001 From: John OHara Date: Mon, 4 Apr 2022 22:03:57 +0100 Subject: [PATCH 01/16] Added gRPC endpoint and python client --- src/README.md | 42 +++ src/__init__.py | 0 src/gRPC/__init__.py | 0 src/gRPC/hpo_pb2.py | 162 +++++++++++ src/gRPC/hpo_pb2_grpc.py | 267 ++++++++++++++++++ src/gRPC/protos/hpo.proto | 123 ++++++++ src/grpc_client.py | 135 +++++++++ src/grpc_service.py | 123 ++++++++ src/hpo_service.py | 6 +- src/rest_service.py | 182 ++++++++++++ src/service.py | 169 +---------- .../searchspace_jsons/newExperiment.json | 27 ++ 12 files changed, 1074 insertions(+), 162 deletions(-) create mode 100644 src/__init__.py create mode 100644 src/gRPC/__init__.py create mode 100644 src/gRPC/hpo_pb2.py create mode 100644 src/gRPC/hpo_pb2_grpc.py create mode 100644 src/gRPC/protos/hpo.proto create mode 100644 src/grpc_client.py create mode 100644 src/grpc_service.py create mode 100644 src/rest_service.py create mode 100644 tests/resources/searchspace_jsons/newExperiment.json diff --git a/src/README.md b/src/README.md index c5d8437..eacca0a 100644 --- a/src/README.md +++ b/src/README.md @@ -46,3 +46,45 @@ The criteria for setting the experiment status is: | `success` | The experiment runs successfully without any error. | | `failure` | The experiment fails due to reason such as OOMKilled. | | `prune` | The experiment terminates due to reasons such as insufficient cpu and memory. | + + +## gRPC Client + +[`grpc_client.py`](./grpc_client.py) is a command line client that allows users to interact with the gRPC service. + +### Usage + +```shell +$ python3 ./grpc_client.py +Usage: grpc_client.py [OPTIONS] COMMAND [ARGS]... + + A HPO command line tool to allow interaction with HPO service + +Options: + --help Show this message and exit. + +Commands: + config Obtain a configuration set for a particular experiment trail + count Return a count of experiments currently running + list List names of all experiments currently running + new Create a new experiment + next Generate next configuration set for running experiment + result Update results for a particular experiment trail + show Show details of running experiment + +``` + +Commands provide interactive input for params or allow users to set params on the command line for scripted use; + +e.g. + +```shell +$ python3 ./grpc_client.py new + Experiment configuration file path: +``` + +or + +```shell +$ python3 ./grpc_client.py new --file=/tmp/hpo/newExperiment.json +``` diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/gRPC/__init__.py b/src/gRPC/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/gRPC/hpo_pb2.py b/src/gRPC/hpo_pb2.py new file mode 100644 index 0000000..82166e5 --- /dev/null +++ b/src/gRPC/hpo_pb2.py @@ -0,0 +1,162 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: hpo.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\thpo.proto\x12\nhelloworld\"\'\n\x16NumberExperimentsReply\x12\r\n\x05\x63ount\x18\x01 \x01(\x05\"+\n\x13NewExperimentsReply\x12\x14\n\x0ctrial_number\x18\x01 \x01(\x05\"*\n\x14\x45xperimentsListReply\x12\x12\n\nexperiment\x18\x01 \x03(\t\"\x19\n\x17NumberExperimentsParams\"\x17\n\x15\x45xperimentsListParams\"\x16\n\x14\x45xperimentTrialReply\"/\n\x14\x45xperimentNameParams\x12\x17\n\x0f\x65xperiment_name\x18\x01 \x01(\t\"9\n\x0f\x45xperimentTrial\x12\x17\n\x0f\x65xperiment_name\x18\x01 \x01(\t\x12\r\n\x05trial\x18\x02 \x01(\x05\"\xbe\x01\n\x15\x45xperimentTrialResult\x12\x15\n\rexperiment_id\x18\x01 \x01(\t\x12\r\n\x05trial\x18\x02 \x01(\x05\x12\x38\n\x06result\x18\x03 \x01(\x0e\x32(.helloworld.ExperimentTrialResult.Result\x12\x12\n\nvalue_type\x18\x04 \x01(\t\x12\r\n\x05value\x18\x05 \x01(\x01\"\"\n\x06Result\x12\x0b\n\x07SUCCESS\x10\x00\x12\x0b\n\x07\x46\x41ILURE\x10\x01\"\x85\x03\n\x11\x45xperimentDetails\x12\x17\n\x0f\x65xperiment_name\x18\x01 \x01(\t\x12\x14\n\x0ctotal_trials\x18\x02 \x01(\x05\x12\x17\n\x0fparallel_trials\x18\x03 \x01(\x05\x12\x11\n\tdirection\x18\x04 \x01(\t\x12\x15\n\rhpo_algo_impl\x18\x05 \x01(\t\x12\x0b\n\x03id_\x18\x06 \x01(\t\x12\x1a\n\x12objective_function\x18\x07 \x01(\t\x12\x38\n\ttuneables\x18\x08 \x03(\x0b\x32%.helloworld.ExperimentDetails.Tunable\x12\x12\n\nvalue_type\x18\t \x01(\t\x12\x11\n\tslo_class\x18\n \x01(\t\x12\x0f\n\x07started\x18\x0b \x01(\x08\x1a\x63\n\x07Tunable\x12\x12\n\nvalue_type\x18\x01 \x01(\t\x12\x13\n\x0blower_bound\x18\x02 \x01(\x05\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x13\n\x0bupper_bound\x18\x04 \x01(\x05\x12\x0c\n\x04step\x18\x05 \x01(\x01\",\n\rTunableConfig\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x02\"8\n\x0bTrialConfig\x12)\n\x06\x63onfig\x18\x01 \x03(\x0b\x32\x19.helloworld.TunableConfig2\xf5\x04\n\nHpoService\x12^\n\x11NumberExperiments\x12#.helloworld.NumberExperimentsParams\x1a\".helloworld.NumberExperimentsReply\"\x00\x12X\n\x0f\x45xperimentsList\x12!.helloworld.ExperimentsListParams\x1a .helloworld.ExperimentsListReply\"\x00\x12Q\n\rNewExperiment\x12\x1d.helloworld.ExperimentDetails\x1a\x1f.helloworld.NewExperimentsReply\"\x00\x12Y\n\x14GetExperimentDetails\x12 .helloworld.ExperimentNameParams\x1a\x1d.helloworld.ExperimentDetails\"\x00\x12H\n\x0eGetTrialConfig\x12\x1b.helloworld.ExperimentTrial\x1a\x17.helloworld.TrialConfig\"\x00\x12Z\n\x11UpdateTrialResult\x12!.helloworld.ExperimentTrialResult\x1a .helloworld.ExperimentTrialReply\"\x00\x12Y\n\x12GenerateNextConfig\x12 .helloworld.ExperimentNameParams\x1a\x1f.helloworld.NewExperimentsReply\"\x00\x42#\n\rio.kruize.hpoB\nHpoServiceP\x01\xa2\x02\x03HLWb\x06proto3') + + + +_NUMBEREXPERIMENTSREPLY = DESCRIPTOR.message_types_by_name['NumberExperimentsReply'] +_NEWEXPERIMENTSREPLY = DESCRIPTOR.message_types_by_name['NewExperimentsReply'] +_EXPERIMENTSLISTREPLY = DESCRIPTOR.message_types_by_name['ExperimentsListReply'] +_NUMBEREXPERIMENTSPARAMS = DESCRIPTOR.message_types_by_name['NumberExperimentsParams'] +_EXPERIMENTSLISTPARAMS = DESCRIPTOR.message_types_by_name['ExperimentsListParams'] +_EXPERIMENTTRIALREPLY = DESCRIPTOR.message_types_by_name['ExperimentTrialReply'] +_EXPERIMENTNAMEPARAMS = DESCRIPTOR.message_types_by_name['ExperimentNameParams'] +_EXPERIMENTTRIAL = DESCRIPTOR.message_types_by_name['ExperimentTrial'] +_EXPERIMENTTRIALRESULT = DESCRIPTOR.message_types_by_name['ExperimentTrialResult'] +_EXPERIMENTDETAILS = DESCRIPTOR.message_types_by_name['ExperimentDetails'] +_EXPERIMENTDETAILS_TUNABLE = _EXPERIMENTDETAILS.nested_types_by_name['Tunable'] +_TUNABLECONFIG = DESCRIPTOR.message_types_by_name['TunableConfig'] +_TRIALCONFIG = DESCRIPTOR.message_types_by_name['TrialConfig'] +_EXPERIMENTTRIALRESULT_RESULT = _EXPERIMENTTRIALRESULT.enum_types_by_name['Result'] +NumberExperimentsReply = _reflection.GeneratedProtocolMessageType('NumberExperimentsReply', (_message.Message,), { + 'DESCRIPTOR' : _NUMBEREXPERIMENTSREPLY, + '__module__' : 'hpo_pb2' + # @@protoc_insertion_point(class_scope:helloworld.NumberExperimentsReply) + }) +_sym_db.RegisterMessage(NumberExperimentsReply) + +NewExperimentsReply = _reflection.GeneratedProtocolMessageType('NewExperimentsReply', (_message.Message,), { + 'DESCRIPTOR' : _NEWEXPERIMENTSREPLY, + '__module__' : 'hpo_pb2' + # @@protoc_insertion_point(class_scope:helloworld.NewExperimentsReply) + }) +_sym_db.RegisterMessage(NewExperimentsReply) + +ExperimentsListReply = _reflection.GeneratedProtocolMessageType('ExperimentsListReply', (_message.Message,), { + 'DESCRIPTOR' : _EXPERIMENTSLISTREPLY, + '__module__' : 'hpo_pb2' + # @@protoc_insertion_point(class_scope:helloworld.ExperimentsListReply) + }) +_sym_db.RegisterMessage(ExperimentsListReply) + +NumberExperimentsParams = _reflection.GeneratedProtocolMessageType('NumberExperimentsParams', (_message.Message,), { + 'DESCRIPTOR' : _NUMBEREXPERIMENTSPARAMS, + '__module__' : 'hpo_pb2' + # @@protoc_insertion_point(class_scope:helloworld.NumberExperimentsParams) + }) +_sym_db.RegisterMessage(NumberExperimentsParams) + +ExperimentsListParams = _reflection.GeneratedProtocolMessageType('ExperimentsListParams', (_message.Message,), { + 'DESCRIPTOR' : _EXPERIMENTSLISTPARAMS, + '__module__' : 'hpo_pb2' + # @@protoc_insertion_point(class_scope:helloworld.ExperimentsListParams) + }) +_sym_db.RegisterMessage(ExperimentsListParams) + +ExperimentTrialReply = _reflection.GeneratedProtocolMessageType('ExperimentTrialReply', (_message.Message,), { + 'DESCRIPTOR' : _EXPERIMENTTRIALREPLY, + '__module__' : 'hpo_pb2' + # @@protoc_insertion_point(class_scope:helloworld.ExperimentTrialReply) + }) +_sym_db.RegisterMessage(ExperimentTrialReply) + +ExperimentNameParams = _reflection.GeneratedProtocolMessageType('ExperimentNameParams', (_message.Message,), { + 'DESCRIPTOR' : _EXPERIMENTNAMEPARAMS, + '__module__' : 'hpo_pb2' + # @@protoc_insertion_point(class_scope:helloworld.ExperimentNameParams) + }) +_sym_db.RegisterMessage(ExperimentNameParams) + +ExperimentTrial = _reflection.GeneratedProtocolMessageType('ExperimentTrial', (_message.Message,), { + 'DESCRIPTOR' : _EXPERIMENTTRIAL, + '__module__' : 'hpo_pb2' + # @@protoc_insertion_point(class_scope:helloworld.ExperimentTrial) + }) +_sym_db.RegisterMessage(ExperimentTrial) + +ExperimentTrialResult = _reflection.GeneratedProtocolMessageType('ExperimentTrialResult', (_message.Message,), { + 'DESCRIPTOR' : _EXPERIMENTTRIALRESULT, + '__module__' : 'hpo_pb2' + # @@protoc_insertion_point(class_scope:helloworld.ExperimentTrialResult) + }) +_sym_db.RegisterMessage(ExperimentTrialResult) + +ExperimentDetails = _reflection.GeneratedProtocolMessageType('ExperimentDetails', (_message.Message,), { + + 'Tunable' : _reflection.GeneratedProtocolMessageType('Tunable', (_message.Message,), { + 'DESCRIPTOR' : _EXPERIMENTDETAILS_TUNABLE, + '__module__' : 'hpo_pb2' + # @@protoc_insertion_point(class_scope:helloworld.ExperimentDetails.Tunable) + }) + , + 'DESCRIPTOR' : _EXPERIMENTDETAILS, + '__module__' : 'hpo_pb2' + # @@protoc_insertion_point(class_scope:helloworld.ExperimentDetails) + }) +_sym_db.RegisterMessage(ExperimentDetails) +_sym_db.RegisterMessage(ExperimentDetails.Tunable) + +TunableConfig = _reflection.GeneratedProtocolMessageType('TunableConfig', (_message.Message,), { + 'DESCRIPTOR' : _TUNABLECONFIG, + '__module__' : 'hpo_pb2' + # @@protoc_insertion_point(class_scope:helloworld.TunableConfig) + }) +_sym_db.RegisterMessage(TunableConfig) + +TrialConfig = _reflection.GeneratedProtocolMessageType('TrialConfig', (_message.Message,), { + 'DESCRIPTOR' : _TRIALCONFIG, + '__module__' : 'hpo_pb2' + # @@protoc_insertion_point(class_scope:helloworld.TrialConfig) + }) +_sym_db.RegisterMessage(TrialConfig) + +_HPOSERVICE = DESCRIPTOR.services_by_name['HpoService'] +if _descriptor._USE_C_DESCRIPTORS == False: + + DESCRIPTOR._options = None + DESCRIPTOR._serialized_options = b'\n\rio.kruize.hpoB\nHpoServiceP\001\242\002\003HLW' + _NUMBEREXPERIMENTSREPLY._serialized_start=25 + _NUMBEREXPERIMENTSREPLY._serialized_end=64 + _NEWEXPERIMENTSREPLY._serialized_start=66 + _NEWEXPERIMENTSREPLY._serialized_end=109 + _EXPERIMENTSLISTREPLY._serialized_start=111 + _EXPERIMENTSLISTREPLY._serialized_end=153 + _NUMBEREXPERIMENTSPARAMS._serialized_start=155 + _NUMBEREXPERIMENTSPARAMS._serialized_end=180 + _EXPERIMENTSLISTPARAMS._serialized_start=182 + _EXPERIMENTSLISTPARAMS._serialized_end=205 + _EXPERIMENTTRIALREPLY._serialized_start=207 + _EXPERIMENTTRIALREPLY._serialized_end=229 + _EXPERIMENTNAMEPARAMS._serialized_start=231 + _EXPERIMENTNAMEPARAMS._serialized_end=278 + _EXPERIMENTTRIAL._serialized_start=280 + _EXPERIMENTTRIAL._serialized_end=337 + _EXPERIMENTTRIALRESULT._serialized_start=340 + _EXPERIMENTTRIALRESULT._serialized_end=530 + _EXPERIMENTTRIALRESULT_RESULT._serialized_start=496 + _EXPERIMENTTRIALRESULT_RESULT._serialized_end=530 + _EXPERIMENTDETAILS._serialized_start=533 + _EXPERIMENTDETAILS._serialized_end=922 + _EXPERIMENTDETAILS_TUNABLE._serialized_start=823 + _EXPERIMENTDETAILS_TUNABLE._serialized_end=922 + _TUNABLECONFIG._serialized_start=924 + _TUNABLECONFIG._serialized_end=968 + _TRIALCONFIG._serialized_start=970 + _TRIALCONFIG._serialized_end=1026 + _HPOSERVICE._serialized_start=1029 + _HPOSERVICE._serialized_end=1658 +# @@protoc_insertion_point(module_scope) diff --git a/src/gRPC/hpo_pb2_grpc.py b/src/gRPC/hpo_pb2_grpc.py new file mode 100644 index 0000000..e2ffdce --- /dev/null +++ b/src/gRPC/hpo_pb2_grpc.py @@ -0,0 +1,267 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + +from . import hpo_pb2 as hpo__pb2 + + +class HpoServiceStub(object): + """The hpo service definition. + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.NumberExperiments = channel.unary_unary( + '/helloworld.HpoService/NumberExperiments', + request_serializer=hpo__pb2.NumberExperimentsParams.SerializeToString, + response_deserializer=hpo__pb2.NumberExperimentsReply.FromString, + ) + self.ExperimentsList = channel.unary_unary( + '/helloworld.HpoService/ExperimentsList', + request_serializer=hpo__pb2.ExperimentsListParams.SerializeToString, + response_deserializer=hpo__pb2.ExperimentsListReply.FromString, + ) + self.NewExperiment = channel.unary_unary( + '/helloworld.HpoService/NewExperiment', + request_serializer=hpo__pb2.ExperimentDetails.SerializeToString, + response_deserializer=hpo__pb2.NewExperimentsReply.FromString, + ) + self.GetExperimentDetails = channel.unary_unary( + '/helloworld.HpoService/GetExperimentDetails', + request_serializer=hpo__pb2.ExperimentNameParams.SerializeToString, + response_deserializer=hpo__pb2.ExperimentDetails.FromString, + ) + self.GetTrialConfig = channel.unary_unary( + '/helloworld.HpoService/GetTrialConfig', + request_serializer=hpo__pb2.ExperimentTrial.SerializeToString, + response_deserializer=hpo__pb2.TrialConfig.FromString, + ) + self.UpdateTrialResult = channel.unary_unary( + '/helloworld.HpoService/UpdateTrialResult', + request_serializer=hpo__pb2.ExperimentTrialResult.SerializeToString, + response_deserializer=hpo__pb2.ExperimentTrialReply.FromString, + ) + self.GenerateNextConfig = channel.unary_unary( + '/helloworld.HpoService/GenerateNextConfig', + request_serializer=hpo__pb2.ExperimentNameParams.SerializeToString, + response_deserializer=hpo__pb2.NewExperimentsReply.FromString, + ) + + +class HpoServiceServicer(object): + """The hpo service definition. + """ + + def NumberExperiments(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 ExperimentsList(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 NewExperiment(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 GetExperimentDetails(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 GetTrialConfig(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 UpdateTrialResult(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 GenerateNextConfig(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_HpoServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'NumberExperiments': grpc.unary_unary_rpc_method_handler( + servicer.NumberExperiments, + request_deserializer=hpo__pb2.NumberExperimentsParams.FromString, + response_serializer=hpo__pb2.NumberExperimentsReply.SerializeToString, + ), + 'ExperimentsList': grpc.unary_unary_rpc_method_handler( + servicer.ExperimentsList, + request_deserializer=hpo__pb2.ExperimentsListParams.FromString, + response_serializer=hpo__pb2.ExperimentsListReply.SerializeToString, + ), + 'NewExperiment': grpc.unary_unary_rpc_method_handler( + servicer.NewExperiment, + request_deserializer=hpo__pb2.ExperimentDetails.FromString, + response_serializer=hpo__pb2.NewExperimentsReply.SerializeToString, + ), + 'GetExperimentDetails': grpc.unary_unary_rpc_method_handler( + servicer.GetExperimentDetails, + request_deserializer=hpo__pb2.ExperimentNameParams.FromString, + response_serializer=hpo__pb2.ExperimentDetails.SerializeToString, + ), + 'GetTrialConfig': grpc.unary_unary_rpc_method_handler( + servicer.GetTrialConfig, + request_deserializer=hpo__pb2.ExperimentTrial.FromString, + response_serializer=hpo__pb2.TrialConfig.SerializeToString, + ), + 'UpdateTrialResult': grpc.unary_unary_rpc_method_handler( + servicer.UpdateTrialResult, + request_deserializer=hpo__pb2.ExperimentTrialResult.FromString, + response_serializer=hpo__pb2.ExperimentTrialReply.SerializeToString, + ), + 'GenerateNextConfig': grpc.unary_unary_rpc_method_handler( + servicer.GenerateNextConfig, + request_deserializer=hpo__pb2.ExperimentNameParams.FromString, + response_serializer=hpo__pb2.NewExperimentsReply.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'helloworld.HpoService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class HpoService(object): + """The hpo service definition. + """ + + @staticmethod + def NumberExperiments(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, '/helloworld.HpoService/NumberExperiments', + hpo__pb2.NumberExperimentsParams.SerializeToString, + hpo__pb2.NumberExperimentsReply.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def ExperimentsList(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, '/helloworld.HpoService/ExperimentsList', + hpo__pb2.ExperimentsListParams.SerializeToString, + hpo__pb2.ExperimentsListReply.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def NewExperiment(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, '/helloworld.HpoService/NewExperiment', + hpo__pb2.ExperimentDetails.SerializeToString, + hpo__pb2.NewExperimentsReply.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def GetExperimentDetails(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, '/helloworld.HpoService/GetExperimentDetails', + hpo__pb2.ExperimentNameParams.SerializeToString, + hpo__pb2.ExperimentDetails.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def GetTrialConfig(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, '/helloworld.HpoService/GetTrialConfig', + hpo__pb2.ExperimentTrial.SerializeToString, + hpo__pb2.TrialConfig.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def UpdateTrialResult(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, '/helloworld.HpoService/UpdateTrialResult', + hpo__pb2.ExperimentTrialResult.SerializeToString, + hpo__pb2.ExperimentTrialReply.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def GenerateNextConfig(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, '/helloworld.HpoService/GenerateNextConfig', + hpo__pb2.ExperimentNameParams.SerializeToString, + hpo__pb2.NewExperimentsReply.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/src/gRPC/protos/hpo.proto b/src/gRPC/protos/hpo.proto new file mode 100644 index 0000000..5109a1e --- /dev/null +++ b/src/gRPC/protos/hpo.proto @@ -0,0 +1,123 @@ +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + + +option java_multiple_files = true; +option java_package = "io.kruize.hpo"; +option java_outer_classname = "HpoService"; +option objc_class_prefix = "HLW"; + +package helloworld; + +// The hpo service definition. +service HpoService { + rpc NumberExperiments(NumberExperimentsParams) returns (NumberExperimentsReply) {} + rpc ExperimentsList(ExperimentsListParams) returns (ExperimentsListReply) {} + rpc NewExperiment(ExperimentDetails) returns (NewExperimentsReply) {} + rpc GetExperimentDetails(ExperimentNameParams) returns (ExperimentDetails) {} + rpc GetTrialConfig(ExperimentTrial) returns (TrialConfig) {} + rpc UpdateTrialResult(ExperimentTrialResult) returns (ExperimentTrialReply) {} + rpc GenerateNextConfig(ExperimentNameParams) returns (NewExperimentsReply) {} +} + +message NumberExperimentsReply { + int32 count = 1; +} + +message NewExperimentsReply { + int32 trial_number = 1; +} + +message ExperimentsListReply { + repeated string experiment = 1; +} + +message NumberExperimentsParams {} + +message ExperimentsListParams {} + +message ExperimentTrialReply{} + +message ExperimentNameParams { + string experiment_name = 1; +} + +message ExperimentTrial { + string experiment_name = 1; + int32 trial = 2; +} + +message ExperimentTrialResult{ + enum Result { + SUCCESS = 0; + FAILURE = 1; + PRUNE = 2; + } + + string experiment_id = 1; + int32 trial = 2; + Result result = 3; + string value_type = 4; + double value = 5; +} + +message ExperimentDetails { + message Tunable{ + string value_type = 1; + int32 lower_bound = 2; + string name = 3; + int32 upper_bound = 4; + double step = 5; + } + + string experiment_name = 1; + int32 total_trials = 2; + int32 parallel_trials = 3; + string direction = 4; + string hpo_algo_impl = 5; + string id_ = 6; + string objective_function= 7; + repeated Tunable tuneables = 8; + string value_type= 9; + string slo_class = 10; + bool started = 11; +} + +message TunableConfig { + string name = 1; + float value = 2; +} + +message TrialConfig { + repeated TunableConfig config = 1; +} + + diff --git a/src/grpc_client.py b/src/grpc_client.py new file mode 100644 index 0000000..fa23658 --- /dev/null +++ b/src/grpc_client.py @@ -0,0 +1,135 @@ +# Copyright 2015 gRPC authors. +# +# 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. +"""The Python implementation of the GRPC helloworld.Greeter client.""" + +from __future__ import print_function + +import logging +import click +import json + +import grpc +from gRPC import hpo_pb2_grpc, hpo_pb2 +from google.protobuf.json_format import MessageToJson +from google.protobuf.json_format import Parse, ParseDict + + +@click.group() +def main(): + """A HPO command line tool to allow interaction with HPO service""" + logging.basicConfig() + pass + +@main.command() +def count(): + """Return a count of experiments currently running""" + empty = hpo_pb2.NumberExperimentsReply() + fun = lambda stub : stub.NumberExperiments(empty) + response = run(fun) + click.echo(" Number of running experiments: {}".format(response.count)) + +@main.command() +def list(): + """List names of all experiments currently running""" + empty = hpo_pb2.NumberExperimentsReply() + fun = lambda stub : stub.ExperimentsList(empty) + experiments: hpo_pb2.ExperimentsListReply = run(fun) + print("Running Experiments:") + for experiment in experiments.experiment: + click.echo(" %s" % experiment) + +@main.command() +@click.option("--name", prompt=" Experiment name", type=str) +def show(name): + """Show details of running experiment""" + expr: hpo_pb2.ExperimentNameParams = hpo_pb2.ExperimentNameParams() + expr.experiment_name = name + fun = lambda stub : stub.GetExperimentDetails(expr) + experiment: hpo_pb2.ExperimentDetails = run(fun) + json_obj = MessageToJson(experiment) + click.echo("Experiment Details:") + click.echo(json_obj) + +@main.command() +@click.option("--file", prompt=" Experiment configuration file path", type=str) +def new(file): + """Create a new experiment""" + # TODO: validate file path + with open(file, 'r') as json_file: + data = json.load(json_file) + # data = file.read().replace('\n', '') + message: hpo_pb2.ExperimentDetails = ParseDict(data, hpo_pb2.ExperimentDetails()) + click.echo(" Adding new experiment: {}".format(message.experiment_name)) + fun = lambda stub : stub.NewExperiment(message) + response: hpo_pb2.NewExperimentsReply = run(fun) + click.echo("Trial Number: {}".format(response.trial_number)) + +@main.command() +@click.option("--name", prompt=" Experiment name", type=str) +@click.option("--trial", prompt=" Trial number", type=int) +def config(name, trial): + """Obtain a configuration set for a particular experiment trail""" + expr: hpo_pb2.ExperimentTrial = hpo_pb2.ExperimentTrial() + expr.experiment_name = name + expr.trial = trial + fun = lambda stub : stub.GetTrialConfig(expr) + trial_config: hpo_pb2.TrialConfig = run(fun) + json_obj = MessageToJson(trial_config) + click.echo("Trial Config:") + click.echo(json_obj) + +@main.command() +@click.option("--name", prompt=" Enter name", type=str) +@click.option("--trial", prompt=" Enter trial number", type=int) +@click.option("--result", prompt=" Enter trial result", type=str) +@click.option("--value_type", prompt=" Enter result type", type=str) +@click.option("--value", prompt=" Enter result value", type=float) +def result(name, trial, result, value_type, value): + """Update results for a particular experiment trail""" + trialResult: hpo_pb2.ExperimentTrialResult = hpo_pb2.ExperimentTrialResult() + trialResult.experiment_id = name + trialResult.trial = trial + trialResult.result = hpo_pb2._EXPERIMENTTRIALRESULT_RESULT.values_by_name[result].number + trialResult.value_type = value_type + trialResult.value = value + fun = lambda stub : stub.UpdateTrialResult(trialResult) + hpo_pb2.TrialConfig = run(fun) + click.echo("Updated Trial Result") + +@main.command() +@click.option("--name", prompt=" Enter name", type=str) +def next(name): + """Generate next configuration set for running experiment""" + experiment: hpo_pb2.ExperimentNameParams = hpo_pb2.ExperimentNameParams() + experiment.experiment_name = name + fun = lambda stub : stub.GenerateNextConfig(experiment) + reply: hpo_pb2.NewExperimentsReply = run(fun) + click.echo("Next Trial: {}".format(reply.trial_number)) + +def run(func): + # NOTE(gRPC Python Team): .close() is possible on a channel and should be + # used in circumstances in which the with statement does not fit the needs + # of the code. + with grpc.insecure_channel('localhost:50051') as channel: + stub = hpo_pb2_grpc.HpoServiceStub(channel) + return func(stub) + +def NewExperiment(stub, **args): + empty = hpo_pb2.NumberExperimentsReply() + response = stub.NumberExperiments(empty) + print("HpoService client received: %s" % response.count) + + +if __name__ == "__main__": + main() diff --git a/src/grpc_service.py b/src/grpc_service.py new file mode 100644 index 0000000..0065b83 --- /dev/null +++ b/src/grpc_service.py @@ -0,0 +1,123 @@ +# Copyright 2015 gRPC authors. +# +# 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. +"""The Python implementation of the GRPC helloworld.Greeter server.""" + +from concurrent import futures +import logging + +import grpc +import json +from gRPC import hpo_pb2, hpo_pb2_grpc +import hpo_service +from google.protobuf.json_format import MessageToJson +from bayes_optuna.optuna_hpo import HpoExperiment +from gRPC.hpo_pb2 import NewExperimentsReply + + +class HpoService(hpo_pb2_grpc.HpoServiceServicer): + + def NumberExperiments(self, request, context): + return hpo_pb2.NumberExperimentsReply(count=len(hpo_service.instance.experiments)) + + def ExperimentsList(self, request, context): + experiments = hpo_service.instance.getExperimentsList() + reply = hpo_pb2.ExperimentsListReply() + for experiment in experiments: + reply.experiment.extend([experiment]) + context.set_code(grpc.StatusCode.OK) + return reply + + def GetExperimentDetails(self, request, context): + experiment: HpoExperiment = hpo_service.instance.getExperiment(request.experiment_name) + reply = hpo_pb2.ExperimentDetails() + reply.experiment_name = experiment.experiment_name + reply.direction = experiment.direction + reply.hpo_algo_impl = experiment.hpo_algo_impl + reply.id_ = experiment.id_ + reply.objective_function = experiment.objective_function + # TODO:: expand tunables message + # reply.tunables = experiment.tunables + reply.started = experiment.started + context.set_code(grpc.StatusCode.OK) + return reply + + + def NewExperiment(self, request, context): + if request.hpo_algo_impl in ("optuna_tpe", "optuna_tpe_multivariate", "optuna_skopt"): + tuneables = [] + for tuneable in request.tuneables: + tuneables.append(json.loads(MessageToJson(tuneable, preserving_proto_field_name=True))) + hpo_service.instance.newExperiment(request.id_, request.experiment_name, + request.total_trials, request.parallel_trials, + request.direction, request.hpo_algo_impl, + request.objective_function, + tuneables, request.value_type) + hpo_service.instance.startExperiment(request.id_) + experiment: HpoExperiment = hpo_service.instance.getExperiment(request.id_) + reply: NewExperimentsReply = NewExperimentsReply() + reply.trial_number = experiment.trialDetails.trial_number + context.set_code(grpc.StatusCode.OK) + return reply + else: + context.set_code(grpc.StatusCode.ABORTED) + context.set_details('Invalid algorithm: %s' % request.hpo_algo_impl) + return + + def GetTrialConfig(self, request, context): + # if ("experiment_id" in query and "trial_number" in query and hpo_service.instance.containsExperiment(query["experiment_id"][0]) and + # query["trial_number"][0] == str(hpo_service.instance.get_trial_number(query["experiment_id"][0]))): + data = hpo_service.instance.get_trial_json_object(request.experiment_name) + trialConfig : hpo_pb2.TrialConfig = hpo_pb2.TrialConfig() + for config in data: + tunable: hpo_pb2.TunableConfig = hpo_pb2.TunableConfig() + tunable.name = config['tunable_name'] + tunable.value = config['tunable_value'] + trialConfig.config.extend([tunable]) + + context.set_code(grpc.StatusCode.OK) + return trialConfig + + def UpdateTrialResult(self, request, context): + if (hpo_service.instance.containsExperiment(request.experiment_id) and + request.trial == hpo_service.instance.get_trial_number(request.experiment_id)): + hpo_service.instance.set_result(request.experiment_id, + request.result, + request.value_type, + request.value) + context.set_code(grpc.StatusCode.OK) + return hpo_pb2.ExperimentTrialReply() + else: + context.set_code(grpc.StatusCode.ABORTED) + context.set_details('Experiment not found or invalid trial number!') + + def GenerateNextConfig(self, request, context): + trial_number = hpo_service.instance.get_trial_number(request.experiment_name) + reply : NewExperimentsReply = NewExperimentsReply() + reply.trial_number = trial_number + context.set_code(grpc.StatusCode.OK) + return reply + +def serve(): + server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) + hpo_pb2_grpc.add_HpoServiceServicer_to_server(HpoService(), server) + server.add_insecure_port('[::]:50051') + print("Starting gRPC server at http://%s:%s" % ("::", "50051")) + + server.start() + server.wait_for_termination() + + +if __name__ == '__main__': + logging.basicConfig() + serve() diff --git a/src/hpo_service.py b/src/hpo_service.py index c2ecb52..670faa7 100644 --- a/src/hpo_service.py +++ b/src/hpo_service.py @@ -38,6 +38,9 @@ def containsExperiment(self, name): def doesNotContainExperiment(self, name): return not self.containsExperiment(name) + def getExperimentsList(self): + return self.experiments.keys() + def getExperiment(self, name) -> optuna_hpo.HpoExperiment: if self.doesNotContainExperiment(name): print("Experiment does not exist") @@ -65,10 +68,9 @@ def get_trial_json_object(self, id_): if experiment.hpo_algo_impl in ("optuna_tpe", "optuna_tpe_multivariate", "optuna_skopt"): try: experiment.resultsAvailableCond.acquire() - trial_json_object = json.dumps(experiment.trialDetails.trial_json_object) + return json.loads(json.dumps(experiment.trialDetails.trial_json_object)) finally: experiment.resultsAvailableCond.release() - return trial_json_object def set_result(self, id_, trial_result, result_value_type, result_value): diff --git a/src/rest_service.py b/src/rest_service.py new file mode 100644 index 0000000..b8617f8 --- /dev/null +++ b/src/rest_service.py @@ -0,0 +1,182 @@ +""" +Copyright (c) 2020, 2022 Red Hat, IBM Corporation and others. + +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 http.server import BaseHTTPRequestHandler, HTTPServer +import re +import cgi +import json +import requests +import os +from urllib.parse import urlparse, parse_qs + +from json_validate import validate_trial_generate_json +from tunables import get_all_tunables +from logger import get_logger + +import hpo_service + +logger = get_logger(__name__) + +n_trials = 10 +n_jobs = 1 +autotune_object_ids = {} +search_space_json = [] + +api_endpoint = "/experiment_trials" +host_name="0.0.0.0" +server_port = 8085 + +fileDir = os.path.dirname(os.path.realpath('index.html')) +filename = os.path.join(fileDir, 'index.html') +welcome_page=filename + + +class HTTPRequestHandler(BaseHTTPRequestHandler): + """ + A class used to handle the HTTP requests that arrive at the server. + + The handler will parse the request and the headers, then call a method specific to the request type. The method name + is constructed from the request. For example, for the request method GET, the do_GET() method will be called. + """ + + def _set_response(self, status_code, return_value): + # TODO: add status_message + self.send_response(status_code) + self.send_header('Content-type', 'text/html') + self.end_headers() + self.wfile.write(return_value.encode('utf-8')) + + def do_POST(self): + """Serve a POST request.""" + if re.search(api_endpoint + "$", self.path): + content_type, params = cgi.parse_header(self.headers.get('content-type')) + if content_type == 'application/json': + length = int(self.headers.get('content-length')) + str_object = self.rfile.read(length).decode('utf8') + json_object = json.loads(str_object) + # TODO: validate structure of json_object for each operation + if json_object["operation"] == "EXP_TRIAL_GENERATE_NEW": + self.handle_generate_new_operation(json_object) + elif json_object["operation"] == "EXP_TRIAL_GENERATE_SUBSEQUENT": + self.handle_generate_subsequent_operation(json_object) + elif json_object["operation"] == "EXP_TRIAL_RESULT": + self.handle_result_operation(json_object) + else: + self._set_response(400, "-1") + else: + self._set_response(400, "-1") + else: + self._set_response(403, "-1") + + def do_GET(self): + """Serve a GET request.""" + if re.search(api_endpoint, self.path): + query = parse_qs(urlparse(self.path).query) + + if ("experiment_name" in query and "trial_number" in query and hpo_service.instance.containsExperiment(query["experiment_name"][0]) and + query["trial_number"][0] == str(hpo_service.instance.get_trial_number(query["experiment_name"][0]))): + data = hpo_service.instance.get_trial_json_object(query["experiment_name"][0]) + self._set_response(200, data) + else: + self._set_response(404, "-1") + elif (self.path == "/"): + data = self.getHomeScreen() + self._set_response(200, data) + else: + self._set_response(403, "-1") + + def getHomeScreen(self): + fin = open(welcome_page) + content = fin.read() + fin.close() + return content + + def handle_generate_new_operation(self, json_object): + """Process EXP_TRIAL_GENERATE_NEW operation.""" + is_valid_json_object = validate_trial_generate_json(json_object) + + if is_valid_json_object and hpo_service.instance.doesNotContainExperiment(json_object["search_space"]["experiment_name"]): + search_space_json = json_object["search_space"] + if str(search_space_json["experiment_name"]).isspace() or not str(search_space_json["experiment_name"]): + self._set_response(400, "-1") + return + get_search_create_study(search_space_json, json_object["operation"]) + trial_number = hpo_service.instance.get_trial_number(json_object["search_space"]["experiment_name"]) + self._set_response(200, str(trial_number)) + else: + self._set_response(400, "-1") + + def handle_generate_subsequent_operation(self, json_object): + """Process EXP_TRIAL_GENERATE_SUBSEQUENT operation.""" + is_valid_json_object = validate_trial_generate_json(json_object) + experiment_name = json_object["experiment_name"] + if is_valid_json_object and hpo_service.instance.containsExperiment(experiment_name): + trial_number = hpo_service.instance.get_trial_number(experiment_name) + self._set_response(200, str(trial_number)) + else: + self._set_response(400, "-1") + + def handle_result_operation(self, json_object): + """Process EXP_TRIAL_RESULT operation.""" + if (hpo_service.instance.containsExperiment(json_object["experiment_name"]) and + json_object["trial_number"] == hpo_service.instance.get_trial_number(json_object["experiment_name"])): + hpo_service.instance.set_result(json_object["experiment_name"], json_object["trial_result"], json_object["result_value_type"], + json_object["result_value"]) + self._set_response(200, "0") + else: + self._set_response(400, "-1") + + +def get_search_create_study(search_space_json, operation): + # TODO: validate structure of search_space_json + + if operation == "EXP_TRIAL_GENERATE_NEW": + if "parallel_trials" not in search_space_json: + search_space_json["parallel_trials"] = n_jobs + experiment_name, total_trials, parallel_trials, direction, hpo_algo_impl, id_, objective_function, tunables, value_type = get_all_tunables( + search_space_json) + if (not parallel_trials): + parallel_trials = n_jobs + elif parallel_trials != 1: + raise Exception("Parallel Trials value should be '1' only!") + + logger.info("Total Trials = "+str(total_trials)) + logger.info("Parallel Trials = "+str(parallel_trials)) + + if hpo_algo_impl in ("optuna_tpe", "optuna_tpe_multivariate", "optuna_skopt"): + hpo_service.instance.newExperiment(id_, experiment_name, total_trials, parallel_trials, direction, hpo_algo_impl, objective_function, + tunables, value_type) + print("Starting Experiment: " + experiment_name) + hpo_service.instance.startExperiment(experiment_name) + + +def get_search_space(id_, url): + """Perform a GET request and return the search space json.""" + params = {"id": id_} + r = requests.get(url, params) + r.raise_for_status() + search_space_json = r.json() + return search_space_json + + + +def main(): + server = HTTPServer((host_name, server_port), HTTPRequestHandler) + logger.info("Access server at http://%s:%s" % ("localhost", server_port)) + server.serve_forever() + +if __name__ == '__main__': + main() diff --git a/src/service.py b/src/service.py index 37918ba..ec63312 100644 --- a/src/service.py +++ b/src/service.py @@ -14,169 +14,18 @@ limitations under the License. """ -from http.server import BaseHTTPRequestHandler, HTTPServer -import re -import cgi -import json -import requests -import os -from urllib.parse import urlparse, parse_qs +import rest_service, grpc_service +import threading -from json_validate import validate_trial_generate_json -from tunables import get_all_tunables -from logger import get_logger +def main(): + restService = threading.Thread(target=rest_service.main) + gRPCservice = threading.Thread(target=grpc_service.serve) -import hpo_service + restService.start() + gRPCservice.start() -logger = get_logger(__name__) - -n_trials = 10 -n_jobs = 1 -autotune_object_ids = {} -search_space_json = [] - -api_endpoint = "/experiment_trials" -host_name="0.0.0.0" -server_port = 8085 - -fileDir = os.path.dirname(os.path.realpath('index.html')) -filename = os.path.join(fileDir, 'index.html') -welcome_page=filename - - -class HTTPRequestHandler(BaseHTTPRequestHandler): - """ - A class used to handle the HTTP requests that arrive at the server. - - The handler will parse the request and the headers, then call a method specific to the request type. The method name - is constructed from the request. For example, for the request method GET, the do_GET() method will be called. - """ - - def _set_response(self, status_code, return_value): - # TODO: add status_message - self.send_response(status_code) - self.send_header('Content-type', 'text/html') - self.end_headers() - self.wfile.write(return_value.encode('utf-8')) - - def do_POST(self): - """Serve a POST request.""" - if re.search(api_endpoint + "$", self.path): - content_type, params = cgi.parse_header(self.headers.get('content-type')) - if content_type == 'application/json': - length = int(self.headers.get('content-length')) - str_object = self.rfile.read(length).decode('utf8') - json_object = json.loads(str_object) - # TODO: validate structure of json_object for each operation - if json_object["operation"] == "EXP_TRIAL_GENERATE_NEW": - self.handle_generate_new_operation(json_object) - elif json_object["operation"] == "EXP_TRIAL_GENERATE_SUBSEQUENT": - self.handle_generate_subsequent_operation(json_object) - elif json_object["operation"] == "EXP_TRIAL_RESULT": - self.handle_result_operation(json_object) - else: - self._set_response(400, "-1") - else: - self._set_response(400, "-1") - else: - self._set_response(403, "-1") - - def do_GET(self): - """Serve a GET request.""" - if re.search(api_endpoint, self.path): - query = parse_qs(urlparse(self.path).query) - - if ("experiment_name" in query and "trial_number" in query and hpo_service.instance.containsExperiment(query["experiment_name"][0]) and - query["trial_number"][0] == str(hpo_service.instance.get_trial_number(query["experiment_name"][0]))): - data = hpo_service.instance.get_trial_json_object(query["experiment_name"][0]) - self._set_response(200, data) - else: - self._set_response(404, "-1") - elif (self.path == "/"): - data = self.getHomeScreen() - self._set_response(200, data) - else: - self._set_response(403, "-1") - - def getHomeScreen(self): - fin = open(welcome_page) - content = fin.read() - fin.close() - return content - - def handle_generate_new_operation(self, json_object): - """Process EXP_TRIAL_GENERATE_NEW operation.""" - is_valid_json_object = validate_trial_generate_json(json_object) - - if is_valid_json_object and hpo_service.instance.doesNotContainExperiment(json_object["search_space"]["experiment_name"]): - search_space_json = json_object["search_space"] - if str(search_space_json["experiment_name"]).isspace() or not str(search_space_json["experiment_name"]): - self._set_response(400, "-1") - return - get_search_create_study(search_space_json, json_object["operation"]) - trial_number = hpo_service.instance.get_trial_number(json_object["search_space"]["experiment_name"]) - self._set_response(200, str(trial_number)) - else: - self._set_response(400, "-1") - - def handle_generate_subsequent_operation(self, json_object): - """Process EXP_TRIAL_GENERATE_SUBSEQUENT operation.""" - is_valid_json_object = validate_trial_generate_json(json_object) - experiment_name = json_object["experiment_name"] - if is_valid_json_object and hpo_service.instance.containsExperiment(experiment_name): - trial_number = hpo_service.instance.get_trial_number(experiment_name) - self._set_response(200, str(trial_number)) - else: - self._set_response(400, "-1") - - def handle_result_operation(self, json_object): - """Process EXP_TRIAL_RESULT operation.""" - if (hpo_service.instance.containsExperiment(json_object["experiment_name"]) and - json_object["trial_number"] == hpo_service.instance.get_trial_number(json_object["experiment_name"])): - hpo_service.instance.set_result(json_object["experiment_name"], json_object["trial_result"], json_object["result_value_type"], - json_object["result_value"]) - self._set_response(200, "0") - else: - self._set_response(400, "-1") - - -def get_search_create_study(search_space_json, operation): - # TODO: validate structure of search_space_json - - if operation == "EXP_TRIAL_GENERATE_NEW": - if "parallel_trials" not in search_space_json: - search_space_json["parallel_trials"] = n_jobs - experiment_name, total_trials, parallel_trials, direction, hpo_algo_impl, id_, objective_function, tunables, value_type = get_all_tunables( - search_space_json) - if (not parallel_trials): - parallel_trials = n_jobs - elif parallel_trials != 1: - raise Exception("Parallel Trials value should be '1' only!") - - logger.info("Total Trials = "+str(total_trials)) - logger.info("Parallel Trials = "+str(parallel_trials)) - - if hpo_algo_impl in ("optuna_tpe", "optuna_tpe_multivariate", "optuna_skopt"): - hpo_service.instance.newExperiment(id_, experiment_name, total_trials, parallel_trials, direction, hpo_algo_impl, objective_function, - tunables, value_type) - print("Starting Experiment: " + experiment_name) - hpo_service.instance.startExperiment(experiment_name) - - -def get_search_space(id_, url): - """Perform a GET request and return the search space json.""" - params = {"id": id_} - r = requests.get(url, params) - r.raise_for_status() - search_space_json = r.json() - return search_space_json - - - -def main(): - server = HTTPServer((host_name, server_port), HTTPRequestHandler) - logger.info("Access server at http://%s:%s" % ("localhost", server_port)) - server.serve_forever() + restService.join() + gRPCservice.join() if __name__ == '__main__': main() diff --git a/tests/resources/searchspace_jsons/newExperiment.json b/tests/resources/searchspace_jsons/newExperiment.json new file mode 100644 index 0000000..d78efb9 --- /dev/null +++ b/tests/resources/searchspace_jsons/newExperiment.json @@ -0,0 +1,27 @@ +{ + "experiment_name": "petclinic-sample-2-75884c5549-npvgd", + "total_trials": 100, + "parallel_trials": 1, + "id": "a123", + "value_type": "double", + "hpo_algo_impl": "optuna_tpe", + "objective_function": "transaction_response_time", + "tuneables": [ + { + "value_type": "double", + "lower_bound": 150, + "name": "memoryRequest", + "upper_bound": 300, + "step": 1 + }, + { + "value_type": "double", + "lower_bound": 1.0, + "name": "cpuRequest", + "upper_bound": 3.0, + "step": 0.01 + } + ], + "slo_class": "response_time", + "direction": "minimize" +} \ No newline at end of file From cf17125f78ecbc5fb6a4bd989fbd229a241091ea Mon Sep 17 00:00:00 2001 From: John OHara Date: Fri, 8 Apr 2022 08:35:44 +0100 Subject: [PATCH 02/16] Fix typos --- src/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/README.md b/src/README.md index eacca0a..8601baf 100644 --- a/src/README.md +++ b/src/README.md @@ -64,12 +64,12 @@ Options: --help Show this message and exit. Commands: - config Obtain a configuration set for a particular experiment trail + config Obtain a configuration set for a particular experiment trial count Return a count of experiments currently running list List names of all experiments currently running new Create a new experiment next Generate next configuration set for running experiment - result Update results for a particular experiment trail + result Update results for a particular experiment trial show Show details of running experiment ``` From 2c67010cfc720f29735169e33fea970529bf8546 Mon Sep 17 00:00:00 2001 From: John OHara Date: Fri, 22 Apr 2022 10:57:40 +0100 Subject: [PATCH 03/16] Fix failing REST endpoint encoding --- src/grpc_service.py | 2 +- src/hpo_service.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/grpc_service.py b/src/grpc_service.py index 0065b83..355e3b3 100644 --- a/src/grpc_service.py +++ b/src/grpc_service.py @@ -77,7 +77,7 @@ def NewExperiment(self, request, context): def GetTrialConfig(self, request, context): # if ("experiment_id" in query and "trial_number" in query and hpo_service.instance.containsExperiment(query["experiment_id"][0]) and # query["trial_number"][0] == str(hpo_service.instance.get_trial_number(query["experiment_id"][0]))): - data = hpo_service.instance.get_trial_json_object(request.experiment_name) + data = json.loads(hpo_service.instance.get_trial_json_object(request.experiment_name)) trialConfig : hpo_pb2.TrialConfig = hpo_pb2.TrialConfig() for config in data: tunable: hpo_pb2.TunableConfig = hpo_pb2.TunableConfig() diff --git a/src/hpo_service.py b/src/hpo_service.py index 670faa7..db7e472 100644 --- a/src/hpo_service.py +++ b/src/hpo_service.py @@ -68,7 +68,7 @@ def get_trial_json_object(self, id_): if experiment.hpo_algo_impl in ("optuna_tpe", "optuna_tpe_multivariate", "optuna_skopt"): try: experiment.resultsAvailableCond.acquire() - return json.loads(json.dumps(experiment.trialDetails.trial_json_object)) + return json.dumps(experiment.trialDetails.trial_json_object) finally: experiment.resultsAvailableCond.release() From 002a37ac6b5a0dd9323288280ff9a83a2f406119 Mon Sep 17 00:00:00 2001 From: Chandrakala Subramanyam Date: Mon, 25 Apr 2022 14:34:18 +0530 Subject: [PATCH 04/16] Updated the module dependencies for grpc and id to experiment_id --- Dockerfile.hpo | 2 +- requirements.txt | 5 ++++- tests/resources/searchspace_jsons/newExperiment.json | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Dockerfile.hpo b/Dockerfile.hpo index e6bcdc9..706c1ba 100644 --- a/Dockerfile.hpo +++ b/Dockerfile.hpo @@ -38,7 +38,7 @@ RUN microdnf install -y python3 \ USER 1001 # Install optuna to the default user -RUN python3 -m pip install --user optuna requests scikit-optimize jsonschema +RUN python3 -m pip install --user optuna requests scikit-optimize jsonschema grpcio google google-api-core LABEL name="Kruize HPO" \ vendor="Red Hat" \ diff --git a/requirements.txt b/requirements.txt index 8df66c0..e4bd2be 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,7 @@ optuna requests scikit-optimize -jsonschema \ No newline at end of file +jsonschema +grpcio +google +google-api-core diff --git a/tests/resources/searchspace_jsons/newExperiment.json b/tests/resources/searchspace_jsons/newExperiment.json index d78efb9..3fbd399 100644 --- a/tests/resources/searchspace_jsons/newExperiment.json +++ b/tests/resources/searchspace_jsons/newExperiment.json @@ -2,7 +2,7 @@ "experiment_name": "petclinic-sample-2-75884c5549-npvgd", "total_trials": 100, "parallel_trials": 1, - "id": "a123", + "experiment_id": "a123", "value_type": "double", "hpo_algo_impl": "optuna_tpe", "objective_function": "transaction_response_time", @@ -24,4 +24,4 @@ ], "slo_class": "response_time", "direction": "minimize" -} \ No newline at end of file +} From 072d0e18a1ba6cf9a2d40275a6cca059b338621c Mon Sep 17 00:00:00 2001 From: Chandrakala Subramanyam Date: Mon, 25 Apr 2022 17:09:31 +0530 Subject: [PATCH 05/16] Removed the google related modules as these aren't required and grpcio in docker for now as it fails --- Dockerfile.hpo | 2 +- requirements.txt | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Dockerfile.hpo b/Dockerfile.hpo index 706c1ba..e6bcdc9 100644 --- a/Dockerfile.hpo +++ b/Dockerfile.hpo @@ -38,7 +38,7 @@ RUN microdnf install -y python3 \ USER 1001 # Install optuna to the default user -RUN python3 -m pip install --user optuna requests scikit-optimize jsonschema grpcio google google-api-core +RUN python3 -m pip install --user optuna requests scikit-optimize jsonschema LABEL name="Kruize HPO" \ vendor="Red Hat" \ diff --git a/requirements.txt b/requirements.txt index e4bd2be..21d8924 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,5 +3,4 @@ requests scikit-optimize jsonschema grpcio -google -google-api-core +click From 52fc81e763518d36c82f705f13cd5502a6b244b9 Mon Sep 17 00:00:00 2001 From: John OHara Date: Mon, 25 Apr 2022 12:48:44 +0100 Subject: [PATCH 06/16] Improve gRPC error handling, improve client UX --- src/exceptions.py | 7 +++++ src/gRPC/hpo_pb2.py | 48 ++++++++++++++++---------------- src/gRPC/hpo_pb2_grpc.py | 12 ++++---- src/gRPC/protos/hpo.proto | 10 +++---- src/grpc_client.py | 35 +++++++++++++++-------- src/grpc_service.py | 58 +++++++++++++++++++++++---------------- src/hpo_service.py | 5 ++-- 7 files changed, 104 insertions(+), 71 deletions(-) create mode 100644 src/exceptions.py diff --git a/src/exceptions.py b/src/exceptions.py new file mode 100644 index 0000000..abc4a51 --- /dev/null +++ b/src/exceptions.py @@ -0,0 +1,7 @@ +class Error(Exception): + """Base class for other exceptions""" + pass + +class ExperimentNotFoundError(Error): + """Raised when the input value is too small""" + pass \ No newline at end of file diff --git a/src/gRPC/hpo_pb2.py b/src/gRPC/hpo_pb2.py index 82166e5..32d1feb 100644 --- a/src/gRPC/hpo_pb2.py +++ b/src/gRPC/hpo_pb2.py @@ -14,7 +14,7 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\thpo.proto\x12\nhelloworld\"\'\n\x16NumberExperimentsReply\x12\r\n\x05\x63ount\x18\x01 \x01(\x05\"+\n\x13NewExperimentsReply\x12\x14\n\x0ctrial_number\x18\x01 \x01(\x05\"*\n\x14\x45xperimentsListReply\x12\x12\n\nexperiment\x18\x01 \x03(\t\"\x19\n\x17NumberExperimentsParams\"\x17\n\x15\x45xperimentsListParams\"\x16\n\x14\x45xperimentTrialReply\"/\n\x14\x45xperimentNameParams\x12\x17\n\x0f\x65xperiment_name\x18\x01 \x01(\t\"9\n\x0f\x45xperimentTrial\x12\x17\n\x0f\x65xperiment_name\x18\x01 \x01(\t\x12\r\n\x05trial\x18\x02 \x01(\x05\"\xbe\x01\n\x15\x45xperimentTrialResult\x12\x15\n\rexperiment_id\x18\x01 \x01(\t\x12\r\n\x05trial\x18\x02 \x01(\x05\x12\x38\n\x06result\x18\x03 \x01(\x0e\x32(.helloworld.ExperimentTrialResult.Result\x12\x12\n\nvalue_type\x18\x04 \x01(\t\x12\r\n\x05value\x18\x05 \x01(\x01\"\"\n\x06Result\x12\x0b\n\x07SUCCESS\x10\x00\x12\x0b\n\x07\x46\x41ILURE\x10\x01\"\x85\x03\n\x11\x45xperimentDetails\x12\x17\n\x0f\x65xperiment_name\x18\x01 \x01(\t\x12\x14\n\x0ctotal_trials\x18\x02 \x01(\x05\x12\x17\n\x0fparallel_trials\x18\x03 \x01(\x05\x12\x11\n\tdirection\x18\x04 \x01(\t\x12\x15\n\rhpo_algo_impl\x18\x05 \x01(\t\x12\x0b\n\x03id_\x18\x06 \x01(\t\x12\x1a\n\x12objective_function\x18\x07 \x01(\t\x12\x38\n\ttuneables\x18\x08 \x03(\x0b\x32%.helloworld.ExperimentDetails.Tunable\x12\x12\n\nvalue_type\x18\t \x01(\t\x12\x11\n\tslo_class\x18\n \x01(\t\x12\x0f\n\x07started\x18\x0b \x01(\x08\x1a\x63\n\x07Tunable\x12\x12\n\nvalue_type\x18\x01 \x01(\t\x12\x13\n\x0blower_bound\x18\x02 \x01(\x05\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x13\n\x0bupper_bound\x18\x04 \x01(\x05\x12\x0c\n\x04step\x18\x05 \x01(\x01\",\n\rTunableConfig\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x02\"8\n\x0bTrialConfig\x12)\n\x06\x63onfig\x18\x01 \x03(\x0b\x32\x19.helloworld.TunableConfig2\xf5\x04\n\nHpoService\x12^\n\x11NumberExperiments\x12#.helloworld.NumberExperimentsParams\x1a\".helloworld.NumberExperimentsReply\"\x00\x12X\n\x0f\x45xperimentsList\x12!.helloworld.ExperimentsListParams\x1a .helloworld.ExperimentsListReply\"\x00\x12Q\n\rNewExperiment\x12\x1d.helloworld.ExperimentDetails\x1a\x1f.helloworld.NewExperimentsReply\"\x00\x12Y\n\x14GetExperimentDetails\x12 .helloworld.ExperimentNameParams\x1a\x1d.helloworld.ExperimentDetails\"\x00\x12H\n\x0eGetTrialConfig\x12\x1b.helloworld.ExperimentTrial\x1a\x17.helloworld.TrialConfig\"\x00\x12Z\n\x11UpdateTrialResult\x12!.helloworld.ExperimentTrialResult\x1a .helloworld.ExperimentTrialReply\"\x00\x12Y\n\x12GenerateNextConfig\x12 .helloworld.ExperimentNameParams\x1a\x1f.helloworld.NewExperimentsReply\"\x00\x42#\n\rio.kruize.hpoB\nHpoServiceP\x01\xa2\x02\x03HLWb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\thpo.proto\x12\nhelloworld\"\'\n\x16NumberExperimentsReply\x12\r\n\x05\x63ount\x18\x01 \x01(\x05\"+\n\x13NewExperimentsReply\x12\x14\n\x0ctrial_number\x18\x01 \x01(\x05\"*\n\x14\x45xperimentsListReply\x12\x12\n\nexperiment\x18\x01 \x03(\t\"\x19\n\x17NumberExperimentsParams\"\x17\n\x15\x45xperimentsListParams\"\x16\n\x14\x45xperimentTrialReply\"+\n\x12\x45xperimentIdParams\x12\x15\n\rexperiment_id\x18\x01 \x01(\t\"7\n\x0f\x45xperimentTrial\x12\x15\n\rexperiment_id\x18\x01 \x01(\t\x12\r\n\x05trial\x18\x02 \x01(\x05\"\xc9\x01\n\x15\x45xperimentTrialResult\x12\x15\n\rexperiment_id\x18\x01 \x01(\t\x12\r\n\x05trial\x18\x02 \x01(\x05\x12\x38\n\x06result\x18\x03 \x01(\x0e\x32(.helloworld.ExperimentTrialResult.Result\x12\x12\n\nvalue_type\x18\x04 \x01(\t\x12\r\n\x05value\x18\x05 \x01(\x01\"-\n\x06Result\x12\x0b\n\x07SUCCESS\x10\x00\x12\x0b\n\x07\x46\x41ILURE\x10\x01\x12\t\n\x05PRUNE\x10\x02\"\x85\x03\n\x11\x45xperimentDetails\x12\x17\n\x0f\x65xperiment_name\x18\x01 \x01(\t\x12\x14\n\x0ctotal_trials\x18\x02 \x01(\x05\x12\x17\n\x0fparallel_trials\x18\x03 \x01(\x05\x12\x11\n\tdirection\x18\x04 \x01(\t\x12\x15\n\rhpo_algo_impl\x18\x05 \x01(\t\x12\x0b\n\x03id_\x18\x06 \x01(\t\x12\x1a\n\x12objective_function\x18\x07 \x01(\t\x12\x38\n\ttuneables\x18\x08 \x03(\x0b\x32%.helloworld.ExperimentDetails.Tunable\x12\x12\n\nvalue_type\x18\t \x01(\t\x12\x11\n\tslo_class\x18\n \x01(\t\x12\x0f\n\x07started\x18\x0b \x01(\x08\x1a\x63\n\x07Tunable\x12\x12\n\nvalue_type\x18\x01 \x01(\t\x12\x13\n\x0blower_bound\x18\x02 \x01(\x05\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x13\n\x0bupper_bound\x18\x04 \x01(\x05\x12\x0c\n\x04step\x18\x05 \x01(\x01\",\n\rTunableConfig\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x02\"8\n\x0bTrialConfig\x12)\n\x06\x63onfig\x18\x01 \x03(\x0b\x32\x19.helloworld.TunableConfig2\xf1\x04\n\nHpoService\x12^\n\x11NumberExperiments\x12#.helloworld.NumberExperimentsParams\x1a\".helloworld.NumberExperimentsReply\"\x00\x12X\n\x0f\x45xperimentsList\x12!.helloworld.ExperimentsListParams\x1a .helloworld.ExperimentsListReply\"\x00\x12Q\n\rNewExperiment\x12\x1d.helloworld.ExperimentDetails\x1a\x1f.helloworld.NewExperimentsReply\"\x00\x12W\n\x14GetExperimentDetails\x12\x1e.helloworld.ExperimentIdParams\x1a\x1d.helloworld.ExperimentDetails\"\x00\x12H\n\x0eGetTrialConfig\x12\x1b.helloworld.ExperimentTrial\x1a\x17.helloworld.TrialConfig\"\x00\x12Z\n\x11UpdateTrialResult\x12!.helloworld.ExperimentTrialResult\x1a .helloworld.ExperimentTrialReply\"\x00\x12W\n\x12GenerateNextConfig\x12\x1e.helloworld.ExperimentIdParams\x1a\x1f.helloworld.NewExperimentsReply\"\x00\x42#\n\rio.kruize.hpoB\nHpoServiceP\x01\xa2\x02\x03HLWb\x06proto3') @@ -24,7 +24,7 @@ _NUMBEREXPERIMENTSPARAMS = DESCRIPTOR.message_types_by_name['NumberExperimentsParams'] _EXPERIMENTSLISTPARAMS = DESCRIPTOR.message_types_by_name['ExperimentsListParams'] _EXPERIMENTTRIALREPLY = DESCRIPTOR.message_types_by_name['ExperimentTrialReply'] -_EXPERIMENTNAMEPARAMS = DESCRIPTOR.message_types_by_name['ExperimentNameParams'] +_EXPERIMENTIDPARAMS = DESCRIPTOR.message_types_by_name['ExperimentIdParams'] _EXPERIMENTTRIAL = DESCRIPTOR.message_types_by_name['ExperimentTrial'] _EXPERIMENTTRIALRESULT = DESCRIPTOR.message_types_by_name['ExperimentTrialResult'] _EXPERIMENTDETAILS = DESCRIPTOR.message_types_by_name['ExperimentDetails'] @@ -74,12 +74,12 @@ }) _sym_db.RegisterMessage(ExperimentTrialReply) -ExperimentNameParams = _reflection.GeneratedProtocolMessageType('ExperimentNameParams', (_message.Message,), { - 'DESCRIPTOR' : _EXPERIMENTNAMEPARAMS, +ExperimentIdParams = _reflection.GeneratedProtocolMessageType('ExperimentIdParams', (_message.Message,), { + 'DESCRIPTOR' : _EXPERIMENTIDPARAMS, '__module__' : 'hpo_pb2' - # @@protoc_insertion_point(class_scope:helloworld.ExperimentNameParams) + # @@protoc_insertion_point(class_scope:helloworld.ExperimentIdParams) }) -_sym_db.RegisterMessage(ExperimentNameParams) +_sym_db.RegisterMessage(ExperimentIdParams) ExperimentTrial = _reflection.GeneratedProtocolMessageType('ExperimentTrial', (_message.Message,), { 'DESCRIPTOR' : _EXPERIMENTTRIAL, @@ -141,22 +141,22 @@ _EXPERIMENTSLISTPARAMS._serialized_end=205 _EXPERIMENTTRIALREPLY._serialized_start=207 _EXPERIMENTTRIALREPLY._serialized_end=229 - _EXPERIMENTNAMEPARAMS._serialized_start=231 - _EXPERIMENTNAMEPARAMS._serialized_end=278 - _EXPERIMENTTRIAL._serialized_start=280 - _EXPERIMENTTRIAL._serialized_end=337 - _EXPERIMENTTRIALRESULT._serialized_start=340 - _EXPERIMENTTRIALRESULT._serialized_end=530 - _EXPERIMENTTRIALRESULT_RESULT._serialized_start=496 - _EXPERIMENTTRIALRESULT_RESULT._serialized_end=530 - _EXPERIMENTDETAILS._serialized_start=533 - _EXPERIMENTDETAILS._serialized_end=922 - _EXPERIMENTDETAILS_TUNABLE._serialized_start=823 - _EXPERIMENTDETAILS_TUNABLE._serialized_end=922 - _TUNABLECONFIG._serialized_start=924 - _TUNABLECONFIG._serialized_end=968 - _TRIALCONFIG._serialized_start=970 - _TRIALCONFIG._serialized_end=1026 - _HPOSERVICE._serialized_start=1029 - _HPOSERVICE._serialized_end=1658 + _EXPERIMENTIDPARAMS._serialized_start=231 + _EXPERIMENTIDPARAMS._serialized_end=274 + _EXPERIMENTTRIAL._serialized_start=276 + _EXPERIMENTTRIAL._serialized_end=331 + _EXPERIMENTTRIALRESULT._serialized_start=334 + _EXPERIMENTTRIALRESULT._serialized_end=535 + _EXPERIMENTTRIALRESULT_RESULT._serialized_start=490 + _EXPERIMENTTRIALRESULT_RESULT._serialized_end=535 + _EXPERIMENTDETAILS._serialized_start=538 + _EXPERIMENTDETAILS._serialized_end=927 + _EXPERIMENTDETAILS_TUNABLE._serialized_start=828 + _EXPERIMENTDETAILS_TUNABLE._serialized_end=927 + _TUNABLECONFIG._serialized_start=929 + _TUNABLECONFIG._serialized_end=973 + _TRIALCONFIG._serialized_start=975 + _TRIALCONFIG._serialized_end=1031 + _HPOSERVICE._serialized_start=1034 + _HPOSERVICE._serialized_end=1659 # @@protoc_insertion_point(module_scope) diff --git a/src/gRPC/hpo_pb2_grpc.py b/src/gRPC/hpo_pb2_grpc.py index e2ffdce..b15e587 100644 --- a/src/gRPC/hpo_pb2_grpc.py +++ b/src/gRPC/hpo_pb2_grpc.py @@ -32,7 +32,7 @@ def __init__(self, channel): ) self.GetExperimentDetails = channel.unary_unary( '/helloworld.HpoService/GetExperimentDetails', - request_serializer=hpo__pb2.ExperimentNameParams.SerializeToString, + request_serializer=hpo__pb2.ExperimentIdParams.SerializeToString, response_deserializer=hpo__pb2.ExperimentDetails.FromString, ) self.GetTrialConfig = channel.unary_unary( @@ -47,7 +47,7 @@ def __init__(self, channel): ) self.GenerateNextConfig = channel.unary_unary( '/helloworld.HpoService/GenerateNextConfig', - request_serializer=hpo__pb2.ExperimentNameParams.SerializeToString, + request_serializer=hpo__pb2.ExperimentIdParams.SerializeToString, response_deserializer=hpo__pb2.NewExperimentsReply.FromString, ) @@ -118,7 +118,7 @@ def add_HpoServiceServicer_to_server(servicer, server): ), 'GetExperimentDetails': grpc.unary_unary_rpc_method_handler( servicer.GetExperimentDetails, - request_deserializer=hpo__pb2.ExperimentNameParams.FromString, + request_deserializer=hpo__pb2.ExperimentIdParams.FromString, response_serializer=hpo__pb2.ExperimentDetails.SerializeToString, ), 'GetTrialConfig': grpc.unary_unary_rpc_method_handler( @@ -133,7 +133,7 @@ def add_HpoServiceServicer_to_server(servicer, server): ), 'GenerateNextConfig': grpc.unary_unary_rpc_method_handler( servicer.GenerateNextConfig, - request_deserializer=hpo__pb2.ExperimentNameParams.FromString, + request_deserializer=hpo__pb2.ExperimentIdParams.FromString, response_serializer=hpo__pb2.NewExperimentsReply.SerializeToString, ), } @@ -210,7 +210,7 @@ def GetExperimentDetails(request, timeout=None, metadata=None): return grpc.experimental.unary_unary(request, target, '/helloworld.HpoService/GetExperimentDetails', - hpo__pb2.ExperimentNameParams.SerializeToString, + hpo__pb2.ExperimentIdParams.SerializeToString, hpo__pb2.ExperimentDetails.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) @@ -261,7 +261,7 @@ def GenerateNextConfig(request, timeout=None, metadata=None): return grpc.experimental.unary_unary(request, target, '/helloworld.HpoService/GenerateNextConfig', - hpo__pb2.ExperimentNameParams.SerializeToString, + hpo__pb2.ExperimentIdParams.SerializeToString, hpo__pb2.NewExperimentsReply.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/src/gRPC/protos/hpo.proto b/src/gRPC/protos/hpo.proto index 5109a1e..fc1d6b2 100644 --- a/src/gRPC/protos/hpo.proto +++ b/src/gRPC/protos/hpo.proto @@ -42,10 +42,10 @@ service HpoService { rpc NumberExperiments(NumberExperimentsParams) returns (NumberExperimentsReply) {} rpc ExperimentsList(ExperimentsListParams) returns (ExperimentsListReply) {} rpc NewExperiment(ExperimentDetails) returns (NewExperimentsReply) {} - rpc GetExperimentDetails(ExperimentNameParams) returns (ExperimentDetails) {} + rpc GetExperimentDetails(ExperimentIdParams) returns (ExperimentDetails) {} rpc GetTrialConfig(ExperimentTrial) returns (TrialConfig) {} rpc UpdateTrialResult(ExperimentTrialResult) returns (ExperimentTrialReply) {} - rpc GenerateNextConfig(ExperimentNameParams) returns (NewExperimentsReply) {} + rpc GenerateNextConfig(ExperimentIdParams) returns (NewExperimentsReply) {} } message NumberExperimentsReply { @@ -66,12 +66,12 @@ message ExperimentsListParams {} message ExperimentTrialReply{} -message ExperimentNameParams { - string experiment_name = 1; +message ExperimentIdParams { + string experiment_id = 1; } message ExperimentTrial { - string experiment_name = 1; + string experiment_id = 1; int32 trial = 2; } diff --git a/src/grpc_client.py b/src/grpc_client.py index fa23658..c57fed7 100644 --- a/src/grpc_client.py +++ b/src/grpc_client.py @@ -35,7 +35,7 @@ def main(): def count(): """Return a count of experiments currently running""" empty = hpo_pb2.NumberExperimentsReply() - fun = lambda stub : stub.NumberExperiments(empty) + fun = lambda stub: stub.NumberExperiments(empty) response = run(fun) click.echo(" Number of running experiments: {}".format(response.count)) @@ -43,22 +43,22 @@ def count(): def list(): """List names of all experiments currently running""" empty = hpo_pb2.NumberExperimentsReply() - fun = lambda stub : stub.ExperimentsList(empty) + fun = lambda stub: stub.ExperimentsList(empty) experiments: hpo_pb2.ExperimentsListReply = run(fun) print("Running Experiments:") for experiment in experiments.experiment: click.echo(" %s" % experiment) + @main.command() @click.option("--name", prompt=" Experiment name", type=str) def show(name): """Show details of running experiment""" expr: hpo_pb2.ExperimentNameParams = hpo_pb2.ExperimentNameParams() expr.experiment_name = name - fun = lambda stub : stub.GetExperimentDetails(expr) + fun = lambda stub: stub.GetExperimentDetails(expr) experiment: hpo_pb2.ExperimentDetails = run(fun) json_obj = MessageToJson(experiment) - click.echo("Experiment Details:") click.echo(json_obj) @main.command() @@ -71,7 +71,7 @@ def new(file): # data = file.read().replace('\n', '') message: hpo_pb2.ExperimentDetails = ParseDict(data, hpo_pb2.ExperimentDetails()) click.echo(" Adding new experiment: {}".format(message.experiment_name)) - fun = lambda stub : stub.NewExperiment(message) + fun = lambda stub: stub.NewExperiment(message) response: hpo_pb2.NewExperimentsReply = run(fun) click.echo("Trial Number: {}".format(response.trial_number)) @@ -83,10 +83,9 @@ def config(name, trial): expr: hpo_pb2.ExperimentTrial = hpo_pb2.ExperimentTrial() expr.experiment_name = name expr.trial = trial - fun = lambda stub : stub.GetTrialConfig(expr) + fun = lambda stub: stub.GetTrialConfig(expr) trial_config: hpo_pb2.TrialConfig = run(fun) json_obj = MessageToJson(trial_config) - click.echo("Trial Config:") click.echo(json_obj) @main.command() @@ -103,9 +102,9 @@ def result(name, trial, result, value_type, value): trialResult.result = hpo_pb2._EXPERIMENTTRIALRESULT_RESULT.values_by_name[result].number trialResult.value_type = value_type trialResult.value = value - fun = lambda stub : stub.UpdateTrialResult(trialResult) + fun = lambda stub: stub.UpdateTrialResult(trialResult) hpo_pb2.TrialConfig = run(fun) - click.echo("Updated Trial Result") + click.echo("Success: Updated Trial Result") @main.command() @click.option("--name", prompt=" Enter name", type=str) @@ -123,12 +122,26 @@ def run(func): # of the code. with grpc.insecure_channel('localhost:50051') as channel: stub = hpo_pb2_grpc.HpoServiceStub(channel) - return func(stub) + try: + response = func(stub) + except grpc.RpcError as rpc_error: + if rpc_error.code() == grpc.StatusCode.CANCELLED: + pass + elif rpc_error.code() == grpc.StatusCode.UNAVAILABLE: + pass + elif rpc_error.code() == grpc.StatusCode.NOT_FOUND: + raise click.ClickException(rpc_error.details()) + elif rpc_error.code() == grpc.StatusCode.INVALID_ARGUMENT: + raise click.ClickException(rpc_error.details()) + else: + raise click.ClickException("Received unknown RPC error: code={rpc_error.code()} message={rpc_error.details()}") + return response + def NewExperiment(stub, **args): empty = hpo_pb2.NumberExperimentsReply() response = stub.NumberExperiments(empty) - print("HpoService client received: %s" % response.count) + click.echo("HpoService client received: %s" % response.count) if __name__ == "__main__": diff --git a/src/grpc_service.py b/src/grpc_service.py index 355e3b3..836f781 100644 --- a/src/grpc_service.py +++ b/src/grpc_service.py @@ -23,6 +23,7 @@ from google.protobuf.json_format import MessageToJson from bayes_optuna.optuna_hpo import HpoExperiment from gRPC.hpo_pb2 import NewExperimentsReply +from exceptions import ExperimentNotFoundError class HpoService(hpo_pb2_grpc.HpoServiceServicer): @@ -39,18 +40,23 @@ def ExperimentsList(self, request, context): return reply def GetExperimentDetails(self, request, context): - experiment: HpoExperiment = hpo_service.instance.getExperiment(request.experiment_name) - reply = hpo_pb2.ExperimentDetails() - reply.experiment_name = experiment.experiment_name - reply.direction = experiment.direction - reply.hpo_algo_impl = experiment.hpo_algo_impl - reply.id_ = experiment.id_ - reply.objective_function = experiment.objective_function - # TODO:: expand tunables message - # reply.tunables = experiment.tunables - reply.started = experiment.started - context.set_code(grpc.StatusCode.OK) - return reply + try: + experiment: HpoExperiment = hpo_service.instance.getExperiment(request.experiment_id) + reply = hpo_pb2.ExperimentDetails() + reply.experiment_name = experiment.experiment_name + reply.direction = experiment.direction + reply.hpo_algo_impl = experiment.hpo_algo_impl + reply.id_ = experiment.id_ + reply.objective_function = experiment.objective_function + # TODO:: expand tunables message + # reply.tunables = experiment.tunables + reply.started = experiment.started + context.set_code(grpc.StatusCode.OK) + return reply + except ExperimentNotFoundError: + context.set_code(grpc.StatusCode.NOT_FOUND) + context.set_details('Could not find experiment: %s' % request.experiment_id) + return def NewExperiment(self, request, context): @@ -70,23 +76,28 @@ def NewExperiment(self, request, context): context.set_code(grpc.StatusCode.OK) return reply else: - context.set_code(grpc.StatusCode.ABORTED) + context.set_code(grpc.StatusCode.INVALID_ARGUMENT) context.set_details('Invalid algorithm: %s' % request.hpo_algo_impl) return def GetTrialConfig(self, request, context): # if ("experiment_id" in query and "trial_number" in query and hpo_service.instance.containsExperiment(query["experiment_id"][0]) and # query["trial_number"][0] == str(hpo_service.instance.get_trial_number(query["experiment_id"][0]))): - data = json.loads(hpo_service.instance.get_trial_json_object(request.experiment_name)) - trialConfig : hpo_pb2.TrialConfig = hpo_pb2.TrialConfig() - for config in data: - tunable: hpo_pb2.TunableConfig = hpo_pb2.TunableConfig() - tunable.name = config['tunable_name'] - tunable.value = config['tunable_value'] - trialConfig.config.extend([tunable]) + if hpo_service.instance.containsExperiment(request.experiment_id): + data = json.loads(hpo_service.instance.get_trial_json_object(request.experiment_id)) + trialConfig : hpo_pb2.TrialConfig = hpo_pb2.TrialConfig() + for config in data: + tunable: hpo_pb2.TunableConfig = hpo_pb2.TunableConfig() + tunable.name = config['tunable_name'] + tunable.value = config['tunable_value'] + trialConfig.config.extend([tunable]) - context.set_code(grpc.StatusCode.OK) - return trialConfig + context.set_code(grpc.StatusCode.OK) + return trialConfig + else: + context.set_code(grpc.StatusCode.NOT_FOUND) + context.set_details('Could not find experiment: %s' % request.experiment_id) + return hpo_pb2.TunableConfig() def UpdateTrialResult(self, request, context): if (hpo_service.instance.containsExperiment(request.experiment_id) and @@ -98,8 +109,9 @@ def UpdateTrialResult(self, request, context): context.set_code(grpc.StatusCode.OK) return hpo_pb2.ExperimentTrialReply() else: - context.set_code(grpc.StatusCode.ABORTED) + context.set_code(grpc.StatusCode.NOT_FOUND) context.set_details('Experiment not found or invalid trial number!') + return hpo_pb2.ExperimentTrialReply() def GenerateNextConfig(self, request, context): trial_number = hpo_service.instance.get_trial_number(request.experiment_name) diff --git a/src/hpo_service.py b/src/hpo_service.py index db7e472..f207d9f 100644 --- a/src/hpo_service.py +++ b/src/hpo_service.py @@ -1,6 +1,7 @@ import threading import json from bayes_optuna import optuna_hpo +from exceptions import ExperimentNotFoundError class HpoService: """ @@ -43,8 +44,8 @@ def getExperimentsList(self): def getExperiment(self, name) -> optuna_hpo.HpoExperiment: if self.doesNotContainExperiment(name): - print("Experiment does not exist") - return + print("Experiment " + name + " does not exist") + raise ExperimentNotFoundError return self.experiments.get(name) From e63a20ff41b3b550f517efb9c0128b4c04edc41f Mon Sep 17 00:00:00 2001 From: Chandrakala Subramanyam Date: Mon, 25 Apr 2022 18:51:43 +0530 Subject: [PATCH 07/16] Modified id to experiment_id --- src/gRPC/hpo_pb2.py | 20 ++++++++++---------- src/gRPC/protos/hpo.proto | 2 +- src/grpc_service.py | 6 +++--- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/gRPC/hpo_pb2.py b/src/gRPC/hpo_pb2.py index 32d1feb..7529247 100644 --- a/src/gRPC/hpo_pb2.py +++ b/src/gRPC/hpo_pb2.py @@ -14,7 +14,7 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\thpo.proto\x12\nhelloworld\"\'\n\x16NumberExperimentsReply\x12\r\n\x05\x63ount\x18\x01 \x01(\x05\"+\n\x13NewExperimentsReply\x12\x14\n\x0ctrial_number\x18\x01 \x01(\x05\"*\n\x14\x45xperimentsListReply\x12\x12\n\nexperiment\x18\x01 \x03(\t\"\x19\n\x17NumberExperimentsParams\"\x17\n\x15\x45xperimentsListParams\"\x16\n\x14\x45xperimentTrialReply\"+\n\x12\x45xperimentIdParams\x12\x15\n\rexperiment_id\x18\x01 \x01(\t\"7\n\x0f\x45xperimentTrial\x12\x15\n\rexperiment_id\x18\x01 \x01(\t\x12\r\n\x05trial\x18\x02 \x01(\x05\"\xc9\x01\n\x15\x45xperimentTrialResult\x12\x15\n\rexperiment_id\x18\x01 \x01(\t\x12\r\n\x05trial\x18\x02 \x01(\x05\x12\x38\n\x06result\x18\x03 \x01(\x0e\x32(.helloworld.ExperimentTrialResult.Result\x12\x12\n\nvalue_type\x18\x04 \x01(\t\x12\r\n\x05value\x18\x05 \x01(\x01\"-\n\x06Result\x12\x0b\n\x07SUCCESS\x10\x00\x12\x0b\n\x07\x46\x41ILURE\x10\x01\x12\t\n\x05PRUNE\x10\x02\"\x85\x03\n\x11\x45xperimentDetails\x12\x17\n\x0f\x65xperiment_name\x18\x01 \x01(\t\x12\x14\n\x0ctotal_trials\x18\x02 \x01(\x05\x12\x17\n\x0fparallel_trials\x18\x03 \x01(\x05\x12\x11\n\tdirection\x18\x04 \x01(\t\x12\x15\n\rhpo_algo_impl\x18\x05 \x01(\t\x12\x0b\n\x03id_\x18\x06 \x01(\t\x12\x1a\n\x12objective_function\x18\x07 \x01(\t\x12\x38\n\ttuneables\x18\x08 \x03(\x0b\x32%.helloworld.ExperimentDetails.Tunable\x12\x12\n\nvalue_type\x18\t \x01(\t\x12\x11\n\tslo_class\x18\n \x01(\t\x12\x0f\n\x07started\x18\x0b \x01(\x08\x1a\x63\n\x07Tunable\x12\x12\n\nvalue_type\x18\x01 \x01(\t\x12\x13\n\x0blower_bound\x18\x02 \x01(\x05\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x13\n\x0bupper_bound\x18\x04 \x01(\x05\x12\x0c\n\x04step\x18\x05 \x01(\x01\",\n\rTunableConfig\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x02\"8\n\x0bTrialConfig\x12)\n\x06\x63onfig\x18\x01 \x03(\x0b\x32\x19.helloworld.TunableConfig2\xf1\x04\n\nHpoService\x12^\n\x11NumberExperiments\x12#.helloworld.NumberExperimentsParams\x1a\".helloworld.NumberExperimentsReply\"\x00\x12X\n\x0f\x45xperimentsList\x12!.helloworld.ExperimentsListParams\x1a .helloworld.ExperimentsListReply\"\x00\x12Q\n\rNewExperiment\x12\x1d.helloworld.ExperimentDetails\x1a\x1f.helloworld.NewExperimentsReply\"\x00\x12W\n\x14GetExperimentDetails\x12\x1e.helloworld.ExperimentIdParams\x1a\x1d.helloworld.ExperimentDetails\"\x00\x12H\n\x0eGetTrialConfig\x12\x1b.helloworld.ExperimentTrial\x1a\x17.helloworld.TrialConfig\"\x00\x12Z\n\x11UpdateTrialResult\x12!.helloworld.ExperimentTrialResult\x1a .helloworld.ExperimentTrialReply\"\x00\x12W\n\x12GenerateNextConfig\x12\x1e.helloworld.ExperimentIdParams\x1a\x1f.helloworld.NewExperimentsReply\"\x00\x42#\n\rio.kruize.hpoB\nHpoServiceP\x01\xa2\x02\x03HLWb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\thpo.proto\x12\nhelloworld\"\'\n\x16NumberExperimentsReply\x12\r\n\x05\x63ount\x18\x01 \x01(\x05\"+\n\x13NewExperimentsReply\x12\x14\n\x0ctrial_number\x18\x01 \x01(\x05\"*\n\x14\x45xperimentsListReply\x12\x12\n\nexperiment\x18\x01 \x03(\t\"\x19\n\x17NumberExperimentsParams\"\x17\n\x15\x45xperimentsListParams\"\x16\n\x14\x45xperimentTrialReply\"+\n\x12\x45xperimentIdParams\x12\x15\n\rexperiment_id\x18\x01 \x01(\t\"7\n\x0f\x45xperimentTrial\x12\x15\n\rexperiment_id\x18\x01 \x01(\t\x12\r\n\x05trial\x18\x02 \x01(\x05\"\xc9\x01\n\x15\x45xperimentTrialResult\x12\x15\n\rexperiment_id\x18\x01 \x01(\t\x12\r\n\x05trial\x18\x02 \x01(\x05\x12\x38\n\x06result\x18\x03 \x01(\x0e\x32(.helloworld.ExperimentTrialResult.Result\x12\x12\n\nvalue_type\x18\x04 \x01(\t\x12\r\n\x05value\x18\x05 \x01(\x01\"-\n\x06Result\x12\x0b\n\x07SUCCESS\x10\x00\x12\x0b\n\x07\x46\x41ILURE\x10\x01\x12\t\n\x05PRUNE\x10\x02\"\x8f\x03\n\x11\x45xperimentDetails\x12\x17\n\x0f\x65xperiment_name\x18\x01 \x01(\t\x12\x14\n\x0ctotal_trials\x18\x02 \x01(\x05\x12\x17\n\x0fparallel_trials\x18\x03 \x01(\x05\x12\x11\n\tdirection\x18\x04 \x01(\t\x12\x15\n\rhpo_algo_impl\x18\x05 \x01(\t\x12\x15\n\rexperiment_id\x18\x06 \x01(\t\x12\x1a\n\x12objective_function\x18\x07 \x01(\t\x12\x38\n\ttuneables\x18\x08 \x03(\x0b\x32%.helloworld.ExperimentDetails.Tunable\x12\x12\n\nvalue_type\x18\t \x01(\t\x12\x11\n\tslo_class\x18\n \x01(\t\x12\x0f\n\x07started\x18\x0b \x01(\x08\x1a\x63\n\x07Tunable\x12\x12\n\nvalue_type\x18\x01 \x01(\t\x12\x13\n\x0blower_bound\x18\x02 \x01(\x05\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x13\n\x0bupper_bound\x18\x04 \x01(\x05\x12\x0c\n\x04step\x18\x05 \x01(\x01\",\n\rTunableConfig\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x02\"8\n\x0bTrialConfig\x12)\n\x06\x63onfig\x18\x01 \x03(\x0b\x32\x19.helloworld.TunableConfig2\xf1\x04\n\nHpoService\x12^\n\x11NumberExperiments\x12#.helloworld.NumberExperimentsParams\x1a\".helloworld.NumberExperimentsReply\"\x00\x12X\n\x0f\x45xperimentsList\x12!.helloworld.ExperimentsListParams\x1a .helloworld.ExperimentsListReply\"\x00\x12Q\n\rNewExperiment\x12\x1d.helloworld.ExperimentDetails\x1a\x1f.helloworld.NewExperimentsReply\"\x00\x12W\n\x14GetExperimentDetails\x12\x1e.helloworld.ExperimentIdParams\x1a\x1d.helloworld.ExperimentDetails\"\x00\x12H\n\x0eGetTrialConfig\x12\x1b.helloworld.ExperimentTrial\x1a\x17.helloworld.TrialConfig\"\x00\x12Z\n\x11UpdateTrialResult\x12!.helloworld.ExperimentTrialResult\x1a .helloworld.ExperimentTrialReply\"\x00\x12W\n\x12GenerateNextConfig\x12\x1e.helloworld.ExperimentIdParams\x1a\x1f.helloworld.NewExperimentsReply\"\x00\x42#\n\rio.kruize.hpoB\nHpoServiceP\x01\xa2\x02\x03HLWb\x06proto3') @@ -150,13 +150,13 @@ _EXPERIMENTTRIALRESULT_RESULT._serialized_start=490 _EXPERIMENTTRIALRESULT_RESULT._serialized_end=535 _EXPERIMENTDETAILS._serialized_start=538 - _EXPERIMENTDETAILS._serialized_end=927 - _EXPERIMENTDETAILS_TUNABLE._serialized_start=828 - _EXPERIMENTDETAILS_TUNABLE._serialized_end=927 - _TUNABLECONFIG._serialized_start=929 - _TUNABLECONFIG._serialized_end=973 - _TRIALCONFIG._serialized_start=975 - _TRIALCONFIG._serialized_end=1031 - _HPOSERVICE._serialized_start=1034 - _HPOSERVICE._serialized_end=1659 + _EXPERIMENTDETAILS._serialized_end=937 + _EXPERIMENTDETAILS_TUNABLE._serialized_start=838 + _EXPERIMENTDETAILS_TUNABLE._serialized_end=937 + _TUNABLECONFIG._serialized_start=939 + _TUNABLECONFIG._serialized_end=983 + _TRIALCONFIG._serialized_start=985 + _TRIALCONFIG._serialized_end=1041 + _HPOSERVICE._serialized_start=1044 + _HPOSERVICE._serialized_end=1669 # @@protoc_insertion_point(module_scope) diff --git a/src/gRPC/protos/hpo.proto b/src/gRPC/protos/hpo.proto index fc1d6b2..4ba15a0 100644 --- a/src/gRPC/protos/hpo.proto +++ b/src/gRPC/protos/hpo.proto @@ -103,7 +103,7 @@ message ExperimentDetails { int32 parallel_trials = 3; string direction = 4; string hpo_algo_impl = 5; - string id_ = 6; + string experiment_id = 6; string objective_function= 7; repeated Tunable tuneables = 8; string value_type= 9; diff --git a/src/grpc_service.py b/src/grpc_service.py index 836f781..389a31c 100644 --- a/src/grpc_service.py +++ b/src/grpc_service.py @@ -64,13 +64,13 @@ def NewExperiment(self, request, context): tuneables = [] for tuneable in request.tuneables: tuneables.append(json.loads(MessageToJson(tuneable, preserving_proto_field_name=True))) - hpo_service.instance.newExperiment(request.id_, request.experiment_name, + hpo_service.instance.newExperiment(request.experiment_id, request.experiment_name, request.total_trials, request.parallel_trials, request.direction, request.hpo_algo_impl, request.objective_function, tuneables, request.value_type) - hpo_service.instance.startExperiment(request.id_) - experiment: HpoExperiment = hpo_service.instance.getExperiment(request.id_) + hpo_service.instance.startExperiment(request.experiment_id) + experiment: HpoExperiment = hpo_service.instance.getExperiment(request.experiment_id) reply: NewExperimentsReply = NewExperimentsReply() reply.trial_number = experiment.trialDetails.trial_number context.set_code(grpc.StatusCode.OK) From fd914967698671cea1f60099851a0bea59916b66 Mon Sep 17 00:00:00 2001 From: Chandrakala Subramanyam Date: Wed, 27 Apr 2022 11:55:32 +0530 Subject: [PATCH 08/16] Included gRPC dependency modules and exposed the gRPC service port --- Dockerfile.hpo | 5 +++-- scripts/cluster-helpers.sh | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Dockerfile.hpo b/Dockerfile.hpo index e6bcdc9..acdae10 100644 --- a/Dockerfile.hpo +++ b/Dockerfile.hpo @@ -25,7 +25,7 @@ WORKDIR /opt/app # Install packages needed for python to function correctly # Create the non root user, same as the one used in the build phase. -RUN microdnf install -y python3 \ +RUN microdnf install -y python3 gcc-c++ python3-devel \ && microdnf update -y \ && microdnf -y install shadow-utils \ && adduser -u 1001 -G root -s /usr/sbin/nologin default \ @@ -38,7 +38,7 @@ RUN microdnf install -y python3 \ USER 1001 # Install optuna to the default user -RUN python3 -m pip install --user optuna requests scikit-optimize jsonschema +RUN python3 -m pip install --user optuna requests scikit-optimize jsonschema click grpcio google-api-core LABEL name="Kruize HPO" \ vendor="Red Hat" \ @@ -54,5 +54,6 @@ COPY --chown=1001:0 index.html /opt/app/ EXPOSE 8085 +EXPOSE 50051 ENTRYPOINT python3 -u src/service.py diff --git a/scripts/cluster-helpers.sh b/scripts/cluster-helpers.sh index b72764d..a060e01 100644 --- a/scripts/cluster-helpers.sh +++ b/scripts/cluster-helpers.sh @@ -69,7 +69,7 @@ function docker_start() { check_prereq running ${SERVICE_STATUS_DOCKER} - ${CONTAINER_RUNTIME} run -d --name hpo_docker_container -p 8085:8085 ${HPO_CONTAINER_IMAGE} >/dev/null 2>&1 + ${CONTAINER_RUNTIME} run -d --name hpo_docker_container -p 8085:8085 -p 50051:50051 ${HPO_CONTAINER_IMAGE} >/dev/null 2>&1 check_err "Unexpected error occured. Service Stopped!" echo From 896cb0e12348ec9e34612760df80034e7b3b33b9 Mon Sep 17 00:00:00 2001 From: John OHara Date: Wed, 27 Apr 2022 13:17:19 +0100 Subject: [PATCH 09/16] Create const for grpc port and host --- src/grpc_service.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/grpc_service.py b/src/grpc_service.py index 389a31c..2dcf974 100644 --- a/src/grpc_service.py +++ b/src/grpc_service.py @@ -25,6 +25,8 @@ from gRPC.hpo_pb2 import NewExperimentsReply from exceptions import ExperimentNotFoundError +host_name="0.0.0.0" +server_port = 50051 class HpoService(hpo_pb2_grpc.HpoServiceServicer): @@ -123,8 +125,8 @@ def GenerateNextConfig(self, request, context): def serve(): server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) hpo_pb2_grpc.add_HpoServiceServicer_to_server(HpoService(), server) - server.add_insecure_port('[::]:50051') - print("Starting gRPC server at http://%s:%s" % ("::", "50051")) + server.add_insecure_port(host_name + ':' + server_port) + print("Starting gRPC server at http://%s:%s" % (host_name, server_port)) server.start() server.wait_for_termination() From e89c111843566f85a69cd6af5841376416f80ee4 Mon Sep 17 00:00:00 2001 From: John OHara Date: Wed, 27 Apr 2022 13:34:04 +0100 Subject: [PATCH 10/16] Allow configuration of client via enviroment variables --- src/README.md | 7 +++++++ src/grpc_client.py | 20 ++++++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/README.md b/src/README.md index 8601baf..46f1f87 100644 --- a/src/README.md +++ b/src/README.md @@ -88,3 +88,10 @@ or ```shell $ python3 ./grpc_client.py new --file=/tmp/hpo/newExperiment.json ``` + +> **_NOTE:_** The default host and port for the client is `localhost` and `50051`. If you wish to connect to a remote machine, or via a diferent port, please set the following environment variables, `HPO_HOST` and `HPO_PORT`. +> e.g. +> ```shell +> $ export HPO_HOST=remoteMachine.com +> $ export HPO_PORT=9191 +> ``` \ No newline at end of file diff --git a/src/grpc_client.py b/src/grpc_client.py index c57fed7..b18cf8b 100644 --- a/src/grpc_client.py +++ b/src/grpc_client.py @@ -16,6 +16,8 @@ from __future__ import print_function import logging +import os + import click import json @@ -24,7 +26,8 @@ from google.protobuf.json_format import MessageToJson from google.protobuf.json_format import Parse, ParseDict - +default_host_name="localhost" +default_server_port = 50051 @click.group() def main(): """A HPO command line tool to allow interaction with HPO service""" @@ -120,7 +123,20 @@ def run(func): # NOTE(gRPC Python Team): .close() is possible on a channel and should be # used in circumstances in which the with statement does not fit the needs # of the code. - with grpc.insecure_channel('localhost:50051') as channel: + + if "HPO_HOST" in os.environ: + host_name = os.environ.get('HPO_HOST') + else : + host_name = default_host_name + + if "HPO_PORT" in os.environ: + server_port = os.environ.get('HPO_PORT') + else : + server_port = default_server_port + + + + with grpc.insecure_channel(host_name + ':' + server_port) as channel: stub = hpo_pb2_grpc.HpoServiceStub(channel) try: response = func(stub) From 5692d1fb9a0a2bc2871203043dcda7f3409e6f17 Mon Sep 17 00:00:00 2001 From: Chandrakala Subramanyam Date: Wed, 27 Apr 2022 18:25:04 +0530 Subject: [PATCH 11/16] Converted the port to string to concatenate --- src/grpc_client.py | 2 +- src/grpc_service.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/grpc_client.py b/src/grpc_client.py index b18cf8b..1e36b6f 100644 --- a/src/grpc_client.py +++ b/src/grpc_client.py @@ -136,7 +136,7 @@ def run(func): - with grpc.insecure_channel(host_name + ':' + server_port) as channel: + with grpc.insecure_channel(host_name + ':' + str(server_port)) as channel: stub = hpo_pb2_grpc.HpoServiceStub(channel) try: response = func(stub) diff --git a/src/grpc_service.py b/src/grpc_service.py index 2dcf974..9d6b3b2 100644 --- a/src/grpc_service.py +++ b/src/grpc_service.py @@ -125,7 +125,7 @@ def GenerateNextConfig(self, request, context): def serve(): server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) hpo_pb2_grpc.add_HpoServiceServicer_to_server(HpoService(), server) - server.add_insecure_port(host_name + ':' + server_port) + server.add_insecure_port(host_name + ':' + str(server_port)) print("Starting gRPC server at http://%s:%s" % (host_name, server_port)) server.start() From 00583ee707e773ebbf16d4428a699d351402199c Mon Sep 17 00:00:00 2001 From: John OHara Date: Wed, 27 Apr 2022 15:56:54 +0100 Subject: [PATCH 12/16] Updated gRPC api to use experiment_name to identify running experiments --- src/gRPC/hpo_pb2.py | 48 +++++++++---------- src/gRPC/hpo_pb2_grpc.py | 12 ++--- src/gRPC/protos/hpo.proto | 12 ++--- src/grpc_client.py | 12 +++-- src/grpc_service.py | 26 +++++----- .../searchspace_jsons/newExperiment.json | 1 - 6 files changed, 55 insertions(+), 56 deletions(-) diff --git a/src/gRPC/hpo_pb2.py b/src/gRPC/hpo_pb2.py index 7529247..bc4efd4 100644 --- a/src/gRPC/hpo_pb2.py +++ b/src/gRPC/hpo_pb2.py @@ -14,7 +14,7 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\thpo.proto\x12\nhelloworld\"\'\n\x16NumberExperimentsReply\x12\r\n\x05\x63ount\x18\x01 \x01(\x05\"+\n\x13NewExperimentsReply\x12\x14\n\x0ctrial_number\x18\x01 \x01(\x05\"*\n\x14\x45xperimentsListReply\x12\x12\n\nexperiment\x18\x01 \x03(\t\"\x19\n\x17NumberExperimentsParams\"\x17\n\x15\x45xperimentsListParams\"\x16\n\x14\x45xperimentTrialReply\"+\n\x12\x45xperimentIdParams\x12\x15\n\rexperiment_id\x18\x01 \x01(\t\"7\n\x0f\x45xperimentTrial\x12\x15\n\rexperiment_id\x18\x01 \x01(\t\x12\r\n\x05trial\x18\x02 \x01(\x05\"\xc9\x01\n\x15\x45xperimentTrialResult\x12\x15\n\rexperiment_id\x18\x01 \x01(\t\x12\r\n\x05trial\x18\x02 \x01(\x05\x12\x38\n\x06result\x18\x03 \x01(\x0e\x32(.helloworld.ExperimentTrialResult.Result\x12\x12\n\nvalue_type\x18\x04 \x01(\t\x12\r\n\x05value\x18\x05 \x01(\x01\"-\n\x06Result\x12\x0b\n\x07SUCCESS\x10\x00\x12\x0b\n\x07\x46\x41ILURE\x10\x01\x12\t\n\x05PRUNE\x10\x02\"\x8f\x03\n\x11\x45xperimentDetails\x12\x17\n\x0f\x65xperiment_name\x18\x01 \x01(\t\x12\x14\n\x0ctotal_trials\x18\x02 \x01(\x05\x12\x17\n\x0fparallel_trials\x18\x03 \x01(\x05\x12\x11\n\tdirection\x18\x04 \x01(\t\x12\x15\n\rhpo_algo_impl\x18\x05 \x01(\t\x12\x15\n\rexperiment_id\x18\x06 \x01(\t\x12\x1a\n\x12objective_function\x18\x07 \x01(\t\x12\x38\n\ttuneables\x18\x08 \x03(\x0b\x32%.helloworld.ExperimentDetails.Tunable\x12\x12\n\nvalue_type\x18\t \x01(\t\x12\x11\n\tslo_class\x18\n \x01(\t\x12\x0f\n\x07started\x18\x0b \x01(\x08\x1a\x63\n\x07Tunable\x12\x12\n\nvalue_type\x18\x01 \x01(\t\x12\x13\n\x0blower_bound\x18\x02 \x01(\x05\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x13\n\x0bupper_bound\x18\x04 \x01(\x05\x12\x0c\n\x04step\x18\x05 \x01(\x01\",\n\rTunableConfig\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x02\"8\n\x0bTrialConfig\x12)\n\x06\x63onfig\x18\x01 \x03(\x0b\x32\x19.helloworld.TunableConfig2\xf1\x04\n\nHpoService\x12^\n\x11NumberExperiments\x12#.helloworld.NumberExperimentsParams\x1a\".helloworld.NumberExperimentsReply\"\x00\x12X\n\x0f\x45xperimentsList\x12!.helloworld.ExperimentsListParams\x1a .helloworld.ExperimentsListReply\"\x00\x12Q\n\rNewExperiment\x12\x1d.helloworld.ExperimentDetails\x1a\x1f.helloworld.NewExperimentsReply\"\x00\x12W\n\x14GetExperimentDetails\x12\x1e.helloworld.ExperimentIdParams\x1a\x1d.helloworld.ExperimentDetails\"\x00\x12H\n\x0eGetTrialConfig\x12\x1b.helloworld.ExperimentTrial\x1a\x17.helloworld.TrialConfig\"\x00\x12Z\n\x11UpdateTrialResult\x12!.helloworld.ExperimentTrialResult\x1a .helloworld.ExperimentTrialReply\"\x00\x12W\n\x12GenerateNextConfig\x12\x1e.helloworld.ExperimentIdParams\x1a\x1f.helloworld.NewExperimentsReply\"\x00\x42#\n\rio.kruize.hpoB\nHpoServiceP\x01\xa2\x02\x03HLWb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\thpo.proto\x12\nhelloworld\"\'\n\x16NumberExperimentsReply\x12\r\n\x05\x63ount\x18\x01 \x01(\x05\"+\n\x13NewExperimentsReply\x12\x14\n\x0ctrial_number\x18\x01 \x01(\x05\"*\n\x14\x45xperimentsListReply\x12\x12\n\nexperiment\x18\x01 \x03(\t\"\x19\n\x17NumberExperimentsParams\"\x17\n\x15\x45xperimentsListParams\"\x16\n\x14\x45xperimentTrialReply\"/\n\x14\x45xperimentNameParams\x12\x17\n\x0f\x65xperiment_name\x18\x01 \x01(\t\"9\n\x0f\x45xperimentTrial\x12\x17\n\x0f\x65xperiment_name\x18\x01 \x01(\t\x12\r\n\x05trial\x18\x02 \x01(\x05\"\xcb\x01\n\x15\x45xperimentTrialResult\x12\x17\n\x0f\x65xperiment_name\x18\x01 \x01(\t\x12\r\n\x05trial\x18\x02 \x01(\x05\x12\x38\n\x06result\x18\x03 \x01(\x0e\x32(.helloworld.ExperimentTrialResult.Result\x12\x12\n\nvalue_type\x18\x04 \x01(\t\x12\r\n\x05value\x18\x05 \x01(\x01\"-\n\x06Result\x12\x0b\n\x07SUCCESS\x10\x00\x12\x0b\n\x07\x46\x41ILURE\x10\x01\x12\t\n\x05PRUNE\x10\x02\"\x8f\x03\n\x11\x45xperimentDetails\x12\x17\n\x0f\x65xperiment_name\x18\x01 \x01(\t\x12\x14\n\x0ctotal_trials\x18\x02 \x01(\x05\x12\x17\n\x0fparallel_trials\x18\x03 \x01(\x05\x12\x11\n\tdirection\x18\x04 \x01(\t\x12\x15\n\rhpo_algo_impl\x18\x05 \x01(\t\x12\x15\n\rexperiment_id\x18\x06 \x01(\t\x12\x1a\n\x12objective_function\x18\x07 \x01(\t\x12\x38\n\ttuneables\x18\x08 \x03(\x0b\x32%.helloworld.ExperimentDetails.Tunable\x12\x12\n\nvalue_type\x18\t \x01(\t\x12\x11\n\tslo_class\x18\n \x01(\t\x12\x0f\n\x07started\x18\x0b \x01(\x08\x1a\x63\n\x07Tunable\x12\x12\n\nvalue_type\x18\x01 \x01(\t\x12\x13\n\x0blower_bound\x18\x02 \x01(\x05\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x13\n\x0bupper_bound\x18\x04 \x01(\x05\x12\x0c\n\x04step\x18\x05 \x01(\x01\",\n\rTunableConfig\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x02\"8\n\x0bTrialConfig\x12)\n\x06\x63onfig\x18\x01 \x03(\x0b\x32\x19.helloworld.TunableConfig2\xf5\x04\n\nHpoService\x12^\n\x11NumberExperiments\x12#.helloworld.NumberExperimentsParams\x1a\".helloworld.NumberExperimentsReply\"\x00\x12X\n\x0f\x45xperimentsList\x12!.helloworld.ExperimentsListParams\x1a .helloworld.ExperimentsListReply\"\x00\x12Q\n\rNewExperiment\x12\x1d.helloworld.ExperimentDetails\x1a\x1f.helloworld.NewExperimentsReply\"\x00\x12Y\n\x14GetExperimentDetails\x12 .helloworld.ExperimentNameParams\x1a\x1d.helloworld.ExperimentDetails\"\x00\x12H\n\x0eGetTrialConfig\x12\x1b.helloworld.ExperimentTrial\x1a\x17.helloworld.TrialConfig\"\x00\x12Z\n\x11UpdateTrialResult\x12!.helloworld.ExperimentTrialResult\x1a .helloworld.ExperimentTrialReply\"\x00\x12Y\n\x12GenerateNextConfig\x12 .helloworld.ExperimentNameParams\x1a\x1f.helloworld.NewExperimentsReply\"\x00\x42#\n\rio.kruize.hpoB\nHpoServiceP\x01\xa2\x02\x03HLWb\x06proto3') @@ -24,7 +24,7 @@ _NUMBEREXPERIMENTSPARAMS = DESCRIPTOR.message_types_by_name['NumberExperimentsParams'] _EXPERIMENTSLISTPARAMS = DESCRIPTOR.message_types_by_name['ExperimentsListParams'] _EXPERIMENTTRIALREPLY = DESCRIPTOR.message_types_by_name['ExperimentTrialReply'] -_EXPERIMENTIDPARAMS = DESCRIPTOR.message_types_by_name['ExperimentIdParams'] +_EXPERIMENTNAMEPARAMS = DESCRIPTOR.message_types_by_name['ExperimentNameParams'] _EXPERIMENTTRIAL = DESCRIPTOR.message_types_by_name['ExperimentTrial'] _EXPERIMENTTRIALRESULT = DESCRIPTOR.message_types_by_name['ExperimentTrialResult'] _EXPERIMENTDETAILS = DESCRIPTOR.message_types_by_name['ExperimentDetails'] @@ -74,12 +74,12 @@ }) _sym_db.RegisterMessage(ExperimentTrialReply) -ExperimentIdParams = _reflection.GeneratedProtocolMessageType('ExperimentIdParams', (_message.Message,), { - 'DESCRIPTOR' : _EXPERIMENTIDPARAMS, +ExperimentNameParams = _reflection.GeneratedProtocolMessageType('ExperimentNameParams', (_message.Message,), { + 'DESCRIPTOR' : _EXPERIMENTNAMEPARAMS, '__module__' : 'hpo_pb2' - # @@protoc_insertion_point(class_scope:helloworld.ExperimentIdParams) + # @@protoc_insertion_point(class_scope:helloworld.ExperimentNameParams) }) -_sym_db.RegisterMessage(ExperimentIdParams) +_sym_db.RegisterMessage(ExperimentNameParams) ExperimentTrial = _reflection.GeneratedProtocolMessageType('ExperimentTrial', (_message.Message,), { 'DESCRIPTOR' : _EXPERIMENTTRIAL, @@ -141,22 +141,22 @@ _EXPERIMENTSLISTPARAMS._serialized_end=205 _EXPERIMENTTRIALREPLY._serialized_start=207 _EXPERIMENTTRIALREPLY._serialized_end=229 - _EXPERIMENTIDPARAMS._serialized_start=231 - _EXPERIMENTIDPARAMS._serialized_end=274 - _EXPERIMENTTRIAL._serialized_start=276 - _EXPERIMENTTRIAL._serialized_end=331 - _EXPERIMENTTRIALRESULT._serialized_start=334 - _EXPERIMENTTRIALRESULT._serialized_end=535 - _EXPERIMENTTRIALRESULT_RESULT._serialized_start=490 - _EXPERIMENTTRIALRESULT_RESULT._serialized_end=535 - _EXPERIMENTDETAILS._serialized_start=538 - _EXPERIMENTDETAILS._serialized_end=937 - _EXPERIMENTDETAILS_TUNABLE._serialized_start=838 - _EXPERIMENTDETAILS_TUNABLE._serialized_end=937 - _TUNABLECONFIG._serialized_start=939 - _TUNABLECONFIG._serialized_end=983 - _TRIALCONFIG._serialized_start=985 - _TRIALCONFIG._serialized_end=1041 - _HPOSERVICE._serialized_start=1044 - _HPOSERVICE._serialized_end=1669 + _EXPERIMENTNAMEPARAMS._serialized_start=231 + _EXPERIMENTNAMEPARAMS._serialized_end=278 + _EXPERIMENTTRIAL._serialized_start=280 + _EXPERIMENTTRIAL._serialized_end=337 + _EXPERIMENTTRIALRESULT._serialized_start=340 + _EXPERIMENTTRIALRESULT._serialized_end=543 + _EXPERIMENTTRIALRESULT_RESULT._serialized_start=498 + _EXPERIMENTTRIALRESULT_RESULT._serialized_end=543 + _EXPERIMENTDETAILS._serialized_start=546 + _EXPERIMENTDETAILS._serialized_end=945 + _EXPERIMENTDETAILS_TUNABLE._serialized_start=846 + _EXPERIMENTDETAILS_TUNABLE._serialized_end=945 + _TUNABLECONFIG._serialized_start=947 + _TUNABLECONFIG._serialized_end=991 + _TRIALCONFIG._serialized_start=993 + _TRIALCONFIG._serialized_end=1049 + _HPOSERVICE._serialized_start=1052 + _HPOSERVICE._serialized_end=1681 # @@protoc_insertion_point(module_scope) diff --git a/src/gRPC/hpo_pb2_grpc.py b/src/gRPC/hpo_pb2_grpc.py index b15e587..e2ffdce 100644 --- a/src/gRPC/hpo_pb2_grpc.py +++ b/src/gRPC/hpo_pb2_grpc.py @@ -32,7 +32,7 @@ def __init__(self, channel): ) self.GetExperimentDetails = channel.unary_unary( '/helloworld.HpoService/GetExperimentDetails', - request_serializer=hpo__pb2.ExperimentIdParams.SerializeToString, + request_serializer=hpo__pb2.ExperimentNameParams.SerializeToString, response_deserializer=hpo__pb2.ExperimentDetails.FromString, ) self.GetTrialConfig = channel.unary_unary( @@ -47,7 +47,7 @@ def __init__(self, channel): ) self.GenerateNextConfig = channel.unary_unary( '/helloworld.HpoService/GenerateNextConfig', - request_serializer=hpo__pb2.ExperimentIdParams.SerializeToString, + request_serializer=hpo__pb2.ExperimentNameParams.SerializeToString, response_deserializer=hpo__pb2.NewExperimentsReply.FromString, ) @@ -118,7 +118,7 @@ def add_HpoServiceServicer_to_server(servicer, server): ), 'GetExperimentDetails': grpc.unary_unary_rpc_method_handler( servicer.GetExperimentDetails, - request_deserializer=hpo__pb2.ExperimentIdParams.FromString, + request_deserializer=hpo__pb2.ExperimentNameParams.FromString, response_serializer=hpo__pb2.ExperimentDetails.SerializeToString, ), 'GetTrialConfig': grpc.unary_unary_rpc_method_handler( @@ -133,7 +133,7 @@ def add_HpoServiceServicer_to_server(servicer, server): ), 'GenerateNextConfig': grpc.unary_unary_rpc_method_handler( servicer.GenerateNextConfig, - request_deserializer=hpo__pb2.ExperimentIdParams.FromString, + request_deserializer=hpo__pb2.ExperimentNameParams.FromString, response_serializer=hpo__pb2.NewExperimentsReply.SerializeToString, ), } @@ -210,7 +210,7 @@ def GetExperimentDetails(request, timeout=None, metadata=None): return grpc.experimental.unary_unary(request, target, '/helloworld.HpoService/GetExperimentDetails', - hpo__pb2.ExperimentIdParams.SerializeToString, + hpo__pb2.ExperimentNameParams.SerializeToString, hpo__pb2.ExperimentDetails.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) @@ -261,7 +261,7 @@ def GenerateNextConfig(request, timeout=None, metadata=None): return grpc.experimental.unary_unary(request, target, '/helloworld.HpoService/GenerateNextConfig', - hpo__pb2.ExperimentIdParams.SerializeToString, + hpo__pb2.ExperimentNameParams.SerializeToString, hpo__pb2.NewExperimentsReply.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/src/gRPC/protos/hpo.proto b/src/gRPC/protos/hpo.proto index 4ba15a0..a8d73af 100644 --- a/src/gRPC/protos/hpo.proto +++ b/src/gRPC/protos/hpo.proto @@ -42,10 +42,10 @@ service HpoService { rpc NumberExperiments(NumberExperimentsParams) returns (NumberExperimentsReply) {} rpc ExperimentsList(ExperimentsListParams) returns (ExperimentsListReply) {} rpc NewExperiment(ExperimentDetails) returns (NewExperimentsReply) {} - rpc GetExperimentDetails(ExperimentIdParams) returns (ExperimentDetails) {} + rpc GetExperimentDetails(ExperimentNameParams) returns (ExperimentDetails) {} rpc GetTrialConfig(ExperimentTrial) returns (TrialConfig) {} rpc UpdateTrialResult(ExperimentTrialResult) returns (ExperimentTrialReply) {} - rpc GenerateNextConfig(ExperimentIdParams) returns (NewExperimentsReply) {} + rpc GenerateNextConfig(ExperimentNameParams) returns (NewExperimentsReply) {} } message NumberExperimentsReply { @@ -66,12 +66,12 @@ message ExperimentsListParams {} message ExperimentTrialReply{} -message ExperimentIdParams { - string experiment_id = 1; +message ExperimentNameParams { + string experiment_name = 1; } message ExperimentTrial { - string experiment_id = 1; + string experiment_name = 1; int32 trial = 2; } @@ -82,7 +82,7 @@ message ExperimentTrialResult{ PRUNE = 2; } - string experiment_id = 1; + string experiment_name = 1; int32 trial = 2; Result result = 3; string value_type = 4; diff --git a/src/grpc_client.py b/src/grpc_client.py index 1e36b6f..ee1f5b8 100644 --- a/src/grpc_client.py +++ b/src/grpc_client.py @@ -23,7 +23,7 @@ import grpc from gRPC import hpo_pb2_grpc, hpo_pb2 -from google.protobuf.json_format import MessageToJson +from google.protobuf.json_format import MessageToJson, ParseError from google.protobuf.json_format import Parse, ParseDict default_host_name="localhost" @@ -71,8 +71,10 @@ def new(file): # TODO: validate file path with open(file, 'r') as json_file: data = json.load(json_file) - # data = file.read().replace('\n', '') - message: hpo_pb2.ExperimentDetails = ParseDict(data, hpo_pb2.ExperimentDetails()) + try: + message: hpo_pb2.ExperimentDetails = ParseDict(data, hpo_pb2.ExperimentDetails()) + except ParseError as pErr : + raise click.ClickException("Unable to parse: " + file) click.echo(" Adding new experiment: {}".format(message.experiment_name)) fun = lambda stub: stub.NewExperiment(message) response: hpo_pb2.NewExperimentsReply = run(fun) @@ -100,7 +102,7 @@ def config(name, trial): def result(name, trial, result, value_type, value): """Update results for a particular experiment trail""" trialResult: hpo_pb2.ExperimentTrialResult = hpo_pb2.ExperimentTrialResult() - trialResult.experiment_id = name + trialResult.experiment_name = name trialResult.trial = trial trialResult.result = hpo_pb2._EXPERIMENTTRIALRESULT_RESULT.values_by_name[result].number trialResult.value_type = value_type @@ -150,7 +152,7 @@ def run(func): elif rpc_error.code() == grpc.StatusCode.INVALID_ARGUMENT: raise click.ClickException(rpc_error.details()) else: - raise click.ClickException("Received unknown RPC error: code={rpc_error.code()} message={rpc_error.details()}") + raise click.ClickException("Received unknown RPC error: code={" + str(rpc_error.code()) + "} message={" + rpc_error.details() + "}") return response diff --git a/src/grpc_service.py b/src/grpc_service.py index 9d6b3b2..a0c1fe5 100644 --- a/src/grpc_service.py +++ b/src/grpc_service.py @@ -43,12 +43,12 @@ def ExperimentsList(self, request, context): def GetExperimentDetails(self, request, context): try: - experiment: HpoExperiment = hpo_service.instance.getExperiment(request.experiment_id) + experiment: HpoExperiment = hpo_service.instance.getExperiment(request.experiment_name) reply = hpo_pb2.ExperimentDetails() reply.experiment_name = experiment.experiment_name reply.direction = experiment.direction reply.hpo_algo_impl = experiment.hpo_algo_impl - reply.id_ = experiment.id_ + # reply.id_ = experiment.id_ reply.objective_function = experiment.objective_function # TODO:: expand tunables message # reply.tunables = experiment.tunables @@ -57,7 +57,7 @@ def GetExperimentDetails(self, request, context): return reply except ExperimentNotFoundError: context.set_code(grpc.StatusCode.NOT_FOUND) - context.set_details('Could not find experiment: %s' % request.experiment_id) + context.set_details('Could not find experiment: %s' % request.experiment_name) return @@ -66,13 +66,13 @@ def NewExperiment(self, request, context): tuneables = [] for tuneable in request.tuneables: tuneables.append(json.loads(MessageToJson(tuneable, preserving_proto_field_name=True))) - hpo_service.instance.newExperiment(request.experiment_id, request.experiment_name, + hpo_service.instance.newExperiment(None, request.experiment_name, request.total_trials, request.parallel_trials, request.direction, request.hpo_algo_impl, request.objective_function, tuneables, request.value_type) - hpo_service.instance.startExperiment(request.experiment_id) - experiment: HpoExperiment = hpo_service.instance.getExperiment(request.experiment_id) + hpo_service.instance.startExperiment(request.experiment_name) + experiment: HpoExperiment = hpo_service.instance.getExperiment(request.experiment_name) reply: NewExperimentsReply = NewExperimentsReply() reply.trial_number = experiment.trialDetails.trial_number context.set_code(grpc.StatusCode.OK) @@ -83,10 +83,8 @@ def NewExperiment(self, request, context): return def GetTrialConfig(self, request, context): - # if ("experiment_id" in query and "trial_number" in query and hpo_service.instance.containsExperiment(query["experiment_id"][0]) and - # query["trial_number"][0] == str(hpo_service.instance.get_trial_number(query["experiment_id"][0]))): - if hpo_service.instance.containsExperiment(request.experiment_id): - data = json.loads(hpo_service.instance.get_trial_json_object(request.experiment_id)) + if hpo_service.instance.containsExperiment(request.experiment_name): + data = json.loads(hpo_service.instance.get_trial_json_object(request.experiment_name)) trialConfig : hpo_pb2.TrialConfig = hpo_pb2.TrialConfig() for config in data: tunable: hpo_pb2.TunableConfig = hpo_pb2.TunableConfig() @@ -98,13 +96,13 @@ def GetTrialConfig(self, request, context): return trialConfig else: context.set_code(grpc.StatusCode.NOT_FOUND) - context.set_details('Could not find experiment: %s' % request.experiment_id) + context.set_details('Could not find experiment: %s' % request.experiment_name) return hpo_pb2.TunableConfig() def UpdateTrialResult(self, request, context): - if (hpo_service.instance.containsExperiment(request.experiment_id) and - request.trial == hpo_service.instance.get_trial_number(request.experiment_id)): - hpo_service.instance.set_result(request.experiment_id, + if (hpo_service.instance.containsExperiment(request.experiment_name) and + request.trial == hpo_service.instance.get_trial_number(request.experiment_name)): + hpo_service.instance.set_result(request.experiment_name, request.result, request.value_type, request.value) diff --git a/tests/resources/searchspace_jsons/newExperiment.json b/tests/resources/searchspace_jsons/newExperiment.json index 3fbd399..d115fa9 100644 --- a/tests/resources/searchspace_jsons/newExperiment.json +++ b/tests/resources/searchspace_jsons/newExperiment.json @@ -2,7 +2,6 @@ "experiment_name": "petclinic-sample-2-75884c5549-npvgd", "total_trials": 100, "parallel_trials": 1, - "experiment_id": "a123", "value_type": "double", "hpo_algo_impl": "optuna_tpe", "objective_function": "transaction_response_time", From 380ba5558e533071a182b8e1cae73c9ea814aef5 Mon Sep 17 00:00:00 2001 From: John OHara Date: Thu, 28 Apr 2022 08:03:36 +0100 Subject: [PATCH 13/16] Depend only on protobuf python module --- Dockerfile.hpo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.hpo b/Dockerfile.hpo index acdae10..0db4788 100644 --- a/Dockerfile.hpo +++ b/Dockerfile.hpo @@ -38,7 +38,7 @@ RUN microdnf install -y python3 gcc-c++ python3-devel \ USER 1001 # Install optuna to the default user -RUN python3 -m pip install --user optuna requests scikit-optimize jsonschema click grpcio google-api-core +RUN python3 -m pip install --user optuna requests scikit-optimize jsonschema click grpcio protobuf LABEL name="Kruize HPO" \ vendor="Red Hat" \ From f1b665aa82f28b6d3d3b90e95798f65fb27e7ecd Mon Sep 17 00:00:00 2001 From: John OHara Date: Thu, 28 Apr 2022 08:27:27 +0100 Subject: [PATCH 14/16] Change java_outer_classname to not conflict with service name --- src/gRPC/protos/hpo.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gRPC/protos/hpo.proto b/src/gRPC/protos/hpo.proto index a8d73af..838258e 100644 --- a/src/gRPC/protos/hpo.proto +++ b/src/gRPC/protos/hpo.proto @@ -32,7 +32,7 @@ syntax = "proto3"; option java_multiple_files = true; option java_package = "io.kruize.hpo"; -option java_outer_classname = "HpoService"; +option java_outer_classname = "Hpo"; option objc_class_prefix = "HLW"; package helloworld; From e42fd1600f38b0816cd6855764212c1cad257349 Mon Sep 17 00:00:00 2001 From: John OHara Date: Thu, 28 Apr 2022 10:21:02 +0100 Subject: [PATCH 15/16] Added protobuf to requirements.txt --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 21d8924..673c167 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,4 @@ scikit-optimize jsonschema grpcio click +protobuf \ No newline at end of file From a6405919a61ccc9407111332dc0b8e3a689e1e4c Mon Sep 17 00:00:00 2001 From: John OHara Date: Thu, 28 Apr 2022 11:28:02 +0100 Subject: [PATCH 16/16] Fix licenses --- src/exceptions.py | 16 ++++++++++++++++ src/gRPC/protos/hpo.proto | 35 ++++++++++------------------------- src/grpc_client.py | 29 +++++++++++++++-------------- src/grpc_service.py | 29 +++++++++++++++-------------- src/hpo_service.py | 16 ++++++++++++++++ 5 files changed, 72 insertions(+), 53 deletions(-) diff --git a/src/exceptions.py b/src/exceptions.py index abc4a51..6395bed 100644 --- a/src/exceptions.py +++ b/src/exceptions.py @@ -1,3 +1,19 @@ +""" +Copyright (c) 2020, 2022 Red Hat, IBM Corporation and others. + +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. +""" + class Error(Exception): """Base class for other exceptions""" pass diff --git a/src/gRPC/protos/hpo.proto b/src/gRPC/protos/hpo.proto index 838258e..9f5ea58 100644 --- a/src/gRPC/protos/hpo.proto +++ b/src/gRPC/protos/hpo.proto @@ -1,31 +1,16 @@ -// Copyright 2015, Google Inc. -// All rights reserved. +// Copyright (c) 2020, 2022 Red Hat, IBM Corporation and others. // -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: +// 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 // -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. +// http://www.apache.org/licenses/LICENSE-2.0 // -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// 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"; diff --git a/src/grpc_client.py b/src/grpc_client.py index ee1f5b8..5d0388b 100644 --- a/src/grpc_client.py +++ b/src/grpc_client.py @@ -1,17 +1,18 @@ -# Copyright 2015 gRPC authors. -# -# 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. -"""The Python implementation of the GRPC helloworld.Greeter client.""" +""" +Copyright (c) 2020, 2022 Red Hat, IBM Corporation and others. + +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 __future__ import print_function diff --git a/src/grpc_service.py b/src/grpc_service.py index a0c1fe5..eab6cd5 100644 --- a/src/grpc_service.py +++ b/src/grpc_service.py @@ -1,17 +1,18 @@ -# Copyright 2015 gRPC authors. -# -# 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. -"""The Python implementation of the GRPC helloworld.Greeter server.""" +""" +Copyright (c) 2020, 2022 Red Hat, IBM Corporation and others. + +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 concurrent import futures import logging diff --git a/src/hpo_service.py b/src/hpo_service.py index f207d9f..7bf1d78 100644 --- a/src/hpo_service.py +++ b/src/hpo_service.py @@ -1,3 +1,19 @@ +""" +Copyright (c) 2020, 2022 Red Hat, IBM Corporation and others. + +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. +""" + import threading import json from bayes_optuna import optuna_hpo