diff --git a/.gitignore b/.gitignore index 5ccc268..a201400 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ # Generated files /bin /docs/book +/include diff --git a/Makefile b/Makefile index b3695d6..ababd71 100644 --- a/Makefile +++ b/Makefile @@ -2,9 +2,15 @@ BIN_DIR := $(shell pwd)/bin # Tool versions MDBOOK_VERSION = 0.4.27 +PROTOC_VERSION = 24.2 +PROTOC_GEN_GO_VERSION = 1.31.0 +PROTOC_GEN_GO_GRPC_VERSION = 1.3.0 +PROTOC_GEN_DOC_VERSION = 1.5.1 MDBOOK := $(BIN_DIR)/mdbook # Test tools +PROTOC := PATH=$(PWD)/bin:'$(PATH)' $(PWD)/bin/protoc -I=$(PWD)/include:. +PROTOC_OUTPUTS = pkg/rpc/necoperf.pb.go pkg/rpc/necoperf_grpc.pb.go docs/necoperf-grpc.md STATICCHECK = $(BIN_DIR)/staticcheck .PHONY: all @@ -30,9 +36,30 @@ test-go: test-tools go test -race -v ./... go vet ./... +.PHONY: generate +generate: + $(MAKE) $(PROTOC_OUTPUTS) + +pkg/rpc/necoperf.pb.go: pkg/rpc/necoperf.proto + $(PROTOC) --go_out=module=github.com/cybozu-go/necoperf:. $< + +pkg/rpc/necoperf_grpc.pb.go: pkg/rpc/necoperf.proto + $(PROTOC) --go-grpc_out=module=github.com/cybozu-go/necoperf:. $< + +docs/necoperf-grpc.md: pkg/rpc/necoperf.proto + $(PROTOC) --doc_out=docs --doc_opt=markdown,$@ $< ##@ Tools +.PHONY: setup +setup: + curl -sfL -o protoc.zip https://github.com/protocolbuffers/protobuf/releases/download/v$(PROTOC_VERSION)/protoc-$(PROTOC_VERSION)-linux-x86_64.zip + unzip -o protoc.zip bin/protoc 'include/*' + rm -f protoc.zip + GOBIN=$(PWD)/bin go install google.golang.org/protobuf/cmd/protoc-gen-go@v$(PROTOC_GEN_GO_VERSION) + GOBIN=$(PWD)/bin go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v$(PROTOC_GEN_GO_GRPC_VERSION) + GOBIN=$(PWD)/bin go install github.com/pseudomuto/protoc-gen-doc/cmd/protoc-gen-doc@v$(PROTOC_GEN_DOC_VERSION) + $(MDBOOK): mkdir -p bin curl -fsL https://github.com/rust-lang/mdBook/releases/download/v$(MDBOOK_VERSION)/mdbook-v$(MDBOOK_VERSION)-x86_64-unknown-linux-gnu.tar.gz | tar -C bin -xzf - diff --git a/docs/necoperf-grpc.md b/docs/necoperf-grpc.md new file mode 100644 index 0000000..62f7f08 --- /dev/null +++ b/docs/necoperf-grpc.md @@ -0,0 +1,92 @@ +# Protocol Documentation + + +## Table of Contents + +- [pkg/rpc/necoperf.proto](#pkg_rpc_necoperf-proto) + - [PerfProfileRequest](#necoperf-PerfProfileRequest) + - [PerfProfileResponse](#necoperf-PerfProfileResponse) + + - [NecoPerf](#necoperf-NecoPerf) + +- [Scalar Value Types](#scalar-value-types) + + + + +

Top

+ +## pkg/rpc/necoperf.proto + + + + + +### PerfProfileRequest + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| container_id | [string](#string) | | | +| timeout | [google.protobuf.Duration](#google-protobuf-Duration) | | | + + + + + + + + +### PerfProfileResponse + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| data | [bytes](#bytes) | | | + + + + + + + + + + + + + + +### NecoPerf + + +| Method Name | Request Type | Response Type | Description | +| ----------- | ------------ | ------------- | ------------| +| Profile | [PerfProfileRequest](#necoperf-PerfProfileRequest) | [PerfProfileResponse](#necoperf-PerfProfileResponse) stream | | + + + + + +## Scalar Value Types + +| .proto Type | Notes | C++ | Java | Python | Go | C# | PHP | Ruby | +| ----------- | ----- | --- | ---- | ------ | -- | -- | --- | ---- | +| double | | double | double | float | float64 | double | float | Float | +| float | | float | float | float | float32 | float | float | Float | +| int32 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) | +| int64 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead. | int64 | long | int/long | int64 | long | integer/string | Bignum | +| uint32 | Uses variable-length encoding. | uint32 | int | int/long | uint32 | uint | integer | Bignum or Fixnum (as required) | +| uint64 | Uses variable-length encoding. | uint64 | long | int/long | uint64 | ulong | integer/string | Bignum or Fixnum (as required) | +| sint32 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) | +| sint64 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s. | int64 | long | int/long | int64 | long | integer/string | Bignum | +| fixed32 | Always four bytes. More efficient than uint32 if values are often greater than 2^28. | uint32 | int | int | uint32 | uint | integer | Bignum or Fixnum (as required) | +| fixed64 | Always eight bytes. More efficient than uint64 if values are often greater than 2^56. | uint64 | long | int/long | uint64 | ulong | integer/string | Bignum | +| sfixed32 | Always four bytes. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) | +| sfixed64 | Always eight bytes. | int64 | long | int/long | int64 | long | integer/string | Bignum | +| bool | | bool | boolean | boolean | bool | bool | boolean | TrueClass/FalseClass | +| string | A string must always contain UTF-8 encoded or 7-bit ASCII text. | string | String | str/unicode | string | string | string | String (UTF-8) | +| bytes | May contain any arbitrary sequence of bytes. | string | ByteString | str | []byte | ByteString | string | String (ASCII-8BIT) | + diff --git a/pkg/rpc/necoperf.pb.go b/pkg/rpc/necoperf.pb.go new file mode 100644 index 0000000..f4ce9cd --- /dev/null +++ b/pkg/rpc/necoperf.pb.go @@ -0,0 +1,232 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.31.0 +// protoc v4.24.2 +// source: pkg/rpc/necoperf.proto + +package rpc + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + durationpb "google.golang.org/protobuf/types/known/durationpb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type PerfProfileRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ContainerId string `protobuf:"bytes,1,opt,name=container_id,json=containerId,proto3" json:"container_id,omitempty"` + Timeout *durationpb.Duration `protobuf:"bytes,2,opt,name=timeout,proto3" json:"timeout,omitempty"` +} + +func (x *PerfProfileRequest) Reset() { + *x = PerfProfileRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_pkg_rpc_necoperf_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PerfProfileRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PerfProfileRequest) ProtoMessage() {} + +func (x *PerfProfileRequest) ProtoReflect() protoreflect.Message { + mi := &file_pkg_rpc_necoperf_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PerfProfileRequest.ProtoReflect.Descriptor instead. +func (*PerfProfileRequest) Descriptor() ([]byte, []int) { + return file_pkg_rpc_necoperf_proto_rawDescGZIP(), []int{0} +} + +func (x *PerfProfileRequest) GetContainerId() string { + if x != nil { + return x.ContainerId + } + return "" +} + +func (x *PerfProfileRequest) GetTimeout() *durationpb.Duration { + if x != nil { + return x.Timeout + } + return nil +} + +type PerfProfileResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` +} + +func (x *PerfProfileResponse) Reset() { + *x = PerfProfileResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_pkg_rpc_necoperf_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PerfProfileResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PerfProfileResponse) ProtoMessage() {} + +func (x *PerfProfileResponse) ProtoReflect() protoreflect.Message { + mi := &file_pkg_rpc_necoperf_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PerfProfileResponse.ProtoReflect.Descriptor instead. +func (*PerfProfileResponse) Descriptor() ([]byte, []int) { + return file_pkg_rpc_necoperf_proto_rawDescGZIP(), []int{1} +} + +func (x *PerfProfileResponse) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +var File_pkg_rpc_necoperf_proto protoreflect.FileDescriptor + +var file_pkg_rpc_necoperf_proto_rawDesc = []byte{ + 0x0a, 0x16, 0x70, 0x6b, 0x67, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x6e, 0x65, 0x63, 0x6f, 0x70, 0x65, + 0x72, 0x66, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x6e, 0x65, 0x63, 0x6f, 0x70, 0x65, + 0x72, 0x66, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x22, 0x6c, 0x0a, 0x12, 0x50, 0x65, 0x72, 0x66, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, + 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, + 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x33, 0x0a, 0x07, 0x74, + 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, + 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, + 0x22, 0x29, 0x0a, 0x13, 0x50, 0x65, 0x72, 0x66, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x32, 0x54, 0x0a, 0x08, 0x4e, + 0x65, 0x63, 0x6f, 0x50, 0x65, 0x72, 0x66, 0x12, 0x48, 0x0a, 0x07, 0x50, 0x72, 0x6f, 0x66, 0x69, + 0x6c, 0x65, 0x12, 0x1c, 0x2e, 0x6e, 0x65, 0x63, 0x6f, 0x70, 0x65, 0x72, 0x66, 0x2e, 0x50, 0x65, + 0x72, 0x66, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1d, 0x2e, 0x6e, 0x65, 0x63, 0x6f, 0x70, 0x65, 0x72, 0x66, 0x2e, 0x50, 0x65, 0x72, 0x66, + 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, + 0x01, 0x42, 0x27, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x63, 0x79, 0x62, 0x6f, 0x7a, 0x75, 0x2d, 0x67, 0x6f, 0x2f, 0x6e, 0x65, 0x63, 0x6f, 0x70, 0x65, + 0x72, 0x66, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, +} + +var ( + file_pkg_rpc_necoperf_proto_rawDescOnce sync.Once + file_pkg_rpc_necoperf_proto_rawDescData = file_pkg_rpc_necoperf_proto_rawDesc +) + +func file_pkg_rpc_necoperf_proto_rawDescGZIP() []byte { + file_pkg_rpc_necoperf_proto_rawDescOnce.Do(func() { + file_pkg_rpc_necoperf_proto_rawDescData = protoimpl.X.CompressGZIP(file_pkg_rpc_necoperf_proto_rawDescData) + }) + return file_pkg_rpc_necoperf_proto_rawDescData +} + +var file_pkg_rpc_necoperf_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_pkg_rpc_necoperf_proto_goTypes = []interface{}{ + (*PerfProfileRequest)(nil), // 0: necoperf.PerfProfileRequest + (*PerfProfileResponse)(nil), // 1: necoperf.PerfProfileResponse + (*durationpb.Duration)(nil), // 2: google.protobuf.Duration +} +var file_pkg_rpc_necoperf_proto_depIdxs = []int32{ + 2, // 0: necoperf.PerfProfileRequest.timeout:type_name -> google.protobuf.Duration + 0, // 1: necoperf.NecoPerf.Profile:input_type -> necoperf.PerfProfileRequest + 1, // 2: necoperf.NecoPerf.Profile:output_type -> necoperf.PerfProfileResponse + 2, // [2:3] is the sub-list for method output_type + 1, // [1:2] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_pkg_rpc_necoperf_proto_init() } +func file_pkg_rpc_necoperf_proto_init() { + if File_pkg_rpc_necoperf_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_pkg_rpc_necoperf_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PerfProfileRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_pkg_rpc_necoperf_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PerfProfileResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_pkg_rpc_necoperf_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_pkg_rpc_necoperf_proto_goTypes, + DependencyIndexes: file_pkg_rpc_necoperf_proto_depIdxs, + MessageInfos: file_pkg_rpc_necoperf_proto_msgTypes, + }.Build() + File_pkg_rpc_necoperf_proto = out.File + file_pkg_rpc_necoperf_proto_rawDesc = nil + file_pkg_rpc_necoperf_proto_goTypes = nil + file_pkg_rpc_necoperf_proto_depIdxs = nil +} diff --git a/pkg/rpc/necoperf.proto b/pkg/rpc/necoperf.proto new file mode 100644 index 0000000..f0f7693 --- /dev/null +++ b/pkg/rpc/necoperf.proto @@ -0,0 +1,19 @@ +syntax = "proto3"; +package necoperf; + +import "google/protobuf/duration.proto"; + +option go_package = "github.com/cybozu-go/necoperf/pkg/rpc"; + +service NecoPerf { + rpc Profile(PerfProfileRequest) returns (stream PerfProfileResponse); +} + +message PerfProfileRequest { + string container_id = 1; + google.protobuf.Duration timeout = 2; +} + +message PerfProfileResponse { + bytes data = 1; +} diff --git a/pkg/rpc/necoperf_grpc.pb.go b/pkg/rpc/necoperf_grpc.pb.go new file mode 100644 index 0000000..646b87e --- /dev/null +++ b/pkg/rpc/necoperf_grpc.pb.go @@ -0,0 +1,136 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.3.0 +// - protoc v4.24.2 +// source: pkg/rpc/necoperf.proto + +package rpc + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +const ( + NecoPerf_Profile_FullMethodName = "/necoperf.NecoPerf/Profile" +) + +// NecoPerfClient is the client API for NecoPerf service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type NecoPerfClient interface { + Profile(ctx context.Context, in *PerfProfileRequest, opts ...grpc.CallOption) (NecoPerf_ProfileClient, error) +} + +type necoPerfClient struct { + cc grpc.ClientConnInterface +} + +func NewNecoPerfClient(cc grpc.ClientConnInterface) NecoPerfClient { + return &necoPerfClient{cc} +} + +func (c *necoPerfClient) Profile(ctx context.Context, in *PerfProfileRequest, opts ...grpc.CallOption) (NecoPerf_ProfileClient, error) { + stream, err := c.cc.NewStream(ctx, &NecoPerf_ServiceDesc.Streams[0], NecoPerf_Profile_FullMethodName, opts...) + if err != nil { + return nil, err + } + x := &necoPerfProfileClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type NecoPerf_ProfileClient interface { + Recv() (*PerfProfileResponse, error) + grpc.ClientStream +} + +type necoPerfProfileClient struct { + grpc.ClientStream +} + +func (x *necoPerfProfileClient) Recv() (*PerfProfileResponse, error) { + m := new(PerfProfileResponse) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// NecoPerfServer is the server API for NecoPerf service. +// All implementations must embed UnimplementedNecoPerfServer +// for forward compatibility +type NecoPerfServer interface { + Profile(*PerfProfileRequest, NecoPerf_ProfileServer) error + mustEmbedUnimplementedNecoPerfServer() +} + +// UnimplementedNecoPerfServer must be embedded to have forward compatible implementations. +type UnimplementedNecoPerfServer struct { +} + +func (UnimplementedNecoPerfServer) Profile(*PerfProfileRequest, NecoPerf_ProfileServer) error { + return status.Errorf(codes.Unimplemented, "method Profile not implemented") +} +func (UnimplementedNecoPerfServer) mustEmbedUnimplementedNecoPerfServer() {} + +// UnsafeNecoPerfServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to NecoPerfServer will +// result in compilation errors. +type UnsafeNecoPerfServer interface { + mustEmbedUnimplementedNecoPerfServer() +} + +func RegisterNecoPerfServer(s grpc.ServiceRegistrar, srv NecoPerfServer) { + s.RegisterService(&NecoPerf_ServiceDesc, srv) +} + +func _NecoPerf_Profile_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(PerfProfileRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(NecoPerfServer).Profile(m, &necoPerfProfileServer{stream}) +} + +type NecoPerf_ProfileServer interface { + Send(*PerfProfileResponse) error + grpc.ServerStream +} + +type necoPerfProfileServer struct { + grpc.ServerStream +} + +func (x *necoPerfProfileServer) Send(m *PerfProfileResponse) error { + return x.ServerStream.SendMsg(m) +} + +// NecoPerf_ServiceDesc is the grpc.ServiceDesc for NecoPerf service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var NecoPerf_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "necoperf.NecoPerf", + HandlerType: (*NecoPerfServer)(nil), + Methods: []grpc.MethodDesc{}, + Streams: []grpc.StreamDesc{ + { + StreamName: "Profile", + Handler: _NecoPerf_Profile_Handler, + ServerStreams: true, + }, + }, + Metadata: "pkg/rpc/necoperf.proto", +}