From e4d1b1c6a7691fa33ea82b0d5064c3b037226ab5 Mon Sep 17 00:00:00 2001 From: Harshit Pathak Date: Wed, 22 Apr 2026 19:28:04 +0000 Subject: [PATCH] feat: add go-memory-load-grpc sample app --- go-memory-load-grpc/.dockerignore | 19 + go-memory-load-grpc/.env.example | 5 + go-memory-load-grpc/Dockerfile | 19 + go-memory-load-grpc/api/proto/loadtest.pb.go | 1801 +++++++++++++++++ go-memory-load-grpc/api/proto/loadtest.proto | 187 ++ .../api/proto/loadtest_grpc.pb.go | 477 +++++ go-memory-load-grpc/cmd/api/main.go | 69 + go-memory-load-grpc/docker-compose.yml | 24 + go-memory-load-grpc/go.mod | 15 + go-memory-load-grpc/go.sum | 34 + go-memory-load-grpc/internal/config/config.go | 25 + .../internal/grpcapi/server.go | 256 +++ go-memory-load-grpc/internal/store/store.go | 455 +++++ go-memory-load-grpc/keploy.yml | 110 + go-memory-load-grpc/loadtest/scenario.js | 178 ++ 15 files changed, 3674 insertions(+) create mode 100644 go-memory-load-grpc/.dockerignore create mode 100644 go-memory-load-grpc/.env.example create mode 100644 go-memory-load-grpc/Dockerfile create mode 100644 go-memory-load-grpc/api/proto/loadtest.pb.go create mode 100644 go-memory-load-grpc/api/proto/loadtest.proto create mode 100644 go-memory-load-grpc/api/proto/loadtest_grpc.pb.go create mode 100644 go-memory-load-grpc/cmd/api/main.go create mode 100644 go-memory-load-grpc/docker-compose.yml create mode 100644 go-memory-load-grpc/go.mod create mode 100644 go-memory-load-grpc/go.sum create mode 100644 go-memory-load-grpc/internal/config/config.go create mode 100644 go-memory-load-grpc/internal/grpcapi/server.go create mode 100644 go-memory-load-grpc/internal/store/store.go create mode 100755 go-memory-load-grpc/keploy.yml create mode 100644 go-memory-load-grpc/loadtest/scenario.js diff --git a/go-memory-load-grpc/.dockerignore b/go-memory-load-grpc/.dockerignore new file mode 100644 index 00000000..0d37e099 --- /dev/null +++ b/go-memory-load-grpc/.dockerignore @@ -0,0 +1,19 @@ +# Build artifacts +/bin/ +*.exe + +# Go module cache +/vendor/ + +# IDE +.idea/ +.vscode/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Test output +coverage.out diff --git a/go-memory-load-grpc/.env.example b/go-memory-load-grpc/.env.example new file mode 100644 index 00000000..ff7df505 --- /dev/null +++ b/go-memory-load-grpc/.env.example @@ -0,0 +1,5 @@ +# HTTP port for the health-check endpoint +APP_HTTP_PORT=8080 + +# gRPC port for the LoadTestService +APP_GRPC_PORT=50051 diff --git a/go-memory-load-grpc/Dockerfile b/go-memory-load-grpc/Dockerfile new file mode 100644 index 00000000..4f96a901 --- /dev/null +++ b/go-memory-load-grpc/Dockerfile @@ -0,0 +1,19 @@ +FROM golang:1.26-alpine AS build + +WORKDIR /app + +COPY go.mod go.sum* ./ +RUN go mod download + +COPY . . +RUN go build -o /bin/api ./cmd/api + +FROM alpine:3.22 + +WORKDIR /app +COPY --from=build /bin/api /app/api + +EXPOSE 8080 +EXPOSE 50051 + +CMD ["/app/api"] diff --git a/go-memory-load-grpc/api/proto/loadtest.pb.go b/go-memory-load-grpc/api/proto/loadtest.pb.go new file mode 100644 index 00000000..b0be3f45 --- /dev/null +++ b/go-memory-load-grpc/api/proto/loadtest.pb.go @@ -0,0 +1,1801 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc v4.25.3 +// source: api/proto/loadtest.proto + +package loadtestv1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Customer struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Email string `protobuf:"bytes,2,opt,name=email,proto3" json:"email,omitempty"` + FullName string `protobuf:"bytes,3,opt,name=full_name,json=fullName,proto3" json:"full_name,omitempty"` + Segment string `protobuf:"bytes,4,opt,name=segment,proto3" json:"segment,omitempty"` + CreatedAt string `protobuf:"bytes,5,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` // RFC3339 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Customer) Reset() { + *x = Customer{} + mi := &file_api_proto_loadtest_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Customer) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Customer) ProtoMessage() {} + +func (x *Customer) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_loadtest_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Customer.ProtoReflect.Descriptor instead. +func (*Customer) Descriptor() ([]byte, []int) { + return file_api_proto_loadtest_proto_rawDescGZIP(), []int{0} +} + +func (x *Customer) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Customer) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *Customer) GetFullName() string { + if x != nil { + return x.FullName + } + return "" +} + +func (x *Customer) GetSegment() string { + if x != nil { + return x.Segment + } + return "" +} + +func (x *Customer) GetCreatedAt() string { + if x != nil { + return x.CreatedAt + } + return "" +} + +type CreateCustomerRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Email string `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"` + FullName string `protobuf:"bytes,2,opt,name=full_name,json=fullName,proto3" json:"full_name,omitempty"` + Segment string `protobuf:"bytes,3,opt,name=segment,proto3" json:"segment,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateCustomerRequest) Reset() { + *x = CreateCustomerRequest{} + mi := &file_api_proto_loadtest_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateCustomerRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateCustomerRequest) ProtoMessage() {} + +func (x *CreateCustomerRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_loadtest_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateCustomerRequest.ProtoReflect.Descriptor instead. +func (*CreateCustomerRequest) Descriptor() ([]byte, []int) { + return file_api_proto_loadtest_proto_rawDescGZIP(), []int{1} +} + +func (x *CreateCustomerRequest) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *CreateCustomerRequest) GetFullName() string { + if x != nil { + return x.FullName + } + return "" +} + +func (x *CreateCustomerRequest) GetSegment() string { + if x != nil { + return x.Segment + } + return "" +} + +type GetCustomerSummaryRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + CustomerId string `protobuf:"bytes,1,opt,name=customer_id,json=customerId,proto3" json:"customer_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetCustomerSummaryRequest) Reset() { + *x = GetCustomerSummaryRequest{} + mi := &file_api_proto_loadtest_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetCustomerSummaryRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetCustomerSummaryRequest) ProtoMessage() {} + +func (x *GetCustomerSummaryRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_loadtest_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetCustomerSummaryRequest.ProtoReflect.Descriptor instead. +func (*GetCustomerSummaryRequest) Descriptor() ([]byte, []int) { + return file_api_proto_loadtest_proto_rawDescGZIP(), []int{2} +} + +func (x *GetCustomerSummaryRequest) GetCustomerId() string { + if x != nil { + return x.CustomerId + } + return "" +} + +type CustomerSummary struct { + state protoimpl.MessageState `protogen:"open.v1"` + Customer *Customer `protobuf:"bytes,1,opt,name=customer,proto3" json:"customer,omitempty"` + OrdersCount int32 `protobuf:"varint,2,opt,name=orders_count,json=ordersCount,proto3" json:"orders_count,omitempty"` + LifetimeValueCents int64 `protobuf:"varint,3,opt,name=lifetime_value_cents,json=lifetimeValueCents,proto3" json:"lifetime_value_cents,omitempty"` + AverageOrderValueCents int64 `protobuf:"varint,4,opt,name=average_order_value_cents,json=averageOrderValueCents,proto3" json:"average_order_value_cents,omitempty"` + FavoriteCategory string `protobuf:"bytes,5,opt,name=favorite_category,json=favoriteCategory,proto3" json:"favorite_category,omitempty"` + LastOrderAt string `protobuf:"bytes,6,opt,name=last_order_at,json=lastOrderAt,proto3" json:"last_order_at,omitempty"` // RFC3339, empty if no orders + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CustomerSummary) Reset() { + *x = CustomerSummary{} + mi := &file_api_proto_loadtest_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CustomerSummary) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CustomerSummary) ProtoMessage() {} + +func (x *CustomerSummary) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_loadtest_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CustomerSummary.ProtoReflect.Descriptor instead. +func (*CustomerSummary) Descriptor() ([]byte, []int) { + return file_api_proto_loadtest_proto_rawDescGZIP(), []int{3} +} + +func (x *CustomerSummary) GetCustomer() *Customer { + if x != nil { + return x.Customer + } + return nil +} + +func (x *CustomerSummary) GetOrdersCount() int32 { + if x != nil { + return x.OrdersCount + } + return 0 +} + +func (x *CustomerSummary) GetLifetimeValueCents() int64 { + if x != nil { + return x.LifetimeValueCents + } + return 0 +} + +func (x *CustomerSummary) GetAverageOrderValueCents() int64 { + if x != nil { + return x.AverageOrderValueCents + } + return 0 +} + +func (x *CustomerSummary) GetFavoriteCategory() string { + if x != nil { + return x.FavoriteCategory + } + return "" +} + +func (x *CustomerSummary) GetLastOrderAt() string { + if x != nil { + return x.LastOrderAt + } + return "" +} + +type Product struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Sku string `protobuf:"bytes,2,opt,name=sku,proto3" json:"sku,omitempty"` + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + Category string `protobuf:"bytes,4,opt,name=category,proto3" json:"category,omitempty"` + PriceCents int32 `protobuf:"varint,5,opt,name=price_cents,json=priceCents,proto3" json:"price_cents,omitempty"` + InventoryCount int32 `protobuf:"varint,6,opt,name=inventory_count,json=inventoryCount,proto3" json:"inventory_count,omitempty"` + CreatedAt string `protobuf:"bytes,7,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` // RFC3339 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Product) Reset() { + *x = Product{} + mi := &file_api_proto_loadtest_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Product) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Product) ProtoMessage() {} + +func (x *Product) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_loadtest_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Product.ProtoReflect.Descriptor instead. +func (*Product) Descriptor() ([]byte, []int) { + return file_api_proto_loadtest_proto_rawDescGZIP(), []int{4} +} + +func (x *Product) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Product) GetSku() string { + if x != nil { + return x.Sku + } + return "" +} + +func (x *Product) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Product) GetCategory() string { + if x != nil { + return x.Category + } + return "" +} + +func (x *Product) GetPriceCents() int32 { + if x != nil { + return x.PriceCents + } + return 0 +} + +func (x *Product) GetInventoryCount() int32 { + if x != nil { + return x.InventoryCount + } + return 0 +} + +func (x *Product) GetCreatedAt() string { + if x != nil { + return x.CreatedAt + } + return "" +} + +type CreateProductRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Sku string `protobuf:"bytes,1,opt,name=sku,proto3" json:"sku,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Category string `protobuf:"bytes,3,opt,name=category,proto3" json:"category,omitempty"` + PriceCents int32 `protobuf:"varint,4,opt,name=price_cents,json=priceCents,proto3" json:"price_cents,omitempty"` + InventoryCount int32 `protobuf:"varint,5,opt,name=inventory_count,json=inventoryCount,proto3" json:"inventory_count,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateProductRequest) Reset() { + *x = CreateProductRequest{} + mi := &file_api_proto_loadtest_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateProductRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateProductRequest) ProtoMessage() {} + +func (x *CreateProductRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_loadtest_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateProductRequest.ProtoReflect.Descriptor instead. +func (*CreateProductRequest) Descriptor() ([]byte, []int) { + return file_api_proto_loadtest_proto_rawDescGZIP(), []int{5} +} + +func (x *CreateProductRequest) GetSku() string { + if x != nil { + return x.Sku + } + return "" +} + +func (x *CreateProductRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *CreateProductRequest) GetCategory() string { + if x != nil { + return x.Category + } + return "" +} + +func (x *CreateProductRequest) GetPriceCents() int32 { + if x != nil { + return x.PriceCents + } + return 0 +} + +func (x *CreateProductRequest) GetInventoryCount() int32 { + if x != nil { + return x.InventoryCount + } + return 0 +} + +type OrderItem struct { + state protoimpl.MessageState `protogen:"open.v1"` + ProductId string `protobuf:"bytes,1,opt,name=product_id,json=productId,proto3" json:"product_id,omitempty"` + Sku string `protobuf:"bytes,2,opt,name=sku,proto3" json:"sku,omitempty"` + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + Category string `protobuf:"bytes,4,opt,name=category,proto3" json:"category,omitempty"` + Quantity int32 `protobuf:"varint,5,opt,name=quantity,proto3" json:"quantity,omitempty"` + UnitPriceCents int32 `protobuf:"varint,6,opt,name=unit_price_cents,json=unitPriceCents,proto3" json:"unit_price_cents,omitempty"` + LineTotalCents int32 `protobuf:"varint,7,opt,name=line_total_cents,json=lineTotalCents,proto3" json:"line_total_cents,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *OrderItem) Reset() { + *x = OrderItem{} + mi := &file_api_proto_loadtest_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *OrderItem) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OrderItem) ProtoMessage() {} + +func (x *OrderItem) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_loadtest_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OrderItem.ProtoReflect.Descriptor instead. +func (*OrderItem) Descriptor() ([]byte, []int) { + return file_api_proto_loadtest_proto_rawDescGZIP(), []int{6} +} + +func (x *OrderItem) GetProductId() string { + if x != nil { + return x.ProductId + } + return "" +} + +func (x *OrderItem) GetSku() string { + if x != nil { + return x.Sku + } + return "" +} + +func (x *OrderItem) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *OrderItem) GetCategory() string { + if x != nil { + return x.Category + } + return "" +} + +func (x *OrderItem) GetQuantity() int32 { + if x != nil { + return x.Quantity + } + return 0 +} + +func (x *OrderItem) GetUnitPriceCents() int32 { + if x != nil { + return x.UnitPriceCents + } + return 0 +} + +func (x *OrderItem) GetLineTotalCents() int32 { + if x != nil { + return x.LineTotalCents + } + return 0 +} + +type Order struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Customer *Customer `protobuf:"bytes,2,opt,name=customer,proto3" json:"customer,omitempty"` + Status string `protobuf:"bytes,3,opt,name=status,proto3" json:"status,omitempty"` + TotalCents int32 `protobuf:"varint,4,opt,name=total_cents,json=totalCents,proto3" json:"total_cents,omitempty"` + CreatedAt string `protobuf:"bytes,5,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` // RFC3339 + Items []*OrderItem `protobuf:"bytes,6,rep,name=items,proto3" json:"items,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Order) Reset() { + *x = Order{} + mi := &file_api_proto_loadtest_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Order) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Order) ProtoMessage() {} + +func (x *Order) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_loadtest_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Order.ProtoReflect.Descriptor instead. +func (*Order) Descriptor() ([]byte, []int) { + return file_api_proto_loadtest_proto_rawDescGZIP(), []int{7} +} + +func (x *Order) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Order) GetCustomer() *Customer { + if x != nil { + return x.Customer + } + return nil +} + +func (x *Order) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *Order) GetTotalCents() int32 { + if x != nil { + return x.TotalCents + } + return 0 +} + +func (x *Order) GetCreatedAt() string { + if x != nil { + return x.CreatedAt + } + return "" +} + +func (x *Order) GetItems() []*OrderItem { + if x != nil { + return x.Items + } + return nil +} + +type OrderItemInput struct { + state protoimpl.MessageState `protogen:"open.v1"` + ProductId string `protobuf:"bytes,1,opt,name=product_id,json=productId,proto3" json:"product_id,omitempty"` + Quantity int32 `protobuf:"varint,2,opt,name=quantity,proto3" json:"quantity,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *OrderItemInput) Reset() { + *x = OrderItemInput{} + mi := &file_api_proto_loadtest_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *OrderItemInput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OrderItemInput) ProtoMessage() {} + +func (x *OrderItemInput) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_loadtest_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OrderItemInput.ProtoReflect.Descriptor instead. +func (*OrderItemInput) Descriptor() ([]byte, []int) { + return file_api_proto_loadtest_proto_rawDescGZIP(), []int{8} +} + +func (x *OrderItemInput) GetProductId() string { + if x != nil { + return x.ProductId + } + return "" +} + +func (x *OrderItemInput) GetQuantity() int32 { + if x != nil { + return x.Quantity + } + return 0 +} + +type CreateOrderRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + CustomerId string `protobuf:"bytes,1,opt,name=customer_id,json=customerId,proto3" json:"customer_id,omitempty"` + Status string `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"` + Items []*OrderItemInput `protobuf:"bytes,3,rep,name=items,proto3" json:"items,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateOrderRequest) Reset() { + *x = CreateOrderRequest{} + mi := &file_api_proto_loadtest_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateOrderRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateOrderRequest) ProtoMessage() {} + +func (x *CreateOrderRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_loadtest_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateOrderRequest.ProtoReflect.Descriptor instead. +func (*CreateOrderRequest) Descriptor() ([]byte, []int) { + return file_api_proto_loadtest_proto_rawDescGZIP(), []int{9} +} + +func (x *CreateOrderRequest) GetCustomerId() string { + if x != nil { + return x.CustomerId + } + return "" +} + +func (x *CreateOrderRequest) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *CreateOrderRequest) GetItems() []*OrderItemInput { + if x != nil { + return x.Items + } + return nil +} + +type GetOrderRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + OrderId string `protobuf:"bytes,1,opt,name=order_id,json=orderId,proto3" json:"order_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetOrderRequest) Reset() { + *x = GetOrderRequest{} + mi := &file_api_proto_loadtest_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetOrderRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetOrderRequest) ProtoMessage() {} + +func (x *GetOrderRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_loadtest_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetOrderRequest.ProtoReflect.Descriptor instead. +func (*GetOrderRequest) Descriptor() ([]byte, []int) { + return file_api_proto_loadtest_proto_rawDescGZIP(), []int{10} +} + +func (x *GetOrderRequest) GetOrderId() string { + if x != nil { + return x.OrderId + } + return "" +} + +type SearchOrdersRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` + CustomerId string `protobuf:"bytes,2,opt,name=customer_id,json=customerId,proto3" json:"customer_id,omitempty"` + MinTotalCents int64 `protobuf:"varint,3,opt,name=min_total_cents,json=minTotalCents,proto3" json:"min_total_cents,omitempty"` + CreatedFrom string `protobuf:"bytes,4,opt,name=created_from,json=createdFrom,proto3" json:"created_from,omitempty"` // RFC3339 + CreatedThrough string `protobuf:"bytes,5,opt,name=created_through,json=createdThrough,proto3" json:"created_through,omitempty"` // RFC3339 + Limit int32 `protobuf:"varint,6,opt,name=limit,proto3" json:"limit,omitempty"` + Offset int32 `protobuf:"varint,7,opt,name=offset,proto3" json:"offset,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SearchOrdersRequest) Reset() { + *x = SearchOrdersRequest{} + mi := &file_api_proto_loadtest_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SearchOrdersRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SearchOrdersRequest) ProtoMessage() {} + +func (x *SearchOrdersRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_loadtest_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SearchOrdersRequest.ProtoReflect.Descriptor instead. +func (*SearchOrdersRequest) Descriptor() ([]byte, []int) { + return file_api_proto_loadtest_proto_rawDescGZIP(), []int{11} +} + +func (x *SearchOrdersRequest) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *SearchOrdersRequest) GetCustomerId() string { + if x != nil { + return x.CustomerId + } + return "" +} + +func (x *SearchOrdersRequest) GetMinTotalCents() int64 { + if x != nil { + return x.MinTotalCents + } + return 0 +} + +func (x *SearchOrdersRequest) GetCreatedFrom() string { + if x != nil { + return x.CreatedFrom + } + return "" +} + +func (x *SearchOrdersRequest) GetCreatedThrough() string { + if x != nil { + return x.CreatedThrough + } + return "" +} + +func (x *SearchOrdersRequest) GetLimit() int32 { + if x != nil { + return x.Limit + } + return 0 +} + +func (x *SearchOrdersRequest) GetOffset() int32 { + if x != nil { + return x.Offset + } + return 0 +} + +type OrderSearchResult struct { + state protoimpl.MessageState `protogen:"open.v1"` + OrderId string `protobuf:"bytes,1,opt,name=order_id,json=orderId,proto3" json:"order_id,omitempty"` + CustomerId string `protobuf:"bytes,2,opt,name=customer_id,json=customerId,proto3" json:"customer_id,omitempty"` + CustomerName string `protobuf:"bytes,3,opt,name=customer_name,json=customerName,proto3" json:"customer_name,omitempty"` + Status string `protobuf:"bytes,4,opt,name=status,proto3" json:"status,omitempty"` + TotalCents int32 `protobuf:"varint,5,opt,name=total_cents,json=totalCents,proto3" json:"total_cents,omitempty"` + CreatedAt string `protobuf:"bytes,6,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` // RFC3339 + TotalItems int32 `protobuf:"varint,7,opt,name=total_items,json=totalItems,proto3" json:"total_items,omitempty"` + DistinctProducts int32 `protobuf:"varint,8,opt,name=distinct_products,json=distinctProducts,proto3" json:"distinct_products,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *OrderSearchResult) Reset() { + *x = OrderSearchResult{} + mi := &file_api_proto_loadtest_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *OrderSearchResult) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OrderSearchResult) ProtoMessage() {} + +func (x *OrderSearchResult) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_loadtest_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OrderSearchResult.ProtoReflect.Descriptor instead. +func (*OrderSearchResult) Descriptor() ([]byte, []int) { + return file_api_proto_loadtest_proto_rawDescGZIP(), []int{12} +} + +func (x *OrderSearchResult) GetOrderId() string { + if x != nil { + return x.OrderId + } + return "" +} + +func (x *OrderSearchResult) GetCustomerId() string { + if x != nil { + return x.CustomerId + } + return "" +} + +func (x *OrderSearchResult) GetCustomerName() string { + if x != nil { + return x.CustomerName + } + return "" +} + +func (x *OrderSearchResult) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *OrderSearchResult) GetTotalCents() int32 { + if x != nil { + return x.TotalCents + } + return 0 +} + +func (x *OrderSearchResult) GetCreatedAt() string { + if x != nil { + return x.CreatedAt + } + return "" +} + +func (x *OrderSearchResult) GetTotalItems() int32 { + if x != nil { + return x.TotalItems + } + return 0 +} + +func (x *OrderSearchResult) GetDistinctProducts() int32 { + if x != nil { + return x.DistinctProducts + } + return 0 +} + +type SearchOrdersResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Results []*OrderSearchResult `protobuf:"bytes,1,rep,name=results,proto3" json:"results,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SearchOrdersResponse) Reset() { + *x = SearchOrdersResponse{} + mi := &file_api_proto_loadtest_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SearchOrdersResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SearchOrdersResponse) ProtoMessage() {} + +func (x *SearchOrdersResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_loadtest_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SearchOrdersResponse.ProtoReflect.Descriptor instead. +func (*SearchOrdersResponse) Descriptor() ([]byte, []int) { + return file_api_proto_loadtest_proto_rawDescGZIP(), []int{13} +} + +func (x *SearchOrdersResponse) GetResults() []*OrderSearchResult { + if x != nil { + return x.Results + } + return nil +} + +type TopProductsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Days int32 `protobuf:"varint,1,opt,name=days,proto3" json:"days,omitempty"` + Limit int32 `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TopProductsRequest) Reset() { + *x = TopProductsRequest{} + mi := &file_api_proto_loadtest_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TopProductsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TopProductsRequest) ProtoMessage() {} + +func (x *TopProductsRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_loadtest_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TopProductsRequest.ProtoReflect.Descriptor instead. +func (*TopProductsRequest) Descriptor() ([]byte, []int) { + return file_api_proto_loadtest_proto_rawDescGZIP(), []int{14} +} + +func (x *TopProductsRequest) GetDays() int32 { + if x != nil { + return x.Days + } + return 0 +} + +func (x *TopProductsRequest) GetLimit() int32 { + if x != nil { + return x.Limit + } + return 0 +} + +type TopProduct struct { + state protoimpl.MessageState `protogen:"open.v1"` + ProductId string `protobuf:"bytes,1,opt,name=product_id,json=productId,proto3" json:"product_id,omitempty"` + Sku string `protobuf:"bytes,2,opt,name=sku,proto3" json:"sku,omitempty"` + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + Category string `protobuf:"bytes,4,opt,name=category,proto3" json:"category,omitempty"` + UnitsSold int32 `protobuf:"varint,5,opt,name=units_sold,json=unitsSold,proto3" json:"units_sold,omitempty"` + RevenueCents int64 `protobuf:"varint,6,opt,name=revenue_cents,json=revenueCents,proto3" json:"revenue_cents,omitempty"` + OrdersCount int32 `protobuf:"varint,7,opt,name=orders_count,json=ordersCount,proto3" json:"orders_count,omitempty"` + RevenueRank int32 `protobuf:"varint,8,opt,name=revenue_rank,json=revenueRank,proto3" json:"revenue_rank,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TopProduct) Reset() { + *x = TopProduct{} + mi := &file_api_proto_loadtest_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TopProduct) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TopProduct) ProtoMessage() {} + +func (x *TopProduct) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_loadtest_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TopProduct.ProtoReflect.Descriptor instead. +func (*TopProduct) Descriptor() ([]byte, []int) { + return file_api_proto_loadtest_proto_rawDescGZIP(), []int{15} +} + +func (x *TopProduct) GetProductId() string { + if x != nil { + return x.ProductId + } + return "" +} + +func (x *TopProduct) GetSku() string { + if x != nil { + return x.Sku + } + return "" +} + +func (x *TopProduct) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *TopProduct) GetCategory() string { + if x != nil { + return x.Category + } + return "" +} + +func (x *TopProduct) GetUnitsSold() int32 { + if x != nil { + return x.UnitsSold + } + return 0 +} + +func (x *TopProduct) GetRevenueCents() int64 { + if x != nil { + return x.RevenueCents + } + return 0 +} + +func (x *TopProduct) GetOrdersCount() int32 { + if x != nil { + return x.OrdersCount + } + return 0 +} + +func (x *TopProduct) GetRevenueRank() int32 { + if x != nil { + return x.RevenueRank + } + return 0 +} + +type TopProductsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Products []*TopProduct `protobuf:"bytes,1,rep,name=products,proto3" json:"products,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TopProductsResponse) Reset() { + *x = TopProductsResponse{} + mi := &file_api_proto_loadtest_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TopProductsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TopProductsResponse) ProtoMessage() {} + +func (x *TopProductsResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_loadtest_proto_msgTypes[16] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TopProductsResponse.ProtoReflect.Descriptor instead. +func (*TopProductsResponse) Descriptor() ([]byte, []int) { + return file_api_proto_loadtest_proto_rawDescGZIP(), []int{16} +} + +func (x *TopProductsResponse) GetProducts() []*TopProduct { + if x != nil { + return x.Products + } + return nil +} + +type LargePayloadRecord struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + ContentType string `protobuf:"bytes,3,opt,name=content_type,json=contentType,proto3" json:"content_type,omitempty"` + PayloadSizeBytes int64 `protobuf:"varint,4,opt,name=payload_size_bytes,json=payloadSizeBytes,proto3" json:"payload_size_bytes,omitempty"` + Sha256 string `protobuf:"bytes,5,opt,name=sha256,proto3" json:"sha256,omitempty"` + CreatedAt string `protobuf:"bytes,6,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` // RFC3339 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *LargePayloadRecord) Reset() { + *x = LargePayloadRecord{} + mi := &file_api_proto_loadtest_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LargePayloadRecord) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LargePayloadRecord) ProtoMessage() {} + +func (x *LargePayloadRecord) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_loadtest_proto_msgTypes[17] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LargePayloadRecord.ProtoReflect.Descriptor instead. +func (*LargePayloadRecord) Descriptor() ([]byte, []int) { + return file_api_proto_loadtest_proto_rawDescGZIP(), []int{17} +} + +func (x *LargePayloadRecord) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *LargePayloadRecord) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *LargePayloadRecord) GetContentType() string { + if x != nil { + return x.ContentType + } + return "" +} + +func (x *LargePayloadRecord) GetPayloadSizeBytes() int64 { + if x != nil { + return x.PayloadSizeBytes + } + return 0 +} + +func (x *LargePayloadRecord) GetSha256() string { + if x != nil { + return x.Sha256 + } + return "" +} + +func (x *LargePayloadRecord) GetCreatedAt() string { + if x != nil { + return x.CreatedAt + } + return "" +} + +type LargePayloadDetail struct { + state protoimpl.MessageState `protogen:"open.v1"` + Record *LargePayloadRecord `protobuf:"bytes,1,opt,name=record,proto3" json:"record,omitempty"` + Payload string `protobuf:"bytes,2,opt,name=payload,proto3" json:"payload,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *LargePayloadDetail) Reset() { + *x = LargePayloadDetail{} + mi := &file_api_proto_loadtest_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LargePayloadDetail) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LargePayloadDetail) ProtoMessage() {} + +func (x *LargePayloadDetail) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_loadtest_proto_msgTypes[18] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LargePayloadDetail.ProtoReflect.Descriptor instead. +func (*LargePayloadDetail) Descriptor() ([]byte, []int) { + return file_api_proto_loadtest_proto_rawDescGZIP(), []int{18} +} + +func (x *LargePayloadDetail) GetRecord() *LargePayloadRecord { + if x != nil { + return x.Record + } + return nil +} + +func (x *LargePayloadDetail) GetPayload() string { + if x != nil { + return x.Payload + } + return "" +} + +type CreateLargePayloadRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + ContentType string `protobuf:"bytes,2,opt,name=content_type,json=contentType,proto3" json:"content_type,omitempty"` + Payload string `protobuf:"bytes,3,opt,name=payload,proto3" json:"payload,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateLargePayloadRequest) Reset() { + *x = CreateLargePayloadRequest{} + mi := &file_api_proto_loadtest_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateLargePayloadRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateLargePayloadRequest) ProtoMessage() {} + +func (x *CreateLargePayloadRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_loadtest_proto_msgTypes[19] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateLargePayloadRequest.ProtoReflect.Descriptor instead. +func (*CreateLargePayloadRequest) Descriptor() ([]byte, []int) { + return file_api_proto_loadtest_proto_rawDescGZIP(), []int{19} +} + +func (x *CreateLargePayloadRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *CreateLargePayloadRequest) GetContentType() string { + if x != nil { + return x.ContentType + } + return "" +} + +func (x *CreateLargePayloadRequest) GetPayload() string { + if x != nil { + return x.Payload + } + return "" +} + +type GetLargePayloadRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + PayloadId string `protobuf:"bytes,1,opt,name=payload_id,json=payloadId,proto3" json:"payload_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetLargePayloadRequest) Reset() { + *x = GetLargePayloadRequest{} + mi := &file_api_proto_loadtest_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetLargePayloadRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetLargePayloadRequest) ProtoMessage() {} + +func (x *GetLargePayloadRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_loadtest_proto_msgTypes[20] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetLargePayloadRequest.ProtoReflect.Descriptor instead. +func (*GetLargePayloadRequest) Descriptor() ([]byte, []int) { + return file_api_proto_loadtest_proto_rawDescGZIP(), []int{20} +} + +func (x *GetLargePayloadRequest) GetPayloadId() string { + if x != nil { + return x.PayloadId + } + return "" +} + +type DeleteLargePayloadRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + PayloadId string `protobuf:"bytes,1,opt,name=payload_id,json=payloadId,proto3" json:"payload_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteLargePayloadRequest) Reset() { + *x = DeleteLargePayloadRequest{} + mi := &file_api_proto_loadtest_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteLargePayloadRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteLargePayloadRequest) ProtoMessage() {} + +func (x *DeleteLargePayloadRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_loadtest_proto_msgTypes[21] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteLargePayloadRequest.ProtoReflect.Descriptor instead. +func (*DeleteLargePayloadRequest) Descriptor() ([]byte, []int) { + return file_api_proto_loadtest_proto_rawDescGZIP(), []int{21} +} + +func (x *DeleteLargePayloadRequest) GetPayloadId() string { + if x != nil { + return x.PayloadId + } + return "" +} + +type DeleteLargePayloadResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Deleted bool `protobuf:"varint,1,opt,name=deleted,proto3" json:"deleted,omitempty"` + Record *LargePayloadRecord `protobuf:"bytes,2,opt,name=record,proto3" json:"record,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteLargePayloadResponse) Reset() { + *x = DeleteLargePayloadResponse{} + mi := &file_api_proto_loadtest_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteLargePayloadResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteLargePayloadResponse) ProtoMessage() {} + +func (x *DeleteLargePayloadResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_loadtest_proto_msgTypes[22] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteLargePayloadResponse.ProtoReflect.Descriptor instead. +func (*DeleteLargePayloadResponse) Descriptor() ([]byte, []int) { + return file_api_proto_loadtest_proto_rawDescGZIP(), []int{22} +} + +func (x *DeleteLargePayloadResponse) GetDeleted() bool { + if x != nil { + return x.Deleted + } + return false +} + +func (x *DeleteLargePayloadResponse) GetRecord() *LargePayloadRecord { + if x != nil { + return x.Record + } + return nil +} + +var File_api_proto_loadtest_proto protoreflect.FileDescriptor + +const file_api_proto_loadtest_proto_rawDesc = "" + + "\n" + + "\x18api/proto/loadtest.proto\x12\vloadtest.v1\"\x86\x01\n" + + "\bCustomer\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x14\n" + + "\x05email\x18\x02 \x01(\tR\x05email\x12\x1b\n" + + "\tfull_name\x18\x03 \x01(\tR\bfullName\x12\x18\n" + + "\asegment\x18\x04 \x01(\tR\asegment\x12\x1d\n" + + "\n" + + "created_at\x18\x05 \x01(\tR\tcreatedAt\"d\n" + + "\x15CreateCustomerRequest\x12\x14\n" + + "\x05email\x18\x01 \x01(\tR\x05email\x12\x1b\n" + + "\tfull_name\x18\x02 \x01(\tR\bfullName\x12\x18\n" + + "\asegment\x18\x03 \x01(\tR\asegment\"<\n" + + "\x19GetCustomerSummaryRequest\x12\x1f\n" + + "\vcustomer_id\x18\x01 \x01(\tR\n" + + "customerId\"\xa5\x02\n" + + "\x0fCustomerSummary\x121\n" + + "\bcustomer\x18\x01 \x01(\v2\x15.loadtest.v1.CustomerR\bcustomer\x12!\n" + + "\forders_count\x18\x02 \x01(\x05R\vordersCount\x120\n" + + "\x14lifetime_value_cents\x18\x03 \x01(\x03R\x12lifetimeValueCents\x129\n" + + "\x19average_order_value_cents\x18\x04 \x01(\x03R\x16averageOrderValueCents\x12+\n" + + "\x11favorite_category\x18\x05 \x01(\tR\x10favoriteCategory\x12\"\n" + + "\rlast_order_at\x18\x06 \x01(\tR\vlastOrderAt\"\xc4\x01\n" + + "\aProduct\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x10\n" + + "\x03sku\x18\x02 \x01(\tR\x03sku\x12\x12\n" + + "\x04name\x18\x03 \x01(\tR\x04name\x12\x1a\n" + + "\bcategory\x18\x04 \x01(\tR\bcategory\x12\x1f\n" + + "\vprice_cents\x18\x05 \x01(\x05R\n" + + "priceCents\x12'\n" + + "\x0finventory_count\x18\x06 \x01(\x05R\x0einventoryCount\x12\x1d\n" + + "\n" + + "created_at\x18\a \x01(\tR\tcreatedAt\"\xa2\x01\n" + + "\x14CreateProductRequest\x12\x10\n" + + "\x03sku\x18\x01 \x01(\tR\x03sku\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12\x1a\n" + + "\bcategory\x18\x03 \x01(\tR\bcategory\x12\x1f\n" + + "\vprice_cents\x18\x04 \x01(\x05R\n" + + "priceCents\x12'\n" + + "\x0finventory_count\x18\x05 \x01(\x05R\x0einventoryCount\"\xdc\x01\n" + + "\tOrderItem\x12\x1d\n" + + "\n" + + "product_id\x18\x01 \x01(\tR\tproductId\x12\x10\n" + + "\x03sku\x18\x02 \x01(\tR\x03sku\x12\x12\n" + + "\x04name\x18\x03 \x01(\tR\x04name\x12\x1a\n" + + "\bcategory\x18\x04 \x01(\tR\bcategory\x12\x1a\n" + + "\bquantity\x18\x05 \x01(\x05R\bquantity\x12(\n" + + "\x10unit_price_cents\x18\x06 \x01(\x05R\x0eunitPriceCents\x12(\n" + + "\x10line_total_cents\x18\a \x01(\x05R\x0elineTotalCents\"\xd0\x01\n" + + "\x05Order\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x121\n" + + "\bcustomer\x18\x02 \x01(\v2\x15.loadtest.v1.CustomerR\bcustomer\x12\x16\n" + + "\x06status\x18\x03 \x01(\tR\x06status\x12\x1f\n" + + "\vtotal_cents\x18\x04 \x01(\x05R\n" + + "totalCents\x12\x1d\n" + + "\n" + + "created_at\x18\x05 \x01(\tR\tcreatedAt\x12,\n" + + "\x05items\x18\x06 \x03(\v2\x16.loadtest.v1.OrderItemR\x05items\"K\n" + + "\x0eOrderItemInput\x12\x1d\n" + + "\n" + + "product_id\x18\x01 \x01(\tR\tproductId\x12\x1a\n" + + "\bquantity\x18\x02 \x01(\x05R\bquantity\"\x80\x01\n" + + "\x12CreateOrderRequest\x12\x1f\n" + + "\vcustomer_id\x18\x01 \x01(\tR\n" + + "customerId\x12\x16\n" + + "\x06status\x18\x02 \x01(\tR\x06status\x121\n" + + "\x05items\x18\x03 \x03(\v2\x1b.loadtest.v1.OrderItemInputR\x05items\",\n" + + "\x0fGetOrderRequest\x12\x19\n" + + "\border_id\x18\x01 \x01(\tR\aorderId\"\xf0\x01\n" + + "\x13SearchOrdersRequest\x12\x16\n" + + "\x06status\x18\x01 \x01(\tR\x06status\x12\x1f\n" + + "\vcustomer_id\x18\x02 \x01(\tR\n" + + "customerId\x12&\n" + + "\x0fmin_total_cents\x18\x03 \x01(\x03R\rminTotalCents\x12!\n" + + "\fcreated_from\x18\x04 \x01(\tR\vcreatedFrom\x12'\n" + + "\x0fcreated_through\x18\x05 \x01(\tR\x0ecreatedThrough\x12\x14\n" + + "\x05limit\x18\x06 \x01(\x05R\x05limit\x12\x16\n" + + "\x06offset\x18\a \x01(\x05R\x06offset\"\x9a\x02\n" + + "\x11OrderSearchResult\x12\x19\n" + + "\border_id\x18\x01 \x01(\tR\aorderId\x12\x1f\n" + + "\vcustomer_id\x18\x02 \x01(\tR\n" + + "customerId\x12#\n" + + "\rcustomer_name\x18\x03 \x01(\tR\fcustomerName\x12\x16\n" + + "\x06status\x18\x04 \x01(\tR\x06status\x12\x1f\n" + + "\vtotal_cents\x18\x05 \x01(\x05R\n" + + "totalCents\x12\x1d\n" + + "\n" + + "created_at\x18\x06 \x01(\tR\tcreatedAt\x12\x1f\n" + + "\vtotal_items\x18\a \x01(\x05R\n" + + "totalItems\x12+\n" + + "\x11distinct_products\x18\b \x01(\x05R\x10distinctProducts\"P\n" + + "\x14SearchOrdersResponse\x128\n" + + "\aresults\x18\x01 \x03(\v2\x1e.loadtest.v1.OrderSearchResultR\aresults\">\n" + + "\x12TopProductsRequest\x12\x12\n" + + "\x04days\x18\x01 \x01(\x05R\x04days\x12\x14\n" + + "\x05limit\x18\x02 \x01(\x05R\x05limit\"\xf7\x01\n" + + "\n" + + "TopProduct\x12\x1d\n" + + "\n" + + "product_id\x18\x01 \x01(\tR\tproductId\x12\x10\n" + + "\x03sku\x18\x02 \x01(\tR\x03sku\x12\x12\n" + + "\x04name\x18\x03 \x01(\tR\x04name\x12\x1a\n" + + "\bcategory\x18\x04 \x01(\tR\bcategory\x12\x1d\n" + + "\n" + + "units_sold\x18\x05 \x01(\x05R\tunitsSold\x12#\n" + + "\rrevenue_cents\x18\x06 \x01(\x03R\frevenueCents\x12!\n" + + "\forders_count\x18\a \x01(\x05R\vordersCount\x12!\n" + + "\frevenue_rank\x18\b \x01(\x05R\vrevenueRank\"J\n" + + "\x13TopProductsResponse\x123\n" + + "\bproducts\x18\x01 \x03(\v2\x17.loadtest.v1.TopProductR\bproducts\"\xc0\x01\n" + + "\x12LargePayloadRecord\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12!\n" + + "\fcontent_type\x18\x03 \x01(\tR\vcontentType\x12,\n" + + "\x12payload_size_bytes\x18\x04 \x01(\x03R\x10payloadSizeBytes\x12\x16\n" + + "\x06sha256\x18\x05 \x01(\tR\x06sha256\x12\x1d\n" + + "\n" + + "created_at\x18\x06 \x01(\tR\tcreatedAt\"g\n" + + "\x12LargePayloadDetail\x127\n" + + "\x06record\x18\x01 \x01(\v2\x1f.loadtest.v1.LargePayloadRecordR\x06record\x12\x18\n" + + "\apayload\x18\x02 \x01(\tR\apayload\"l\n" + + "\x19CreateLargePayloadRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12!\n" + + "\fcontent_type\x18\x02 \x01(\tR\vcontentType\x12\x18\n" + + "\apayload\x18\x03 \x01(\tR\apayload\"7\n" + + "\x16GetLargePayloadRequest\x12\x1d\n" + + "\n" + + "payload_id\x18\x01 \x01(\tR\tpayloadId\":\n" + + "\x19DeleteLargePayloadRequest\x12\x1d\n" + + "\n" + + "payload_id\x18\x01 \x01(\tR\tpayloadId\"o\n" + + "\x1aDeleteLargePayloadResponse\x12\x18\n" + + "\adeleted\x18\x01 \x01(\bR\adeleted\x127\n" + + "\x06record\x18\x02 \x01(\v2\x1f.loadtest.v1.LargePayloadRecordR\x06record2\xcc\x06\n" + + "\x0fLoadTestService\x12K\n" + + "\x0eCreateCustomer\x12\".loadtest.v1.CreateCustomerRequest\x1a\x15.loadtest.v1.Customer\x12Z\n" + + "\x12GetCustomerSummary\x12&.loadtest.v1.GetCustomerSummaryRequest\x1a\x1c.loadtest.v1.CustomerSummary\x12H\n" + + "\rCreateProduct\x12!.loadtest.v1.CreateProductRequest\x1a\x14.loadtest.v1.Product\x12B\n" + + "\vCreateOrder\x12\x1f.loadtest.v1.CreateOrderRequest\x1a\x12.loadtest.v1.Order\x12<\n" + + "\bGetOrder\x12\x1c.loadtest.v1.GetOrderRequest\x1a\x12.loadtest.v1.Order\x12S\n" + + "\fSearchOrders\x12 .loadtest.v1.SearchOrdersRequest\x1a!.loadtest.v1.SearchOrdersResponse\x12P\n" + + "\vTopProducts\x12\x1f.loadtest.v1.TopProductsRequest\x1a .loadtest.v1.TopProductsResponse\x12]\n" + + "\x12CreateLargePayload\x12&.loadtest.v1.CreateLargePayloadRequest\x1a\x1f.loadtest.v1.LargePayloadRecord\x12W\n" + + "\x0fGetLargePayload\x12#.loadtest.v1.GetLargePayloadRequest\x1a\x1f.loadtest.v1.LargePayloadDetail\x12e\n" + + "\x12DeleteLargePayload\x12&.loadtest.v1.DeleteLargePayloadRequest\x1a'.loadtest.v1.DeleteLargePayloadResponseB&Z$loadtestgrpcapi/api/proto/loadtestv1b\x06proto3" + +var ( + file_api_proto_loadtest_proto_rawDescOnce sync.Once + file_api_proto_loadtest_proto_rawDescData []byte +) + +func file_api_proto_loadtest_proto_rawDescGZIP() []byte { + file_api_proto_loadtest_proto_rawDescOnce.Do(func() { + file_api_proto_loadtest_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_api_proto_loadtest_proto_rawDesc), len(file_api_proto_loadtest_proto_rawDesc))) + }) + return file_api_proto_loadtest_proto_rawDescData +} + +var file_api_proto_loadtest_proto_msgTypes = make([]protoimpl.MessageInfo, 23) +var file_api_proto_loadtest_proto_goTypes = []any{ + (*Customer)(nil), // 0: loadtest.v1.Customer + (*CreateCustomerRequest)(nil), // 1: loadtest.v1.CreateCustomerRequest + (*GetCustomerSummaryRequest)(nil), // 2: loadtest.v1.GetCustomerSummaryRequest + (*CustomerSummary)(nil), // 3: loadtest.v1.CustomerSummary + (*Product)(nil), // 4: loadtest.v1.Product + (*CreateProductRequest)(nil), // 5: loadtest.v1.CreateProductRequest + (*OrderItem)(nil), // 6: loadtest.v1.OrderItem + (*Order)(nil), // 7: loadtest.v1.Order + (*OrderItemInput)(nil), // 8: loadtest.v1.OrderItemInput + (*CreateOrderRequest)(nil), // 9: loadtest.v1.CreateOrderRequest + (*GetOrderRequest)(nil), // 10: loadtest.v1.GetOrderRequest + (*SearchOrdersRequest)(nil), // 11: loadtest.v1.SearchOrdersRequest + (*OrderSearchResult)(nil), // 12: loadtest.v1.OrderSearchResult + (*SearchOrdersResponse)(nil), // 13: loadtest.v1.SearchOrdersResponse + (*TopProductsRequest)(nil), // 14: loadtest.v1.TopProductsRequest + (*TopProduct)(nil), // 15: loadtest.v1.TopProduct + (*TopProductsResponse)(nil), // 16: loadtest.v1.TopProductsResponse + (*LargePayloadRecord)(nil), // 17: loadtest.v1.LargePayloadRecord + (*LargePayloadDetail)(nil), // 18: loadtest.v1.LargePayloadDetail + (*CreateLargePayloadRequest)(nil), // 19: loadtest.v1.CreateLargePayloadRequest + (*GetLargePayloadRequest)(nil), // 20: loadtest.v1.GetLargePayloadRequest + (*DeleteLargePayloadRequest)(nil), // 21: loadtest.v1.DeleteLargePayloadRequest + (*DeleteLargePayloadResponse)(nil), // 22: loadtest.v1.DeleteLargePayloadResponse +} +var file_api_proto_loadtest_proto_depIdxs = []int32{ + 0, // 0: loadtest.v1.CustomerSummary.customer:type_name -> loadtest.v1.Customer + 0, // 1: loadtest.v1.Order.customer:type_name -> loadtest.v1.Customer + 6, // 2: loadtest.v1.Order.items:type_name -> loadtest.v1.OrderItem + 8, // 3: loadtest.v1.CreateOrderRequest.items:type_name -> loadtest.v1.OrderItemInput + 12, // 4: loadtest.v1.SearchOrdersResponse.results:type_name -> loadtest.v1.OrderSearchResult + 15, // 5: loadtest.v1.TopProductsResponse.products:type_name -> loadtest.v1.TopProduct + 17, // 6: loadtest.v1.LargePayloadDetail.record:type_name -> loadtest.v1.LargePayloadRecord + 17, // 7: loadtest.v1.DeleteLargePayloadResponse.record:type_name -> loadtest.v1.LargePayloadRecord + 1, // 8: loadtest.v1.LoadTestService.CreateCustomer:input_type -> loadtest.v1.CreateCustomerRequest + 2, // 9: loadtest.v1.LoadTestService.GetCustomerSummary:input_type -> loadtest.v1.GetCustomerSummaryRequest + 5, // 10: loadtest.v1.LoadTestService.CreateProduct:input_type -> loadtest.v1.CreateProductRequest + 9, // 11: loadtest.v1.LoadTestService.CreateOrder:input_type -> loadtest.v1.CreateOrderRequest + 10, // 12: loadtest.v1.LoadTestService.GetOrder:input_type -> loadtest.v1.GetOrderRequest + 11, // 13: loadtest.v1.LoadTestService.SearchOrders:input_type -> loadtest.v1.SearchOrdersRequest + 14, // 14: loadtest.v1.LoadTestService.TopProducts:input_type -> loadtest.v1.TopProductsRequest + 19, // 15: loadtest.v1.LoadTestService.CreateLargePayload:input_type -> loadtest.v1.CreateLargePayloadRequest + 20, // 16: loadtest.v1.LoadTestService.GetLargePayload:input_type -> loadtest.v1.GetLargePayloadRequest + 21, // 17: loadtest.v1.LoadTestService.DeleteLargePayload:input_type -> loadtest.v1.DeleteLargePayloadRequest + 0, // 18: loadtest.v1.LoadTestService.CreateCustomer:output_type -> loadtest.v1.Customer + 3, // 19: loadtest.v1.LoadTestService.GetCustomerSummary:output_type -> loadtest.v1.CustomerSummary + 4, // 20: loadtest.v1.LoadTestService.CreateProduct:output_type -> loadtest.v1.Product + 7, // 21: loadtest.v1.LoadTestService.CreateOrder:output_type -> loadtest.v1.Order + 7, // 22: loadtest.v1.LoadTestService.GetOrder:output_type -> loadtest.v1.Order + 13, // 23: loadtest.v1.LoadTestService.SearchOrders:output_type -> loadtest.v1.SearchOrdersResponse + 16, // 24: loadtest.v1.LoadTestService.TopProducts:output_type -> loadtest.v1.TopProductsResponse + 17, // 25: loadtest.v1.LoadTestService.CreateLargePayload:output_type -> loadtest.v1.LargePayloadRecord + 18, // 26: loadtest.v1.LoadTestService.GetLargePayload:output_type -> loadtest.v1.LargePayloadDetail + 22, // 27: loadtest.v1.LoadTestService.DeleteLargePayload:output_type -> loadtest.v1.DeleteLargePayloadResponse + 18, // [18:28] is the sub-list for method output_type + 8, // [8:18] is the sub-list for method input_type + 8, // [8:8] is the sub-list for extension type_name + 8, // [8:8] is the sub-list for extension extendee + 0, // [0:8] is the sub-list for field type_name +} + +func init() { file_api_proto_loadtest_proto_init() } +func file_api_proto_loadtest_proto_init() { + if File_api_proto_loadtest_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_proto_loadtest_proto_rawDesc), len(file_api_proto_loadtest_proto_rawDesc)), + NumEnums: 0, + NumMessages: 23, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_api_proto_loadtest_proto_goTypes, + DependencyIndexes: file_api_proto_loadtest_proto_depIdxs, + MessageInfos: file_api_proto_loadtest_proto_msgTypes, + }.Build() + File_api_proto_loadtest_proto = out.File + file_api_proto_loadtest_proto_goTypes = nil + file_api_proto_loadtest_proto_depIdxs = nil +} diff --git a/go-memory-load-grpc/api/proto/loadtest.proto b/go-memory-load-grpc/api/proto/loadtest.proto new file mode 100644 index 00000000..e497afb5 --- /dev/null +++ b/go-memory-load-grpc/api/proto/loadtest.proto @@ -0,0 +1,187 @@ +syntax = "proto3"; + +package loadtest.v1; + +option go_package = "loadtestgrpcapi/api/proto/loadtestv1"; + +// LoadTestService exposes CRUD operations for the gRPC memory load test. +service LoadTestService { + // Customer operations + rpc CreateCustomer(CreateCustomerRequest) returns (Customer); + rpc GetCustomerSummary(GetCustomerSummaryRequest) returns (CustomerSummary); + + // Product operations + rpc CreateProduct(CreateProductRequest) returns (Product); + + // Order operations + rpc CreateOrder(CreateOrderRequest) returns (Order); + rpc GetOrder(GetOrderRequest) returns (Order); + rpc SearchOrders(SearchOrdersRequest) returns (SearchOrdersResponse); + + // Analytics + rpc TopProducts(TopProductsRequest) returns (TopProductsResponse); + + // Large payload operations + rpc CreateLargePayload(CreateLargePayloadRequest) returns (LargePayloadRecord); + rpc GetLargePayload(GetLargePayloadRequest) returns (LargePayloadDetail); + rpc DeleteLargePayload(DeleteLargePayloadRequest) returns (DeleteLargePayloadResponse); +} + +// ─── Messages ──────────────────────────────────────────────────────────────── + +message Customer { + string id = 1; + string email = 2; + string full_name = 3; + string segment = 4; + string created_at = 5; // RFC3339 +} + +message CreateCustomerRequest { + string email = 1; + string full_name = 2; + string segment = 3; +} + +message GetCustomerSummaryRequest { + string customer_id = 1; +} + +message CustomerSummary { + Customer customer = 1; + int32 orders_count = 2; + int64 lifetime_value_cents = 3; + int64 average_order_value_cents = 4; + string favorite_category = 5; + string last_order_at = 6; // RFC3339, empty if no orders +} + +message Product { + string id = 1; + string sku = 2; + string name = 3; + string category = 4; + int32 price_cents = 5; + int32 inventory_count = 6; + string created_at = 7; // RFC3339 +} + +message CreateProductRequest { + string sku = 1; + string name = 2; + string category = 3; + int32 price_cents = 4; + int32 inventory_count = 5; +} + +message OrderItem { + string product_id = 1; + string sku = 2; + string name = 3; + string category = 4; + int32 quantity = 5; + int32 unit_price_cents = 6; + int32 line_total_cents = 7; +} + +message Order { + string id = 1; + Customer customer = 2; + string status = 3; + int32 total_cents = 4; + string created_at = 5; // RFC3339 + repeated OrderItem items = 6; +} + +message OrderItemInput { + string product_id = 1; + int32 quantity = 2; +} + +message CreateOrderRequest { + string customer_id = 1; + string status = 2; + repeated OrderItemInput items = 3; +} + +message GetOrderRequest { + string order_id = 1; +} + +message SearchOrdersRequest { + string status = 1; + string customer_id = 2; + int64 min_total_cents = 3; + string created_from = 4; // RFC3339 + string created_through = 5; // RFC3339 + int32 limit = 6; + int32 offset = 7; +} + +message OrderSearchResult { + string order_id = 1; + string customer_id = 2; + string customer_name = 3; + string status = 4; + int32 total_cents = 5; + string created_at = 6; // RFC3339 + int32 total_items = 7; + int32 distinct_products = 8; +} + +message SearchOrdersResponse { + repeated OrderSearchResult results = 1; +} + +message TopProductsRequest { + int32 days = 1; + int32 limit = 2; +} + +message TopProduct { + string product_id = 1; + string sku = 2; + string name = 3; + string category = 4; + int32 units_sold = 5; + int64 revenue_cents = 6; + int32 orders_count = 7; + int32 revenue_rank = 8; +} + +message TopProductsResponse { + repeated TopProduct products = 1; +} + +message LargePayloadRecord { + string id = 1; + string name = 2; + string content_type = 3; + int64 payload_size_bytes = 4; + string sha256 = 5; + string created_at = 6; // RFC3339 +} + +message LargePayloadDetail { + LargePayloadRecord record = 1; + string payload = 2; +} + +message CreateLargePayloadRequest { + string name = 1; + string content_type = 2; + string payload = 3; +} + +message GetLargePayloadRequest { + string payload_id = 1; +} + +message DeleteLargePayloadRequest { + string payload_id = 1; +} + +message DeleteLargePayloadResponse { + bool deleted = 1; + LargePayloadRecord record = 2; +} diff --git a/go-memory-load-grpc/api/proto/loadtest_grpc.pb.go b/go-memory-load-grpc/api/proto/loadtest_grpc.pb.go new file mode 100644 index 00000000..b0e793a4 --- /dev/null +++ b/go-memory-load-grpc/api/proto/loadtest_grpc.pb.go @@ -0,0 +1,477 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.6.1 +// - protoc v4.25.3 +// source: api/proto/loadtest.proto + +package loadtestv1 + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + LoadTestService_CreateCustomer_FullMethodName = "/loadtest.v1.LoadTestService/CreateCustomer" + LoadTestService_GetCustomerSummary_FullMethodName = "/loadtest.v1.LoadTestService/GetCustomerSummary" + LoadTestService_CreateProduct_FullMethodName = "/loadtest.v1.LoadTestService/CreateProduct" + LoadTestService_CreateOrder_FullMethodName = "/loadtest.v1.LoadTestService/CreateOrder" + LoadTestService_GetOrder_FullMethodName = "/loadtest.v1.LoadTestService/GetOrder" + LoadTestService_SearchOrders_FullMethodName = "/loadtest.v1.LoadTestService/SearchOrders" + LoadTestService_TopProducts_FullMethodName = "/loadtest.v1.LoadTestService/TopProducts" + LoadTestService_CreateLargePayload_FullMethodName = "/loadtest.v1.LoadTestService/CreateLargePayload" + LoadTestService_GetLargePayload_FullMethodName = "/loadtest.v1.LoadTestService/GetLargePayload" + LoadTestService_DeleteLargePayload_FullMethodName = "/loadtest.v1.LoadTestService/DeleteLargePayload" +) + +// LoadTestServiceClient is the client API for LoadTestService 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. +// +// LoadTestService exposes CRUD operations for the gRPC memory load test. +type LoadTestServiceClient interface { + // Customer operations + CreateCustomer(ctx context.Context, in *CreateCustomerRequest, opts ...grpc.CallOption) (*Customer, error) + GetCustomerSummary(ctx context.Context, in *GetCustomerSummaryRequest, opts ...grpc.CallOption) (*CustomerSummary, error) + // Product operations + CreateProduct(ctx context.Context, in *CreateProductRequest, opts ...grpc.CallOption) (*Product, error) + // Order operations + CreateOrder(ctx context.Context, in *CreateOrderRequest, opts ...grpc.CallOption) (*Order, error) + GetOrder(ctx context.Context, in *GetOrderRequest, opts ...grpc.CallOption) (*Order, error) + SearchOrders(ctx context.Context, in *SearchOrdersRequest, opts ...grpc.CallOption) (*SearchOrdersResponse, error) + // Analytics + TopProducts(ctx context.Context, in *TopProductsRequest, opts ...grpc.CallOption) (*TopProductsResponse, error) + // Large payload operations + CreateLargePayload(ctx context.Context, in *CreateLargePayloadRequest, opts ...grpc.CallOption) (*LargePayloadRecord, error) + GetLargePayload(ctx context.Context, in *GetLargePayloadRequest, opts ...grpc.CallOption) (*LargePayloadDetail, error) + DeleteLargePayload(ctx context.Context, in *DeleteLargePayloadRequest, opts ...grpc.CallOption) (*DeleteLargePayloadResponse, error) +} + +type loadTestServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewLoadTestServiceClient(cc grpc.ClientConnInterface) LoadTestServiceClient { + return &loadTestServiceClient{cc} +} + +func (c *loadTestServiceClient) CreateCustomer(ctx context.Context, in *CreateCustomerRequest, opts ...grpc.CallOption) (*Customer, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Customer) + err := c.cc.Invoke(ctx, LoadTestService_CreateCustomer_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *loadTestServiceClient) GetCustomerSummary(ctx context.Context, in *GetCustomerSummaryRequest, opts ...grpc.CallOption) (*CustomerSummary, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CustomerSummary) + err := c.cc.Invoke(ctx, LoadTestService_GetCustomerSummary_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *loadTestServiceClient) CreateProduct(ctx context.Context, in *CreateProductRequest, opts ...grpc.CallOption) (*Product, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Product) + err := c.cc.Invoke(ctx, LoadTestService_CreateProduct_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *loadTestServiceClient) CreateOrder(ctx context.Context, in *CreateOrderRequest, opts ...grpc.CallOption) (*Order, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Order) + err := c.cc.Invoke(ctx, LoadTestService_CreateOrder_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *loadTestServiceClient) GetOrder(ctx context.Context, in *GetOrderRequest, opts ...grpc.CallOption) (*Order, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Order) + err := c.cc.Invoke(ctx, LoadTestService_GetOrder_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *loadTestServiceClient) SearchOrders(ctx context.Context, in *SearchOrdersRequest, opts ...grpc.CallOption) (*SearchOrdersResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(SearchOrdersResponse) + err := c.cc.Invoke(ctx, LoadTestService_SearchOrders_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *loadTestServiceClient) TopProducts(ctx context.Context, in *TopProductsRequest, opts ...grpc.CallOption) (*TopProductsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(TopProductsResponse) + err := c.cc.Invoke(ctx, LoadTestService_TopProducts_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *loadTestServiceClient) CreateLargePayload(ctx context.Context, in *CreateLargePayloadRequest, opts ...grpc.CallOption) (*LargePayloadRecord, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(LargePayloadRecord) + err := c.cc.Invoke(ctx, LoadTestService_CreateLargePayload_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *loadTestServiceClient) GetLargePayload(ctx context.Context, in *GetLargePayloadRequest, opts ...grpc.CallOption) (*LargePayloadDetail, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(LargePayloadDetail) + err := c.cc.Invoke(ctx, LoadTestService_GetLargePayload_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *loadTestServiceClient) DeleteLargePayload(ctx context.Context, in *DeleteLargePayloadRequest, opts ...grpc.CallOption) (*DeleteLargePayloadResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeleteLargePayloadResponse) + err := c.cc.Invoke(ctx, LoadTestService_DeleteLargePayload_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// LoadTestServiceServer is the server API for LoadTestService service. +// All implementations must embed UnimplementedLoadTestServiceServer +// for forward compatibility. +// +// LoadTestService exposes CRUD operations for the gRPC memory load test. +type LoadTestServiceServer interface { + // Customer operations + CreateCustomer(context.Context, *CreateCustomerRequest) (*Customer, error) + GetCustomerSummary(context.Context, *GetCustomerSummaryRequest) (*CustomerSummary, error) + // Product operations + CreateProduct(context.Context, *CreateProductRequest) (*Product, error) + // Order operations + CreateOrder(context.Context, *CreateOrderRequest) (*Order, error) + GetOrder(context.Context, *GetOrderRequest) (*Order, error) + SearchOrders(context.Context, *SearchOrdersRequest) (*SearchOrdersResponse, error) + // Analytics + TopProducts(context.Context, *TopProductsRequest) (*TopProductsResponse, error) + // Large payload operations + CreateLargePayload(context.Context, *CreateLargePayloadRequest) (*LargePayloadRecord, error) + GetLargePayload(context.Context, *GetLargePayloadRequest) (*LargePayloadDetail, error) + DeleteLargePayload(context.Context, *DeleteLargePayloadRequest) (*DeleteLargePayloadResponse, error) + mustEmbedUnimplementedLoadTestServiceServer() +} + +// UnimplementedLoadTestServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedLoadTestServiceServer struct{} + +func (UnimplementedLoadTestServiceServer) CreateCustomer(context.Context, *CreateCustomerRequest) (*Customer, error) { + return nil, status.Error(codes.Unimplemented, "method CreateCustomer not implemented") +} +func (UnimplementedLoadTestServiceServer) GetCustomerSummary(context.Context, *GetCustomerSummaryRequest) (*CustomerSummary, error) { + return nil, status.Error(codes.Unimplemented, "method GetCustomerSummary not implemented") +} +func (UnimplementedLoadTestServiceServer) CreateProduct(context.Context, *CreateProductRequest) (*Product, error) { + return nil, status.Error(codes.Unimplemented, "method CreateProduct not implemented") +} +func (UnimplementedLoadTestServiceServer) CreateOrder(context.Context, *CreateOrderRequest) (*Order, error) { + return nil, status.Error(codes.Unimplemented, "method CreateOrder not implemented") +} +func (UnimplementedLoadTestServiceServer) GetOrder(context.Context, *GetOrderRequest) (*Order, error) { + return nil, status.Error(codes.Unimplemented, "method GetOrder not implemented") +} +func (UnimplementedLoadTestServiceServer) SearchOrders(context.Context, *SearchOrdersRequest) (*SearchOrdersResponse, error) { + return nil, status.Error(codes.Unimplemented, "method SearchOrders not implemented") +} +func (UnimplementedLoadTestServiceServer) TopProducts(context.Context, *TopProductsRequest) (*TopProductsResponse, error) { + return nil, status.Error(codes.Unimplemented, "method TopProducts not implemented") +} +func (UnimplementedLoadTestServiceServer) CreateLargePayload(context.Context, *CreateLargePayloadRequest) (*LargePayloadRecord, error) { + return nil, status.Error(codes.Unimplemented, "method CreateLargePayload not implemented") +} +func (UnimplementedLoadTestServiceServer) GetLargePayload(context.Context, *GetLargePayloadRequest) (*LargePayloadDetail, error) { + return nil, status.Error(codes.Unimplemented, "method GetLargePayload not implemented") +} +func (UnimplementedLoadTestServiceServer) DeleteLargePayload(context.Context, *DeleteLargePayloadRequest) (*DeleteLargePayloadResponse, error) { + return nil, status.Error(codes.Unimplemented, "method DeleteLargePayload not implemented") +} +func (UnimplementedLoadTestServiceServer) mustEmbedUnimplementedLoadTestServiceServer() {} +func (UnimplementedLoadTestServiceServer) testEmbeddedByValue() {} + +// UnsafeLoadTestServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to LoadTestServiceServer will +// result in compilation errors. +type UnsafeLoadTestServiceServer interface { + mustEmbedUnimplementedLoadTestServiceServer() +} + +func RegisterLoadTestServiceServer(s grpc.ServiceRegistrar, srv LoadTestServiceServer) { + // If the following call panics, it indicates UnimplementedLoadTestServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&LoadTestService_ServiceDesc, srv) +} + +func _LoadTestService_CreateCustomer_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateCustomerRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(LoadTestServiceServer).CreateCustomer(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: LoadTestService_CreateCustomer_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(LoadTestServiceServer).CreateCustomer(ctx, req.(*CreateCustomerRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _LoadTestService_GetCustomerSummary_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetCustomerSummaryRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(LoadTestServiceServer).GetCustomerSummary(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: LoadTestService_GetCustomerSummary_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(LoadTestServiceServer).GetCustomerSummary(ctx, req.(*GetCustomerSummaryRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _LoadTestService_CreateProduct_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateProductRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(LoadTestServiceServer).CreateProduct(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: LoadTestService_CreateProduct_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(LoadTestServiceServer).CreateProduct(ctx, req.(*CreateProductRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _LoadTestService_CreateOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateOrderRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(LoadTestServiceServer).CreateOrder(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: LoadTestService_CreateOrder_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(LoadTestServiceServer).CreateOrder(ctx, req.(*CreateOrderRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _LoadTestService_GetOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetOrderRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(LoadTestServiceServer).GetOrder(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: LoadTestService_GetOrder_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(LoadTestServiceServer).GetOrder(ctx, req.(*GetOrderRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _LoadTestService_SearchOrders_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SearchOrdersRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(LoadTestServiceServer).SearchOrders(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: LoadTestService_SearchOrders_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(LoadTestServiceServer).SearchOrders(ctx, req.(*SearchOrdersRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _LoadTestService_TopProducts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(TopProductsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(LoadTestServiceServer).TopProducts(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: LoadTestService_TopProducts_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(LoadTestServiceServer).TopProducts(ctx, req.(*TopProductsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _LoadTestService_CreateLargePayload_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateLargePayloadRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(LoadTestServiceServer).CreateLargePayload(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: LoadTestService_CreateLargePayload_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(LoadTestServiceServer).CreateLargePayload(ctx, req.(*CreateLargePayloadRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _LoadTestService_GetLargePayload_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetLargePayloadRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(LoadTestServiceServer).GetLargePayload(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: LoadTestService_GetLargePayload_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(LoadTestServiceServer).GetLargePayload(ctx, req.(*GetLargePayloadRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _LoadTestService_DeleteLargePayload_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteLargePayloadRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(LoadTestServiceServer).DeleteLargePayload(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: LoadTestService_DeleteLargePayload_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(LoadTestServiceServer).DeleteLargePayload(ctx, req.(*DeleteLargePayloadRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// LoadTestService_ServiceDesc is the grpc.ServiceDesc for LoadTestService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var LoadTestService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "loadtest.v1.LoadTestService", + HandlerType: (*LoadTestServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "CreateCustomer", + Handler: _LoadTestService_CreateCustomer_Handler, + }, + { + MethodName: "GetCustomerSummary", + Handler: _LoadTestService_GetCustomerSummary_Handler, + }, + { + MethodName: "CreateProduct", + Handler: _LoadTestService_CreateProduct_Handler, + }, + { + MethodName: "CreateOrder", + Handler: _LoadTestService_CreateOrder_Handler, + }, + { + MethodName: "GetOrder", + Handler: _LoadTestService_GetOrder_Handler, + }, + { + MethodName: "SearchOrders", + Handler: _LoadTestService_SearchOrders_Handler, + }, + { + MethodName: "TopProducts", + Handler: _LoadTestService_TopProducts_Handler, + }, + { + MethodName: "CreateLargePayload", + Handler: _LoadTestService_CreateLargePayload_Handler, + }, + { + MethodName: "GetLargePayload", + Handler: _LoadTestService_GetLargePayload_Handler, + }, + { + MethodName: "DeleteLargePayload", + Handler: _LoadTestService_DeleteLargePayload_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "api/proto/loadtest.proto", +} diff --git a/go-memory-load-grpc/cmd/api/main.go b/go-memory-load-grpc/cmd/api/main.go new file mode 100644 index 00000000..6c41aea5 --- /dev/null +++ b/go-memory-load-grpc/cmd/api/main.go @@ -0,0 +1,69 @@ +package main + +import ( + "context" + "fmt" + "log" + "net" + "net/http" + "os" + "os/signal" + "syscall" + + pb "loadtestgrpcapi/api/proto" + "loadtestgrpcapi/internal/config" + "loadtestgrpcapi/internal/grpcapi" + "loadtestgrpcapi/internal/store" + + "google.golang.org/grpc" + "google.golang.org/grpc/reflection" +) + +func main() { + cfg := config.Load() + st := store.New() + srv := grpcapi.New(st) + + // gRPC server + grpcLis, err := net.Listen("tcp", ":"+cfg.GRPCPort) + if err != nil { + log.Fatalf("grpc listen :%s: %v", cfg.GRPCPort, err) + } + grpcServer := grpc.NewServer() + pb.RegisterLoadTestServiceServer(grpcServer, srv) + reflection.Register(grpcServer) + + // HTTP server (health-check only) + mux := http.NewServeMux() + mux.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintln(w, `{"status":"ok"}`) + }) + httpServer := &http.Server{ + Addr: ":" + cfg.HTTPPort, + Handler: mux, + } + + ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) + defer stop() + + go func() { + log.Printf("gRPC server listening on :%s", cfg.GRPCPort) + if err := grpcServer.Serve(grpcLis); err != nil { + log.Printf("gRPC server stopped: %v", err) + } + }() + + go func() { + log.Printf("HTTP server listening on :%s", cfg.HTTPPort) + if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Printf("HTTP server stopped: %v", err) + } + }() + + <-ctx.Done() + log.Println("shutting down…") + grpcServer.GracefulStop() + _ = httpServer.Shutdown(context.Background()) +} diff --git a/go-memory-load-grpc/docker-compose.yml b/go-memory-load-grpc/docker-compose.yml new file mode 100644 index 00000000..38454a73 --- /dev/null +++ b/go-memory-load-grpc/docker-compose.yml @@ -0,0 +1,24 @@ +services: + api: + build: + context: . + container_name: load-test-grpc-api + environment: + APP_HTTP_PORT: "8080" + APP_GRPC_PORT: "50051" + ports: + - "8080:8080" + - "50051:50051" + + k6: + image: grafana/k6:0.49.0 + profiles: ["loadtest"] + environment: + GRPC_ADDR: api:50051 + volumes: + - ./loadtest:/scripts:ro + - ./api/proto:/proto:ro + depends_on: + api: + condition: service_started + entrypoint: ["k6"] diff --git a/go-memory-load-grpc/go.mod b/go-memory-load-grpc/go.mod new file mode 100644 index 00000000..cd0740b1 --- /dev/null +++ b/go-memory-load-grpc/go.mod @@ -0,0 +1,15 @@ +module loadtestgrpcapi + +go 1.26 + +require ( + google.golang.org/grpc v1.73.0 + google.golang.org/protobuf v1.36.6 +) + +require ( + golang.org/x/net v0.38.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/text v0.23.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect +) diff --git a/go-memory-load-grpc/go.sum b/go-memory-load-grpc/go.sum new file mode 100644 index 00000000..c5308a51 --- /dev/null +++ b/go-memory-load-grpc/go.sum @@ -0,0 +1,34 @@ +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= +go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= +go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= +go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= +go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= +go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= +go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= +go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= +go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= +google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= diff --git a/go-memory-load-grpc/internal/config/config.go b/go-memory-load-grpc/internal/config/config.go new file mode 100644 index 00000000..55e101ee --- /dev/null +++ b/go-memory-load-grpc/internal/config/config.go @@ -0,0 +1,25 @@ +package config + +import "os" + +// Config holds runtime configuration for the gRPC load-test app. +type Config struct { + HTTPPort string + GRPCPort string +} + +// Load reads configuration from environment variables. +func Load() *Config { + httpPort := os.Getenv("APP_HTTP_PORT") + if httpPort == "" { + httpPort = "8080" + } + grpcPort := os.Getenv("APP_GRPC_PORT") + if grpcPort == "" { + grpcPort = "50051" + } + return &Config{ + HTTPPort: httpPort, + GRPCPort: grpcPort, + } +} diff --git a/go-memory-load-grpc/internal/grpcapi/server.go b/go-memory-load-grpc/internal/grpcapi/server.go new file mode 100644 index 00000000..3af56970 --- /dev/null +++ b/go-memory-load-grpc/internal/grpcapi/server.go @@ -0,0 +1,256 @@ +package grpcapi + +import ( + "context" + "errors" + "time" + + pb "loadtestgrpcapi/api/proto" + "loadtestgrpcapi/internal/store" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// Server implements pb.LoadTestServiceServer backed by an in-memory store. +type Server struct { + pb.UnimplementedLoadTestServiceServer + store *store.Store +} + +// New creates a new gRPC server with the given store. +func New(s *store.Store) *Server { + return &Server{store: s} +} + +// ─── Customer ──────────────────────────────────────────────────────────────── + +func (s *Server) CreateCustomer(_ context.Context, req *pb.CreateCustomerRequest) (*pb.Customer, error) { + c, err := s.store.CreateCustomer(req.Email, req.FullName, req.Segment) + if err != nil { + return nil, status.Errorf(codes.Internal, "create customer: %v", err) + } + return customerPB(c), nil +} + +func (s *Server) GetCustomerSummary(_ context.Context, req *pb.GetCustomerSummaryRequest) (*pb.CustomerSummary, error) { + sum, err := s.store.GetCustomerSummary(req.CustomerId) + if err != nil { + if errors.Is(err, store.ErrNotFound) { + return nil, status.Errorf(codes.NotFound, "customer %s not found", req.CustomerId) + } + return nil, status.Errorf(codes.Internal, "get customer summary: %v", err) + } + lastOrder := "" + if !sum.LastOrderAt.IsZero() { + lastOrder = sum.LastOrderAt.Format(time.RFC3339) + } + return &pb.CustomerSummary{ + Customer: customerPB(sum.Customer), + OrdersCount: sum.OrdersCount, + LifetimeValueCents: sum.LifetimeValueCents, + AverageOrderValueCents: sum.AverageOrderValueCents, + FavoriteCategory: sum.FavoriteCategory, + LastOrderAt: lastOrder, + }, nil +} + +// ─── Product ───────────────────────────────────────────────────────────────── + +func (s *Server) CreateProduct(_ context.Context, req *pb.CreateProductRequest) (*pb.Product, error) { + p, err := s.store.CreateProduct(req.Sku, req.Name, req.Category, req.PriceCents, req.InventoryCount) + if err != nil { + return nil, status.Errorf(codes.Internal, "create product: %v", err) + } + return productPB(p), nil +} + +// ─── Order ─────────────────────────────────────────────────────────────────── + +func (s *Server) CreateOrder(_ context.Context, req *pb.CreateOrderRequest) (*pb.Order, error) { + inputs := make([]store.OrderItemInput, len(req.Items)) + for i, it := range req.Items { + inputs[i] = store.OrderItemInput{ProductID: it.ProductId, Quantity: it.Quantity} + } + o, err := s.store.CreateOrder(req.CustomerId, req.Status, inputs) + if err != nil { + if errors.Is(err, store.ErrNotFound) { + return nil, status.Errorf(codes.NotFound, "resource not found: %v", err) + } + if errors.Is(err, store.ErrOutOfStock) { + return nil, status.Errorf(codes.FailedPrecondition, "out of stock: %v", err) + } + return nil, status.Errorf(codes.Internal, "create order: %v", err) + } + order, cust, err2 := s.store.GetOrder(o.ID) + if err2 != nil { + return nil, status.Errorf(codes.Internal, "get order after create: %v", err2) + } + return orderPB(order, cust), nil +} + +func (s *Server) GetOrder(_ context.Context, req *pb.GetOrderRequest) (*pb.Order, error) { + o, c, err := s.store.GetOrder(req.OrderId) + if err != nil { + if errors.Is(err, store.ErrNotFound) { + return nil, status.Errorf(codes.NotFound, "order %s not found", req.OrderId) + } + return nil, status.Errorf(codes.Internal, "get order: %v", err) + } + return orderPB(o, c), nil +} + +func (s *Server) SearchOrders(_ context.Context, req *pb.SearchOrdersRequest) (*pb.SearchOrdersResponse, error) { + var from, through time.Time + if req.CreatedFrom != "" { + if t, err := time.Parse(time.RFC3339, req.CreatedFrom); err == nil { + from = t + } + } + if req.CreatedThrough != "" { + if t, err := time.Parse(time.RFC3339, req.CreatedThrough); err == nil { + through = t + } + } + results, err := s.store.SearchOrders(req.Status, req.CustomerId, req.MinTotalCents, from, through, req.Limit, req.Offset) + if err != nil { + return nil, status.Errorf(codes.Internal, "search orders: %v", err) + } + pbResults := make([]*pb.OrderSearchResult, len(results)) + for i, r := range results { + pbResults[i] = &pb.OrderSearchResult{ + OrderId: r.OrderID, + CustomerId: r.CustomerID, + CustomerName: r.CustomerName, + Status: r.Status, + TotalCents: r.TotalCents, + CreatedAt: r.CreatedAt.Format(time.RFC3339), + TotalItems: r.TotalItems, + DistinctProducts: r.DistinctProducts, + } + } + return &pb.SearchOrdersResponse{Results: pbResults}, nil +} + +// ─── Analytics ─────────────────────────────────────────────────────────────── + +func (s *Server) TopProducts(_ context.Context, req *pb.TopProductsRequest) (*pb.TopProductsResponse, error) { + products, err := s.store.TopProducts(req.Days, req.Limit) + if err != nil { + return nil, status.Errorf(codes.Internal, "top products: %v", err) + } + pbProducts := make([]*pb.TopProduct, len(products)) + for i, p := range products { + pbProducts[i] = &pb.TopProduct{ + ProductId: p.ProductID, + Sku: p.SKU, + Name: p.Name, + Category: p.Category, + UnitsSold: p.UnitsSold, + RevenueCents: p.RevenueCents, + OrdersCount: p.OrdersCount, + RevenueRank: p.RevenueRank, + } + } + return &pb.TopProductsResponse{Products: pbProducts}, nil +} + +// ─── Large payloads ────────────────────────────────────────────────────────── + +func (s *Server) CreateLargePayload(_ context.Context, req *pb.CreateLargePayloadRequest) (*pb.LargePayloadRecord, error) { + lp, err := s.store.CreateLargePayload(req.Name, req.ContentType, req.Payload) + if err != nil { + return nil, status.Errorf(codes.Internal, "create large payload: %v", err) + } + return largePayloadRecordPB(lp), nil +} + +func (s *Server) GetLargePayload(_ context.Context, req *pb.GetLargePayloadRequest) (*pb.LargePayloadDetail, error) { + lp, err := s.store.GetLargePayload(req.PayloadId) + if err != nil { + if errors.Is(err, store.ErrNotFound) { + return nil, status.Errorf(codes.NotFound, "payload %s not found", req.PayloadId) + } + return nil, status.Errorf(codes.Internal, "get large payload: %v", err) + } + return &pb.LargePayloadDetail{ + Record: largePayloadRecordPB(lp), + Payload: lp.Payload, + }, nil +} + +func (s *Server) DeleteLargePayload(_ context.Context, req *pb.DeleteLargePayloadRequest) (*pb.DeleteLargePayloadResponse, error) { + lp, err := s.store.DeleteLargePayload(req.PayloadId) + if err != nil { + if errors.Is(err, store.ErrNotFound) { + return nil, status.Errorf(codes.NotFound, "payload %s not found", req.PayloadId) + } + return nil, status.Errorf(codes.Internal, "delete large payload: %v", err) + } + return &pb.DeleteLargePayloadResponse{ + Deleted: true, + Record: largePayloadRecordPB(lp), + }, nil +} + +// ─── Helpers ───────────────────────────────────────────────────────────────── + +func customerPB(c *store.Customer) *pb.Customer { + return &pb.Customer{ + Id: c.ID, + Email: c.Email, + FullName: c.FullName, + Segment: c.Segment, + CreatedAt: c.CreatedAt.Format(time.RFC3339), + } +} + +func productPB(p *store.Product) *pb.Product { + return &pb.Product{ + Id: p.ID, + Sku: p.SKU, + Name: p.Name, + Category: p.Category, + PriceCents: p.PriceCents, + InventoryCount: p.InventoryCount, + CreatedAt: p.CreatedAt.Format(time.RFC3339), + } +} + +func orderPB(o *store.Order, c *store.Customer) *pb.Order { + items := make([]*pb.OrderItem, len(o.Items)) + for i, it := range o.Items { + items[i] = &pb.OrderItem{ + ProductId: it.ProductID, + Sku: it.SKU, + Name: it.Name, + Category: it.Category, + Quantity: it.Quantity, + UnitPriceCents: it.UnitPriceCents, + LineTotalCents: it.LineTotalCents, + } + } + var custPB *pb.Customer + if c != nil { + custPB = customerPB(c) + } + return &pb.Order{ + Id: o.ID, + Customer: custPB, + Status: o.Status, + TotalCents: o.TotalCents, + CreatedAt: o.CreatedAt.Format(time.RFC3339), + Items: items, + } +} + +func largePayloadRecordPB(lp *store.LargePayload) *pb.LargePayloadRecord { + return &pb.LargePayloadRecord{ + Id: lp.ID, + Name: lp.Name, + ContentType: lp.ContentType, + PayloadSizeBytes: lp.PayloadSizeBytes, + Sha256: lp.SHA256, + CreatedAt: lp.CreatedAt.Format(time.RFC3339), + } +} diff --git a/go-memory-load-grpc/internal/store/store.go b/go-memory-load-grpc/internal/store/store.go new file mode 100644 index 00000000..68f63412 --- /dev/null +++ b/go-memory-load-grpc/internal/store/store.go @@ -0,0 +1,455 @@ +package store + +import ( + "crypto/sha256" + "encoding/hex" + "errors" + "fmt" + "sort" + "strings" + "sync" + "time" +) + +// ErrNotFound is returned when a requested resource does not exist. +var ErrNotFound = errors.New("not found") + +// ErrOutOfStock is returned when a product has insufficient inventory. +var ErrOutOfStock = errors.New("out of stock") + +// ─── Domain types ──────────────────────────────────────────────────────────── + +type Customer struct { + ID string + Email string + FullName string + Segment string + CreatedAt time.Time +} + +type Product struct { + ID string + SKU string + Name string + Category string + PriceCents int32 + InventoryCount int32 + CreatedAt time.Time +} + +type OrderItem struct { + ProductID string + SKU string + Name string + Category string + Quantity int32 + UnitPriceCents int32 + LineTotalCents int32 +} + +type Order struct { + ID string + CustomerID string + Status string + TotalCents int32 + CreatedAt time.Time + Items []OrderItem +} + +type LargePayload struct { + ID string + Name string + ContentType string + Payload string + SHA256 string + PayloadSizeBytes int64 + CreatedAt time.Time +} + +// ─── Aggregate types ───────────────────────────────────────────────────────── + +type CustomerSummary struct { + Customer *Customer + OrdersCount int32 + LifetimeValueCents int64 + AverageOrderValueCents int64 + FavoriteCategory string + LastOrderAt time.Time +} + +type OrderItemInput struct { + ProductID string + Quantity int32 +} + +type OrderSearchResult struct { + OrderID string + CustomerID string + CustomerName string + Status string + TotalCents int32 + CreatedAt time.Time + TotalItems int32 + DistinctProducts int32 +} + +type TopProduct struct { + ProductID string + SKU string + Name string + Category string + UnitsSold int32 + RevenueCents int64 + OrdersCount int32 + RevenueRank int32 +} + +// ─── Store ─────────────────────────────────────────────────────────────────── + +type Store struct { + mu sync.RWMutex + customers map[string]*Customer + products map[string]*Product + orders map[string]*Order + largePayloads map[string]*LargePayload +} + +func New() *Store { + return &Store{ + customers: make(map[string]*Customer), + products: make(map[string]*Product), + orders: make(map[string]*Order), + largePayloads: make(map[string]*LargePayload), + } +} + +// contentID derives a deterministic 24-hex-char ID from the supplied key +// parts, so that the same inputs always produce the same ID across keploy +// record and replay sessions. +func contentID(parts ...string) string { + h := sha256.Sum256([]byte(strings.Join(parts, "\x00"))) + return hex.EncodeToString(h[:])[:24] +} + +// contentTime derives a deterministic creation timestamp from the supplied key +// parts using the same SHA-256 approach, producing a stable RFC3339 value +// within a 2-year window starting 2020-01-01. +func contentTime(parts ...string) time.Time { + h := sha256.Sum256([]byte(strings.Join(parts, "\x00"))) + const base = int64(1577836800) // 2020-01-01T00:00:00Z + const window = int64(2 * 365 * 24 * 3600) + raw := int64(h[0])<<56 | int64(h[1])<<48 | int64(h[2])<<40 | int64(h[3])<<32 | + int64(h[4])<<24 | int64(h[5])<<16 | int64(h[6])<<8 | int64(h[7]) + return time.Unix(base+(raw&0x7FFFFFFFFFFFFFFF)%window, 0).UTC() +} + +// orderFingerprint builds a canonical, sorted string representation of order +// items so that the order ID is independent of input slice ordering. +func orderFingerprint(inputs []OrderItemInput) string { + sorted := make([]OrderItemInput, len(inputs)) + copy(sorted, inputs) + sort.Slice(sorted, func(i, j int) bool { + return sorted[i].ProductID < sorted[j].ProductID + }) + parts := make([]string, len(sorted)) + for i, inp := range sorted { + parts[i] = fmt.Sprintf("%s:%d", inp.ProductID, inp.Quantity) + } + return strings.Join(parts, ",") +} + +// ─── Customer operations ───────────────────────────────────────────────────── + +func (s *Store) CreateCustomer(email, fullName, segment string) (*Customer, error) { + s.mu.Lock() + defer s.mu.Unlock() + c := &Customer{ + ID: contentID(email), + Email: email, + FullName: fullName, + Segment: segment, + CreatedAt: contentTime(email), + } + s.customers[c.ID] = c + return c, nil +} + +func (s *Store) GetCustomerSummary(customerID string) (*CustomerSummary, error) { + s.mu.RLock() + defer s.mu.RUnlock() + + c, ok := s.customers[customerID] + if !ok { + return nil, ErrNotFound + } + + var sum CustomerSummary + sum.Customer = c + catCount := make(map[string]int32) + + for _, o := range s.orders { + if o.CustomerID != customerID { + continue + } + sum.OrdersCount++ + sum.LifetimeValueCents += int64(o.TotalCents) + if o.CreatedAt.After(sum.LastOrderAt) { + sum.LastOrderAt = o.CreatedAt + } + for _, it := range o.Items { + catCount[it.Category] += it.Quantity + } + } + + if sum.OrdersCount > 0 { + sum.AverageOrderValueCents = sum.LifetimeValueCents / int64(sum.OrdersCount) + } + + var maxCat string + var maxQty int32 + for cat, qty := range catCount { + if qty > maxQty { + maxQty = qty + maxCat = cat + } + } + sum.FavoriteCategory = maxCat + return &sum, nil +} + +// ─── Product operations ────────────────────────────────────────────────────── + +func (s *Store) CreateProduct(sku, name, category string, priceCents, inventoryCount int32) (*Product, error) { + s.mu.Lock() + defer s.mu.Unlock() + p := &Product{ + ID: contentID(sku), + SKU: sku, + Name: name, + Category: category, + PriceCents: priceCents, + InventoryCount: inventoryCount, + CreatedAt: contentTime(sku), + } + s.products[p.ID] = p + return p, nil +} + +// ─── Order operations ──────────────────────────────────────────────────────── + +func (s *Store) CreateOrder(customerID, orderStatus string, inputs []OrderItemInput) (*Order, error) { + s.mu.Lock() + defer s.mu.Unlock() + + if _, ok := s.customers[customerID]; !ok { + return nil, fmt.Errorf("customer %s: %w", customerID, ErrNotFound) + } + + var items []OrderItem + var totalCents int32 + for _, inp := range inputs { + p, ok := s.products[inp.ProductID] + if !ok { + return nil, fmt.Errorf("product %s: %w", inp.ProductID, ErrNotFound) + } + if p.InventoryCount < inp.Quantity { + return nil, fmt.Errorf("product %s: %w", inp.ProductID, ErrOutOfStock) + } + p.InventoryCount -= inp.Quantity + line := inp.Quantity * p.PriceCents + items = append(items, OrderItem{ + ProductID: p.ID, + SKU: p.SKU, + Name: p.Name, + Category: p.Category, + Quantity: inp.Quantity, + UnitPriceCents: p.PriceCents, + LineTotalCents: line, + }) + totalCents += line + } + + if orderStatus == "" { + orderStatus = "pending" + } + fingerprint := orderFingerprint(inputs) + orderID := contentID(customerID, fingerprint, orderStatus) + // Idempotent: if an identical order already exists, return it without + // re-decrementing inventory (handles duplicate keploy replay calls). + if existing, ok := s.orders[orderID]; ok { + return existing, nil + } + o := &Order{ + ID: orderID, + CustomerID: customerID, + Status: orderStatus, + TotalCents: totalCents, + CreatedAt: contentTime(customerID, fingerprint, orderStatus), + Items: items, + } + s.orders[o.ID] = o + return o, nil +} + +func (s *Store) GetOrder(orderID string) (*Order, *Customer, error) { + s.mu.RLock() + defer s.mu.RUnlock() + o, ok := s.orders[orderID] + if !ok { + return nil, nil, ErrNotFound + } + return o, s.customers[o.CustomerID], nil +} + +func (s *Store) SearchOrders( + statusFilter, customerID string, + minTotalCents int64, + createdFrom, createdThrough time.Time, + limit, offset int32, +) ([]OrderSearchResult, error) { + s.mu.RLock() + defer s.mu.RUnlock() + + var results []OrderSearchResult + for _, o := range s.orders { + if statusFilter != "" && o.Status != statusFilter { + continue + } + if customerID != "" && o.CustomerID != customerID { + continue + } + if minTotalCents > 0 && int64(o.TotalCents) < minTotalCents { + continue + } + if !createdFrom.IsZero() && o.CreatedAt.Before(createdFrom) { + continue + } + if !createdThrough.IsZero() && o.CreatedAt.After(createdThrough) { + continue + } + cust := s.customers[o.CustomerID] + custName := "" + if cust != nil { + custName = cust.FullName + } + var totalItems int32 + seen := make(map[string]bool) + for _, it := range o.Items { + totalItems += it.Quantity + seen[it.ProductID] = true + } + results = append(results, OrderSearchResult{ + OrderID: o.ID, + CustomerID: o.CustomerID, + CustomerName: custName, + Status: o.Status, + TotalCents: o.TotalCents, + CreatedAt: o.CreatedAt, + TotalItems: totalItems, + DistinctProducts: int32(len(seen)), + }) + } + + sort.Slice(results, func(i, j int) bool { + return results[i].CreatedAt.After(results[j].CreatedAt) + }) + + if int(offset) >= len(results) { + return []OrderSearchResult{}, nil + } + results = results[offset:] + if limit > 0 && int(limit) < len(results) { + results = results[:limit] + } + return results, nil +} + +// ─── Analytics ─────────────────────────────────────────────────────────────── + +func (s *Store) TopProducts(days, limit int32) ([]TopProduct, error) { + s.mu.RLock() + defer s.mu.RUnlock() + + _ = days // days filter intentionally unused: order CreatedAt is derived + // from content hash (not wall-clock time), so a time.Now()-based cutoff + // would exclude all orders during keploy replay. Using all-time data keeps + // mock matching deterministic across record and replay sessions. + agg := make(map[string]*TopProduct) + for _, o := range s.orders { + for _, it := range o.Items { + tp, ok := agg[it.ProductID] + if !ok { + sku, name, cat := "", "", "" + if p := s.products[it.ProductID]; p != nil { + sku, name, cat = p.SKU, p.Name, p.Category + } + agg[it.ProductID] = &TopProduct{ + ProductID: it.ProductID, + SKU: sku, + Name: name, + Category: cat, + } + tp = agg[it.ProductID] + } + tp.UnitsSold += it.Quantity + tp.RevenueCents += int64(it.LineTotalCents) + tp.OrdersCount++ + } + } + + products := make([]TopProduct, 0, len(agg)) + for _, tp := range agg { + products = append(products, *tp) + } + sort.Slice(products, func(i, j int) bool { + return products[i].RevenueCents > products[j].RevenueCents + }) + for i := range products { + products[i].RevenueRank = int32(i + 1) + } + if limit > 0 && int(limit) < len(products) { + products = products[:limit] + } + return products, nil +} + +// ─── Large payload operations ──────────────────────────────────────────────── + +func (s *Store) CreateLargePayload(name, contentType, payload string) (*LargePayload, error) { + s.mu.Lock() + defer s.mu.Unlock() + h := sha256.Sum256([]byte(payload)) + sha256Hex := hex.EncodeToString(h[:]) + lp := &LargePayload{ + ID: contentID(name, contentType, sha256Hex), + Name: name, + ContentType: contentType, + Payload: payload, + SHA256: sha256Hex, + PayloadSizeBytes: int64(len(payload)), + CreatedAt: contentTime(name, contentType, sha256Hex), + } + s.largePayloads[lp.ID] = lp + return lp, nil +} + +func (s *Store) GetLargePayload(payloadID string) (*LargePayload, error) { + s.mu.RLock() + defer s.mu.RUnlock() + lp, ok := s.largePayloads[payloadID] + if !ok { + return nil, ErrNotFound + } + return lp, nil +} + +func (s *Store) DeleteLargePayload(payloadID string) (*LargePayload, error) { + s.mu.Lock() + defer s.mu.Unlock() + lp, ok := s.largePayloads[payloadID] + if !ok { + return nil, ErrNotFound + } + delete(s.largePayloads, payloadID) + return lp, nil +} diff --git a/go-memory-load-grpc/keploy.yml b/go-memory-load-grpc/keploy.yml new file mode 100755 index 00000000..9f82c9fa --- /dev/null +++ b/go-memory-load-grpc/keploy.yml @@ -0,0 +1,110 @@ +# Generated by Keploy (3-dev) +path: "" +appName: go-memory-load-grpc +appId: 0 +command: docker compose up +templatize: + testSets: [] +port: 0 +e2e: false +dnsPort: 26789 +proxyPort: 16789 +incomingProxyPort: 36789 +debug: false +disableTele: false +disableANSI: false +jsonOutput: false +containerName: load-test-grpc-api +networkName: "" +buildDelay: 30 +test: + selectedTests: {} + globalNoise: + global: {} + test-sets: {} + replaceWith: + global: + url: {} + port: {} + test-sets: {} + delay: 5 + host: localhost + port: 0 + grpcPort: 0 + ssePort: 0 + protocol: + grpc: + port: 0 + http: + port: 0 + sse: + port: 0 + apiTimeout: 5 + skipCoverage: false + coverageReportPath: "" + ignoreOrdering: true + mongoPassword: default@123 + language: "" + removeUnusedMocks: false + preserveFailedMocks: false + fallBackOnMiss: false + jacocoAgentPath: "" + basePath: "" + mocking: true + ignoredTests: {} + disableLineCoverage: false + disableMockUpload: true + useLocalMock: false + updateTemplate: false + mustPass: false + maxFailAttempts: 5 + maxFlakyChecks: 1 + protoFile: "" + protoDir: "" + protoInclude: [] + compareAll: false + schemaMatch: false + updateTestMapping: false + disableAutoHeaderNoise: false + strictMockWindow: false +record: + filters: [] + basePath: "" + recordTimer: 0s + metadata: "" + sync: false + enableSampling: 0 + memoryLimit: 0 + globalPassthrough: false + tlsPrivateKeyPath: "" +report: + selectedTestSets: {} + showFullBody: false + reportPath: "" + summary: false + testCaseIDs: [] + format: "" +disableMapping: true +retryPassing: false +configPath: "" +bypassRules: [] +generateGithubActions: false +keployContainer: keploy-v3 +keployNetwork: keploy-network +cmdType: native +contract: + services: [] + tests: [] + path: "" + download: false + generate: false + driven: consumer + mappings: + servicesMapping: {} + self: s1 +inCi: false +serverPort: 0 +mockDownload: + registryIds: [] + +# Visit [https://keploy.io/docs/running-keploy/configuration-file/] to learn about using keploy through configration file. diff --git a/go-memory-load-grpc/loadtest/scenario.js b/go-memory-load-grpc/loadtest/scenario.js new file mode 100644 index 00000000..ccf1733c --- /dev/null +++ b/go-memory-load-grpc/loadtest/scenario.js @@ -0,0 +1,178 @@ +import grpc from 'k6/net/grpc'; +import { check, sleep } from 'k6'; +import { Counter } from 'k6/metrics'; + +const client = new grpc.Client(); +client.load(['/proto'], 'loadtest.proto'); + +const TARGET_ADDR = __ENV.GRPC_ADDR || 'load-test-grpc-api:50051'; + +const grpcReqFailed = new Counter('grpc_req_failed'); + +export const options = { + scenarios: { + constant_load: { + executor: 'constant-vus', + vus: 20, + duration: '120s', + }, + }, + thresholds: { + grpc_req_duration: [ + { threshold: `p(95)<${__ENV.THRESHOLD_HTTP_P95 || 120000}`, abortOnFail: false }, + { threshold: `avg<${__ENV.THRESHOLD_HTTP_AVG || 60000}`, abortOnFail: false }, + ], + grpc_req_failed: [ + { threshold: `rate<${__ENV.CI_MAX_HTTP_FAILURE_RATE || 0.40}`, abortOnFail: false }, + ], + }, +}; + +// ─── setup ─────────────────────────────────────────────────────────────────── +// Seed reference data (products + customers) that VUs will share. + +export function setup() { + client.connect(TARGET_ADDR, { plaintext: true }); + + const categories = ['electronics', 'clothing', 'books', 'home', 'sports']; + const segments = ['startup', 'enterprise', 'smb', 'consumer']; + + const products = []; + for (let i = 0; i < 10; i++) { + const res = client.invoke('loadtest.v1.LoadTestService/CreateProduct', { + sku: `SEED-${i}-${Date.now()}`, + name: `Seed Product ${i}`, + category: categories[i % categories.length], + price_cents: 999 + i * 100, + inventory_count: 100000, + }); + if (res && res.status === grpc.StatusOK) { + products.push(res.message.id); + } + } + + const customers = []; + for (let i = 0; i < 5; i++) { + const res = client.invoke('loadtest.v1.LoadTestService/CreateCustomer', { + email: `seed-${i}-${Date.now()}@example.com`, + full_name: `Seed Customer ${i}`, + segment: segments[i % segments.length], + }); + if (res && res.status === grpc.StatusOK) { + customers.push(res.message.id); + } + } + + client.close(); + return { products, customers }; +} + +// ─── default ───────────────────────────────────────────────────────────────── + +export default function (data) { + client.connect(TARGET_ADDR, { plaintext: true }); + + const customerID = data.customers[__VU % Math.max(data.customers.length, 1)] || ''; + const productID = data.products[__VU % Math.max(data.products.length, 1)] || ''; + + // 1. Create customer + { + const res = client.invoke('loadtest.v1.LoadTestService/CreateCustomer', { + email: `vu${__VU}-${Date.now()}@example.com`, + full_name: `VU User ${__VU}`, + segment: 'startup', + }); + const ok = check(res, { 'create customer ok': (r) => r && r.status === grpc.StatusOK }); + if (!ok) grpcReqFailed.add(1); + } + + // 2. Create product + { + const res = client.invoke('loadtest.v1.LoadTestService/CreateProduct', { + sku: `VU-${__VU}-${Date.now()}`, + name: `VU Product ${__VU}`, + category: 'electronics', + price_cents: 1499, + inventory_count: 99999, + }); + const ok = check(res, { 'create product ok': (r) => r && r.status === grpc.StatusOK }); + if (!ok) grpcReqFailed.add(1); + } + + // 3. Create order (requires seeded customer + product) + let orderID = ''; + if (customerID && productID) { + const res = client.invoke('loadtest.v1.LoadTestService/CreateOrder', { + customer_id: customerID, + status: 'pending', + items: [{ product_id: productID, quantity: 1 }], + }); + const ok = check(res, { 'create order ok': (r) => r && r.status === grpc.StatusOK }); + if (!ok) { + grpcReqFailed.add(1); + } else if (res.message) { + orderID = res.message.id; + } + } + + // 4. Get order + if (orderID) { + const res = client.invoke('loadtest.v1.LoadTestService/GetOrder', { order_id: orderID }); + const ok = check(res, { 'get order ok': (r) => r && r.status === grpc.StatusOK }); + if (!ok) grpcReqFailed.add(1); + } + + // 5. Customer summary + if (customerID) { + const res = client.invoke('loadtest.v1.LoadTestService/GetCustomerSummary', { + customer_id: customerID, + }); + const ok = check(res, { 'customer summary ok': (r) => r && r.status === grpc.StatusOK }); + if (!ok) grpcReqFailed.add(1); + } + + // 6. Search orders + { + const res = client.invoke('loadtest.v1.LoadTestService/SearchOrders', { + status: 'pending', + limit: 10, + offset: 0, + }); + const ok = check(res, { 'search orders ok': (r) => r && r.status === grpc.StatusOK }); + if (!ok) grpcReqFailed.add(1); + } + + // 7. Top products + { + const res = client.invoke('loadtest.v1.LoadTestService/TopProducts', { days: 30, limit: 5 }); + const ok = check(res, { 'top products ok': (r) => r && r.status === grpc.StatusOK }); + if (!ok) grpcReqFailed.add(1); + } + + // 8. Large payload round-trip + { + const payload = 'x'.repeat(1024); + const createRes = client.invoke('loadtest.v1.LoadTestService/CreateLargePayload', { + name: `payload-${__VU}-${Date.now()}`, + content_type: 'text/plain', + payload: payload, + }); + const createOk = check(createRes, { 'create payload ok': (r) => r && r.status === grpc.StatusOK }); + if (!createOk) { + grpcReqFailed.add(1); + } else { + const pid = createRes.message.id; + + const getRes = client.invoke('loadtest.v1.LoadTestService/GetLargePayload', { payload_id: pid }); + const getOk = check(getRes, { 'get payload ok': (r) => r && r.status === grpc.StatusOK }); + if (!getOk) grpcReqFailed.add(1); + + const delRes = client.invoke('loadtest.v1.LoadTestService/DeleteLargePayload', { payload_id: pid }); + const delOk = check(delRes, { 'delete payload ok': (r) => r && r.status === grpc.StatusOK }); + if (!delOk) grpcReqFailed.add(1); + } + } + + client.close(); + sleep(0.5); +}