From a5ff416fa277f396ac442008923333abcba79e70 Mon Sep 17 00:00:00 2001 From: Orne Brocaar Date: Wed, 17 Apr 2024 14:34:11 +0100 Subject: [PATCH] Implement storing + display duty-cycle stats. Note that these stats are only available for Concentratord based gateways as these metrics must be aggregated by the gateway. --- api/go/api/gateway.pb.go | 408 +++++++++++++----- api/go/api/gateway_grpc.pb.go | 41 ++ api/go/common/common.pb.go | 49 ++- api/proto/api/gateway.proto | 27 ++ api/proto/common/common.proto | 3 + api/rust/proto/chirpstack/api/gateway.proto | 27 ++ api/rust/proto/chirpstack/common/common.proto | 3 + chirpstack/src/api/gateway.rs | 305 ++++++++++++- chirpstack/src/api/helpers.rs | 2 + chirpstack/src/storage/metrics.rs | 107 ++++- chirpstack/src/uplink/data.rs | 21 +- chirpstack/src/uplink/join.rs | 2 + chirpstack/src/uplink/join_sns.rs | 2 + chirpstack/src/uplink/stats.rs | 70 +++ ui/package.json | 1 + ui/src/components/MetricBar.tsx | 25 +- ui/src/components/MetricChart.tsx | 56 ++- ui/src/index.css | 2 +- ui/src/stores/GatewayStore.ts | 16 + ui/src/views/gateways/GatewayDashboard.tsx | 35 +- ui/tsconfig.json | 3 +- ui/types/google-palette/index.d.ts | 1 + ui/yarn.lock | 7 +- 23 files changed, 1012 insertions(+), 201 deletions(-) create mode 100644 ui/types/google-palette/index.d.ts diff --git a/api/go/api/gateway.pb.go b/api/go/api/gateway.pb.go index 1c2625a1..557fd9bc 100644 --- a/api/go/api/gateway.pb.go +++ b/api/go/api/gateway.pb.go @@ -1029,6 +1029,129 @@ func (x *GetGatewayMetricsResponse) GetTxPacketsPerStatus() *common.Metric { return nil } +type GetGatewayDutyCycleMetricsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Gateway ID (EUI64). + GatewayId string `protobuf:"bytes,1,opt,name=gateway_id,json=gatewayId,proto3" json:"gateway_id,omitempty"` + // Interval start timestamp. + Start *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=start,proto3" json:"start,omitempty"` + // Interval end timestamp. + End *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=end,proto3" json:"end,omitempty"` +} + +func (x *GetGatewayDutyCycleMetricsRequest) Reset() { + *x = GetGatewayDutyCycleMetricsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_api_gateway_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetGatewayDutyCycleMetricsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetGatewayDutyCycleMetricsRequest) ProtoMessage() {} + +func (x *GetGatewayDutyCycleMetricsRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_gateway_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetGatewayDutyCycleMetricsRequest.ProtoReflect.Descriptor instead. +func (*GetGatewayDutyCycleMetricsRequest) Descriptor() ([]byte, []int) { + return file_api_gateway_proto_rawDescGZIP(), []int{13} +} + +func (x *GetGatewayDutyCycleMetricsRequest) GetGatewayId() string { + if x != nil { + return x.GatewayId + } + return "" +} + +func (x *GetGatewayDutyCycleMetricsRequest) GetStart() *timestamppb.Timestamp { + if x != nil { + return x.Start + } + return nil +} + +func (x *GetGatewayDutyCycleMetricsRequest) GetEnd() *timestamppb.Timestamp { + if x != nil { + return x.End + } + return nil +} + +type GetGatewayDutyCycleMetricsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Percentage relative to max load. + MaxLoadPercentage *common.Metric `protobuf:"bytes,1,opt,name=max_load_percentage,json=maxLoadPercentage,proto3" json:"max_load_percentage,omitempty"` + // Percentage relative to tracking window. + WindowPercentage *common.Metric `protobuf:"bytes,2,opt,name=window_percentage,json=windowPercentage,proto3" json:"window_percentage,omitempty"` +} + +func (x *GetGatewayDutyCycleMetricsResponse) Reset() { + *x = GetGatewayDutyCycleMetricsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_api_gateway_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetGatewayDutyCycleMetricsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetGatewayDutyCycleMetricsResponse) ProtoMessage() {} + +func (x *GetGatewayDutyCycleMetricsResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_gateway_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetGatewayDutyCycleMetricsResponse.ProtoReflect.Descriptor instead. +func (*GetGatewayDutyCycleMetricsResponse) Descriptor() ([]byte, []int) { + return file_api_gateway_proto_rawDescGZIP(), []int{14} +} + +func (x *GetGatewayDutyCycleMetricsResponse) GetMaxLoadPercentage() *common.Metric { + if x != nil { + return x.MaxLoadPercentage + } + return nil +} + +func (x *GetGatewayDutyCycleMetricsResponse) GetWindowPercentage() *common.Metric { + if x != nil { + return x.WindowPercentage + } + return nil +} + var File_api_gateway_proto protoreflect.FileDescriptor var file_api_gateway_proto_rawDesc = []byte{ @@ -1203,67 +1326,98 @@ var file_api_gateway_proto_rawDesc = []byte{ 0x74, 0x78, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x12, 0x74, 0x78, 0x50, - 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x50, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2a, - 0x37, 0x0a, 0x0c, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, - 0x0e, 0x0a, 0x0a, 0x4e, 0x45, 0x56, 0x45, 0x52, 0x5f, 0x53, 0x45, 0x45, 0x4e, 0x10, 0x00, 0x12, - 0x0a, 0x0a, 0x06, 0x4f, 0x4e, 0x4c, 0x49, 0x4e, 0x45, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x4f, - 0x46, 0x46, 0x4c, 0x49, 0x4e, 0x45, 0x10, 0x02, 0x32, 0x91, 0x06, 0x0a, 0x0e, 0x47, 0x61, 0x74, - 0x65, 0x77, 0x61, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x55, 0x0a, 0x06, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, - 0x3a, 0x01, 0x2a, 0x22, 0x0d, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, - 0x79, 0x73, 0x12, 0x5a, 0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, - 0x47, 0x65, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, - 0x61, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x22, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x1c, 0x12, 0x1a, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, - 0x73, 0x2f, 0x7b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x6a, - 0x0a, 0x06, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x2d, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x27, 0x3a, 0x01, 0x2a, 0x1a, 0x22, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x61, 0x74, - 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x7b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x67, - 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x5f, 0x0a, 0x06, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x12, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x22, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1c, 0x2a, - 0x1a, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x7b, - 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x52, 0x0a, 0x04, 0x4c, - 0x69, 0x73, 0x74, 0x12, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x61, - 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, - 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x15, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0f, - 0x12, 0x0d, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x12, - 0xb1, 0x01, 0x0a, 0x19, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, - 0x6e, 0x74, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x2c, 0x2e, - 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x47, 0x61, 0x74, 0x65, - 0x77, 0x61, 0x79, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, - 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, - 0x79, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, - 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x37, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x31, 0x22, 0x2f, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, - 0x73, 0x2f, 0x7b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x67, - 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x2d, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, - 0x61, 0x74, 0x65, 0x12, 0x77, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, - 0x73, 0x12, 0x1d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, - 0x61, 0x79, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, - 0x79, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x2a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x24, 0x12, 0x22, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, + 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x50, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, + 0xa2, 0x01, 0x0a, 0x21, 0x47, 0x65, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x44, 0x75, + 0x74, 0x79, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x67, 0x61, 0x74, 0x65, 0x77, + 0x61, 0x79, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, + 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x2c, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, + 0x03, 0x65, 0x6e, 0x64, 0x22, 0xa1, 0x01, 0x0a, 0x22, 0x47, 0x65, 0x74, 0x47, 0x61, 0x74, 0x65, + 0x77, 0x61, 0x79, 0x44, 0x75, 0x74, 0x79, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x4d, 0x65, 0x74, 0x72, + 0x69, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x13, 0x6d, + 0x61, 0x78, 0x5f, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, + 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, + 0x6e, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x11, 0x6d, 0x61, 0x78, 0x4c, 0x6f, 0x61, + 0x64, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x12, 0x3b, 0x0a, 0x11, 0x77, + 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x5f, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, + 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x10, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x50, 0x65, + 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x2a, 0x37, 0x0a, 0x0c, 0x47, 0x61, 0x74, 0x65, + 0x77, 0x61, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x0a, 0x4e, 0x45, 0x56, 0x45, + 0x52, 0x5f, 0x53, 0x45, 0x45, 0x4e, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x4f, 0x4e, 0x4c, 0x49, + 0x4e, 0x45, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x4f, 0x46, 0x46, 0x4c, 0x49, 0x4e, 0x45, 0x10, + 0x02, 0x32, 0xb1, 0x07, 0x0a, 0x0e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x12, 0x55, 0x0a, 0x06, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x19, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x47, 0x61, 0x74, 0x65, 0x77, + 0x61, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, + 0x79, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x3a, 0x01, 0x2a, 0x22, 0x0d, 0x2f, 0x61, + 0x70, 0x69, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x12, 0x5a, 0x0a, 0x03, 0x47, + 0x65, 0x74, 0x12, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x47, 0x61, 0x74, 0x65, + 0x77, 0x61, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x47, 0x65, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x22, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1c, 0x12, 0x1a, 0x2f, 0x61, 0x70, + 0x69, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x7b, 0x67, 0x61, 0x74, 0x65, + 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x6a, 0x0a, 0x06, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x12, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x47, 0x61, + 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x22, 0x2d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x27, 0x3a, 0x01, 0x2a, 0x1a, + 0x22, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x7b, + 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, + 0x69, 0x64, 0x7d, 0x12, 0x5f, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x19, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, + 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x22, 0x22, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1c, 0x2a, 0x1a, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x7b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, - 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x42, 0x64, 0x0a, 0x11, - 0x69, 0x6f, 0x2e, 0x63, 0x68, 0x69, 0x72, 0x70, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x61, 0x70, - 0x69, 0x42, 0x0c, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, - 0x01, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x68, - 0x69, 0x72, 0x70, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x63, 0x68, 0x69, 0x72, 0x70, 0x73, 0x74, - 0x61, 0x63, 0x6b, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x34, 0x2f, 0x61, 0x70, - 0x69, 0xaa, 0x02, 0x0e, 0x43, 0x68, 0x69, 0x72, 0x70, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x41, - 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x52, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x18, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x15, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0f, 0x12, 0x0d, 0x2f, 0x61, 0x70, 0x69, 0x2f, + 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x12, 0xb1, 0x01, 0x0a, 0x19, 0x47, 0x65, 0x6e, + 0x65, 0x72, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x65, 0x72, 0x74, 0x69, + 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x2c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x6e, + 0x65, 0x72, 0x61, 0x74, 0x65, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x43, 0x6c, 0x69, 0x65, + 0x6e, 0x74, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, + 0x61, 0x74, 0x65, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, + 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x37, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x31, 0x22, 0x2f, 0x2f, 0x61, 0x70, + 0x69, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x7b, 0x67, 0x61, 0x74, 0x65, + 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, + 0x2d, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x77, 0x0a, 0x0a, + 0x47, 0x65, 0x74, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x1d, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x47, 0x65, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x4d, 0x65, 0x74, 0x72, 0x69, + 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x47, 0x65, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2a, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x24, 0x12, 0x22, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, + 0x2f, 0x7b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x6d, 0x65, + 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x9d, 0x01, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x44, 0x75, 0x74, + 0x79, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x26, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x44, 0x75, + 0x74, 0x79, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x47, + 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x44, 0x75, 0x74, 0x79, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x4d, + 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x35, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2f, 0x12, 0x2d, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x61, 0x74, + 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x7b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, + 0x64, 0x7d, 0x2f, 0x64, 0x75, 0x74, 0x79, 0x2d, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x2d, 0x6d, 0x65, + 0x74, 0x72, 0x69, 0x63, 0x73, 0x42, 0x64, 0x0a, 0x11, 0x69, 0x6f, 0x2e, 0x63, 0x68, 0x69, 0x72, + 0x70, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x61, 0x70, 0x69, 0x42, 0x0c, 0x47, 0x61, 0x74, 0x65, + 0x77, 0x61, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x68, 0x69, 0x72, 0x70, 0x73, 0x74, 0x61, 0x63, + 0x6b, 0x2f, 0x63, 0x68, 0x69, 0x72, 0x70, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x61, 0x70, 0x69, + 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x34, 0x2f, 0x61, 0x70, 0x69, 0xaa, 0x02, 0x0e, 0x43, 0x68, 0x69, + 0x72, 0x70, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x41, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, } var ( @@ -1279,7 +1433,7 @@ func file_api_gateway_proto_rawDescGZIP() []byte { } var file_api_gateway_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_api_gateway_proto_msgTypes = make([]protoimpl.MessageInfo, 16) +var file_api_gateway_proto_msgTypes = make([]protoimpl.MessageInfo, 18) var file_api_gateway_proto_goTypes = []interface{}{ (GatewayState)(0), // 0: api.GatewayState (*Gateway)(nil), // 1: api.Gateway @@ -1295,62 +1449,70 @@ var file_api_gateway_proto_goTypes = []interface{}{ (*GenerateGatewayClientCertificateResponse)(nil), // 11: api.GenerateGatewayClientCertificateResponse (*GetGatewayMetricsRequest)(nil), // 12: api.GetGatewayMetricsRequest (*GetGatewayMetricsResponse)(nil), // 13: api.GetGatewayMetricsResponse - nil, // 14: api.Gateway.TagsEntry - nil, // 15: api.Gateway.MetadataEntry - nil, // 16: api.GatewayListItem.PropertiesEntry - (*common.Location)(nil), // 17: common.Location - (*timestamppb.Timestamp)(nil), // 18: google.protobuf.Timestamp - (common.Aggregation)(0), // 19: common.Aggregation - (*common.Metric)(nil), // 20: common.Metric - (*emptypb.Empty)(nil), // 21: google.protobuf.Empty + (*GetGatewayDutyCycleMetricsRequest)(nil), // 14: api.GetGatewayDutyCycleMetricsRequest + (*GetGatewayDutyCycleMetricsResponse)(nil), // 15: api.GetGatewayDutyCycleMetricsResponse + nil, // 16: api.Gateway.TagsEntry + nil, // 17: api.Gateway.MetadataEntry + nil, // 18: api.GatewayListItem.PropertiesEntry + (*common.Location)(nil), // 19: common.Location + (*timestamppb.Timestamp)(nil), // 20: google.protobuf.Timestamp + (common.Aggregation)(0), // 21: common.Aggregation + (*common.Metric)(nil), // 22: common.Metric + (*emptypb.Empty)(nil), // 23: google.protobuf.Empty } var file_api_gateway_proto_depIdxs = []int32{ - 17, // 0: api.Gateway.location:type_name -> common.Location - 14, // 1: api.Gateway.tags:type_name -> api.Gateway.TagsEntry - 15, // 2: api.Gateway.metadata:type_name -> api.Gateway.MetadataEntry - 17, // 3: api.GatewayListItem.location:type_name -> common.Location - 16, // 4: api.GatewayListItem.properties:type_name -> api.GatewayListItem.PropertiesEntry - 18, // 5: api.GatewayListItem.created_at:type_name -> google.protobuf.Timestamp - 18, // 6: api.GatewayListItem.updated_at:type_name -> google.protobuf.Timestamp - 18, // 7: api.GatewayListItem.last_seen_at:type_name -> google.protobuf.Timestamp + 19, // 0: api.Gateway.location:type_name -> common.Location + 16, // 1: api.Gateway.tags:type_name -> api.Gateway.TagsEntry + 17, // 2: api.Gateway.metadata:type_name -> api.Gateway.MetadataEntry + 19, // 3: api.GatewayListItem.location:type_name -> common.Location + 18, // 4: api.GatewayListItem.properties:type_name -> api.GatewayListItem.PropertiesEntry + 20, // 5: api.GatewayListItem.created_at:type_name -> google.protobuf.Timestamp + 20, // 6: api.GatewayListItem.updated_at:type_name -> google.protobuf.Timestamp + 20, // 7: api.GatewayListItem.last_seen_at:type_name -> google.protobuf.Timestamp 0, // 8: api.GatewayListItem.state:type_name -> api.GatewayState 1, // 9: api.CreateGatewayRequest.gateway:type_name -> api.Gateway 1, // 10: api.GetGatewayResponse.gateway:type_name -> api.Gateway - 18, // 11: api.GetGatewayResponse.created_at:type_name -> google.protobuf.Timestamp - 18, // 12: api.GetGatewayResponse.updated_at:type_name -> google.protobuf.Timestamp - 18, // 13: api.GetGatewayResponse.last_seen_at:type_name -> google.protobuf.Timestamp + 20, // 11: api.GetGatewayResponse.created_at:type_name -> google.protobuf.Timestamp + 20, // 12: api.GetGatewayResponse.updated_at:type_name -> google.protobuf.Timestamp + 20, // 13: api.GetGatewayResponse.last_seen_at:type_name -> google.protobuf.Timestamp 1, // 14: api.UpdateGatewayRequest.gateway:type_name -> api.Gateway 2, // 15: api.ListGatewaysResponse.result:type_name -> api.GatewayListItem - 18, // 16: api.GenerateGatewayClientCertificateResponse.expires_at:type_name -> google.protobuf.Timestamp - 18, // 17: api.GetGatewayMetricsRequest.start:type_name -> google.protobuf.Timestamp - 18, // 18: api.GetGatewayMetricsRequest.end:type_name -> google.protobuf.Timestamp - 19, // 19: api.GetGatewayMetricsRequest.aggregation:type_name -> common.Aggregation - 20, // 20: api.GetGatewayMetricsResponse.rx_packets:type_name -> common.Metric - 20, // 21: api.GetGatewayMetricsResponse.tx_packets:type_name -> common.Metric - 20, // 22: api.GetGatewayMetricsResponse.tx_packets_per_freq:type_name -> common.Metric - 20, // 23: api.GetGatewayMetricsResponse.rx_packets_per_freq:type_name -> common.Metric - 20, // 24: api.GetGatewayMetricsResponse.tx_packets_per_dr:type_name -> common.Metric - 20, // 25: api.GetGatewayMetricsResponse.rx_packets_per_dr:type_name -> common.Metric - 20, // 26: api.GetGatewayMetricsResponse.tx_packets_per_status:type_name -> common.Metric - 3, // 27: api.GatewayService.Create:input_type -> api.CreateGatewayRequest - 4, // 28: api.GatewayService.Get:input_type -> api.GetGatewayRequest - 6, // 29: api.GatewayService.Update:input_type -> api.UpdateGatewayRequest - 7, // 30: api.GatewayService.Delete:input_type -> api.DeleteGatewayRequest - 8, // 31: api.GatewayService.List:input_type -> api.ListGatewaysRequest - 10, // 32: api.GatewayService.GenerateClientCertificate:input_type -> api.GenerateGatewayClientCertificateRequest - 12, // 33: api.GatewayService.GetMetrics:input_type -> api.GetGatewayMetricsRequest - 21, // 34: api.GatewayService.Create:output_type -> google.protobuf.Empty - 5, // 35: api.GatewayService.Get:output_type -> api.GetGatewayResponse - 21, // 36: api.GatewayService.Update:output_type -> google.protobuf.Empty - 21, // 37: api.GatewayService.Delete:output_type -> google.protobuf.Empty - 9, // 38: api.GatewayService.List:output_type -> api.ListGatewaysResponse - 11, // 39: api.GatewayService.GenerateClientCertificate:output_type -> api.GenerateGatewayClientCertificateResponse - 13, // 40: api.GatewayService.GetMetrics:output_type -> api.GetGatewayMetricsResponse - 34, // [34:41] is the sub-list for method output_type - 27, // [27:34] is the sub-list for method input_type - 27, // [27:27] is the sub-list for extension type_name - 27, // [27:27] is the sub-list for extension extendee - 0, // [0:27] is the sub-list for field type_name + 20, // 16: api.GenerateGatewayClientCertificateResponse.expires_at:type_name -> google.protobuf.Timestamp + 20, // 17: api.GetGatewayMetricsRequest.start:type_name -> google.protobuf.Timestamp + 20, // 18: api.GetGatewayMetricsRequest.end:type_name -> google.protobuf.Timestamp + 21, // 19: api.GetGatewayMetricsRequest.aggregation:type_name -> common.Aggregation + 22, // 20: api.GetGatewayMetricsResponse.rx_packets:type_name -> common.Metric + 22, // 21: api.GetGatewayMetricsResponse.tx_packets:type_name -> common.Metric + 22, // 22: api.GetGatewayMetricsResponse.tx_packets_per_freq:type_name -> common.Metric + 22, // 23: api.GetGatewayMetricsResponse.rx_packets_per_freq:type_name -> common.Metric + 22, // 24: api.GetGatewayMetricsResponse.tx_packets_per_dr:type_name -> common.Metric + 22, // 25: api.GetGatewayMetricsResponse.rx_packets_per_dr:type_name -> common.Metric + 22, // 26: api.GetGatewayMetricsResponse.tx_packets_per_status:type_name -> common.Metric + 20, // 27: api.GetGatewayDutyCycleMetricsRequest.start:type_name -> google.protobuf.Timestamp + 20, // 28: api.GetGatewayDutyCycleMetricsRequest.end:type_name -> google.protobuf.Timestamp + 22, // 29: api.GetGatewayDutyCycleMetricsResponse.max_load_percentage:type_name -> common.Metric + 22, // 30: api.GetGatewayDutyCycleMetricsResponse.window_percentage:type_name -> common.Metric + 3, // 31: api.GatewayService.Create:input_type -> api.CreateGatewayRequest + 4, // 32: api.GatewayService.Get:input_type -> api.GetGatewayRequest + 6, // 33: api.GatewayService.Update:input_type -> api.UpdateGatewayRequest + 7, // 34: api.GatewayService.Delete:input_type -> api.DeleteGatewayRequest + 8, // 35: api.GatewayService.List:input_type -> api.ListGatewaysRequest + 10, // 36: api.GatewayService.GenerateClientCertificate:input_type -> api.GenerateGatewayClientCertificateRequest + 12, // 37: api.GatewayService.GetMetrics:input_type -> api.GetGatewayMetricsRequest + 14, // 38: api.GatewayService.GetDutyCycleMetrics:input_type -> api.GetGatewayDutyCycleMetricsRequest + 23, // 39: api.GatewayService.Create:output_type -> google.protobuf.Empty + 5, // 40: api.GatewayService.Get:output_type -> api.GetGatewayResponse + 23, // 41: api.GatewayService.Update:output_type -> google.protobuf.Empty + 23, // 42: api.GatewayService.Delete:output_type -> google.protobuf.Empty + 9, // 43: api.GatewayService.List:output_type -> api.ListGatewaysResponse + 11, // 44: api.GatewayService.GenerateClientCertificate:output_type -> api.GenerateGatewayClientCertificateResponse + 13, // 45: api.GatewayService.GetMetrics:output_type -> api.GetGatewayMetricsResponse + 15, // 46: api.GatewayService.GetDutyCycleMetrics:output_type -> api.GetGatewayDutyCycleMetricsResponse + 39, // [39:47] is the sub-list for method output_type + 31, // [31:39] is the sub-list for method input_type + 31, // [31:31] is the sub-list for extension type_name + 31, // [31:31] is the sub-list for extension extendee + 0, // [0:31] is the sub-list for field type_name } func init() { file_api_gateway_proto_init() } @@ -1515,6 +1677,30 @@ func file_api_gateway_proto_init() { return nil } } + file_api_gateway_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetGatewayDutyCycleMetricsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_gateway_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetGatewayDutyCycleMetricsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -1522,7 +1708,7 @@ func file_api_gateway_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_api_gateway_proto_rawDesc, NumEnums: 1, - NumMessages: 16, + NumMessages: 18, NumExtensions: 0, NumServices: 1, }, diff --git a/api/go/api/gateway_grpc.pb.go b/api/go/api/gateway_grpc.pb.go index 9c35fc5b..3f3d7d4e 100644 --- a/api/go/api/gateway_grpc.pb.go +++ b/api/go/api/gateway_grpc.pb.go @@ -27,6 +27,7 @@ const ( GatewayService_List_FullMethodName = "/api.GatewayService/List" GatewayService_GenerateClientCertificate_FullMethodName = "/api.GatewayService/GenerateClientCertificate" GatewayService_GetMetrics_FullMethodName = "/api.GatewayService/GetMetrics" + GatewayService_GetDutyCycleMetrics_FullMethodName = "/api.GatewayService/GetDutyCycleMetrics" ) // GatewayServiceClient is the client API for GatewayService service. @@ -47,6 +48,9 @@ type GatewayServiceClient interface { GenerateClientCertificate(ctx context.Context, in *GenerateGatewayClientCertificateRequest, opts ...grpc.CallOption) (*GenerateGatewayClientCertificateResponse, error) // GetMetrics returns the gateway metrics. GetMetrics(ctx context.Context, in *GetGatewayMetricsRequest, opts ...grpc.CallOption) (*GetGatewayMetricsResponse, error) + // GetDutyCycleMetrics returns the duty-cycle metrics. + // Note that only the last 2 hours of data are stored. Currently only per minute aggregation is available. + GetDutyCycleMetrics(ctx context.Context, in *GetGatewayDutyCycleMetricsRequest, opts ...grpc.CallOption) (*GetGatewayDutyCycleMetricsResponse, error) } type gatewayServiceClient struct { @@ -120,6 +124,15 @@ func (c *gatewayServiceClient) GetMetrics(ctx context.Context, in *GetGatewayMet return out, nil } +func (c *gatewayServiceClient) GetDutyCycleMetrics(ctx context.Context, in *GetGatewayDutyCycleMetricsRequest, opts ...grpc.CallOption) (*GetGatewayDutyCycleMetricsResponse, error) { + out := new(GetGatewayDutyCycleMetricsResponse) + err := c.cc.Invoke(ctx, GatewayService_GetDutyCycleMetrics_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // GatewayServiceServer is the server API for GatewayService service. // All implementations must embed UnimplementedGatewayServiceServer // for forward compatibility @@ -138,6 +151,9 @@ type GatewayServiceServer interface { GenerateClientCertificate(context.Context, *GenerateGatewayClientCertificateRequest) (*GenerateGatewayClientCertificateResponse, error) // GetMetrics returns the gateway metrics. GetMetrics(context.Context, *GetGatewayMetricsRequest) (*GetGatewayMetricsResponse, error) + // GetDutyCycleMetrics returns the duty-cycle metrics. + // Note that only the last 2 hours of data are stored. Currently only per minute aggregation is available. + GetDutyCycleMetrics(context.Context, *GetGatewayDutyCycleMetricsRequest) (*GetGatewayDutyCycleMetricsResponse, error) mustEmbedUnimplementedGatewayServiceServer() } @@ -166,6 +182,9 @@ func (UnimplementedGatewayServiceServer) GenerateClientCertificate(context.Conte func (UnimplementedGatewayServiceServer) GetMetrics(context.Context, *GetGatewayMetricsRequest) (*GetGatewayMetricsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetMetrics not implemented") } +func (UnimplementedGatewayServiceServer) GetDutyCycleMetrics(context.Context, *GetGatewayDutyCycleMetricsRequest) (*GetGatewayDutyCycleMetricsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetDutyCycleMetrics not implemented") +} func (UnimplementedGatewayServiceServer) mustEmbedUnimplementedGatewayServiceServer() {} // UnsafeGatewayServiceServer may be embedded to opt out of forward compatibility for this service. @@ -305,6 +324,24 @@ func _GatewayService_GetMetrics_Handler(srv interface{}, ctx context.Context, de return interceptor(ctx, in, info, handler) } +func _GatewayService_GetDutyCycleMetrics_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetGatewayDutyCycleMetricsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GatewayServiceServer).GetDutyCycleMetrics(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: GatewayService_GetDutyCycleMetrics_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GatewayServiceServer).GetDutyCycleMetrics(ctx, req.(*GetGatewayDutyCycleMetricsRequest)) + } + return interceptor(ctx, in, info, handler) +} + // GatewayService_ServiceDesc is the grpc.ServiceDesc for GatewayService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -340,6 +377,10 @@ var GatewayService_ServiceDesc = grpc.ServiceDesc{ MethodName: "GetMetrics", Handler: _GatewayService_GetMetrics_Handler, }, + { + MethodName: "GetDutyCycleMetrics", + Handler: _GatewayService_GetDutyCycleMetrics_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "api/gateway.proto", diff --git a/api/go/common/common.pb.go b/api/go/common/common.pb.go index 1cad6161..5fbe56fe 100644 --- a/api/go/common/common.pb.go +++ b/api/go/common/common.pb.go @@ -434,6 +434,8 @@ const ( Aggregation_DAY Aggregation = 1 // Month. Aggregation_MONTH Aggregation = 2 + // Minute. + Aggregation_MINUTE Aggregation = 3 ) // Enum value maps for Aggregation. @@ -442,11 +444,13 @@ var ( 0: "HOUR", 1: "DAY", 2: "MONTH", + 3: "MINUTE", } Aggregation_value = map[string]int32{ - "HOUR": 0, - "DAY": 1, - "MONTH": 2, + "HOUR": 0, + "DAY": 1, + "MONTH": 2, + "MINUTE": 3, } ) @@ -1054,27 +1058,28 @@ var file_common_common_proto_rawDesc = []byte{ 0x56, 0x45, 0x52, 0x5f, 0x52, 0x53, 0x53, 0x49, 0x10, 0x04, 0x12, 0x15, 0x0a, 0x11, 0x47, 0x45, 0x4f, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x4c, 0x56, 0x45, 0x52, 0x5f, 0x47, 0x4e, 0x53, 0x53, 0x10, 0x05, 0x12, 0x15, 0x0a, 0x11, 0x47, 0x45, 0x4f, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x4c, 0x56, 0x45, - 0x52, 0x5f, 0x57, 0x49, 0x46, 0x49, 0x10, 0x06, 0x2a, 0x2b, 0x0a, 0x0b, 0x41, 0x67, 0x67, 0x72, + 0x52, 0x5f, 0x57, 0x49, 0x46, 0x49, 0x10, 0x06, 0x2a, 0x37, 0x0a, 0x0b, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x4f, 0x55, 0x52, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x44, 0x41, 0x59, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x4d, 0x4f, - 0x4e, 0x54, 0x48, 0x10, 0x02, 0x2a, 0x32, 0x0a, 0x0a, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x4b, - 0x69, 0x6e, 0x64, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x45, 0x52, 0x10, 0x00, - 0x12, 0x0c, 0x0a, 0x08, 0x41, 0x42, 0x53, 0x4f, 0x4c, 0x55, 0x54, 0x45, 0x10, 0x01, 0x12, 0x09, - 0x0a, 0x05, 0x47, 0x41, 0x55, 0x47, 0x45, 0x10, 0x02, 0x2a, 0x39, 0x0a, 0x0a, 0x52, 0x65, 0x67, - 0x75, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x12, 0x52, 0x45, 0x47, 0x55, 0x4c, - 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, - 0x13, 0x0a, 0x0f, 0x45, 0x54, 0x53, 0x49, 0x5f, 0x45, 0x4e, 0x5f, 0x33, 0x30, 0x30, 0x5f, 0x32, - 0x32, 0x30, 0x10, 0x01, 0x2a, 0x34, 0x0a, 0x0b, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6c, - 0x61, 0x73, 0x73, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x41, 0x10, 0x00, - 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x42, 0x10, 0x01, 0x12, 0x0b, 0x0a, - 0x07, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x43, 0x10, 0x02, 0x42, 0x69, 0x0a, 0x11, 0x69, 0x6f, - 0x2e, 0x63, 0x68, 0x69, 0x72, 0x70, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x61, 0x70, 0x69, 0x42, - 0x0b, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x31, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x68, 0x69, 0x72, 0x70, - 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x63, 0x68, 0x69, 0x72, 0x70, 0x73, 0x74, 0x61, 0x63, 0x6b, - 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x34, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, - 0x6e, 0xaa, 0x02, 0x11, 0x43, 0x68, 0x69, 0x72, 0x70, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x43, - 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x4e, 0x54, 0x48, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x4d, 0x49, 0x4e, 0x55, 0x54, 0x45, 0x10, + 0x03, 0x2a, 0x32, 0x0a, 0x0a, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x4b, 0x69, 0x6e, 0x64, 0x12, + 0x0b, 0x0a, 0x07, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x45, 0x52, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, + 0x41, 0x42, 0x53, 0x4f, 0x4c, 0x55, 0x54, 0x45, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x47, 0x41, + 0x55, 0x47, 0x45, 0x10, 0x02, 0x2a, 0x39, 0x0a, 0x0a, 0x52, 0x65, 0x67, 0x75, 0x6c, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x12, 0x52, 0x45, 0x47, 0x55, 0x4c, 0x41, 0x54, 0x49, 0x4f, + 0x4e, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x45, + 0x54, 0x53, 0x49, 0x5f, 0x45, 0x4e, 0x5f, 0x33, 0x30, 0x30, 0x5f, 0x32, 0x32, 0x30, 0x10, 0x01, + 0x2a, 0x34, 0x0a, 0x0b, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x12, + 0x0b, 0x0a, 0x07, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x41, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, + 0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x42, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x4c, 0x41, + 0x53, 0x53, 0x5f, 0x43, 0x10, 0x02, 0x42, 0x69, 0x0a, 0x11, 0x69, 0x6f, 0x2e, 0x63, 0x68, 0x69, + 0x72, 0x70, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x61, 0x70, 0x69, 0x42, 0x0b, 0x43, 0x6f, 0x6d, + 0x6d, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x68, 0x69, 0x72, 0x70, 0x73, 0x74, 0x61, 0x63, + 0x6b, 0x2f, 0x63, 0x68, 0x69, 0x72, 0x70, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x61, 0x70, 0x69, + 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x34, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0xaa, 0x02, 0x11, + 0x43, 0x68, 0x69, 0x72, 0x70, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, + 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/api/proto/api/gateway.proto b/api/proto/api/gateway.proto index 092cbb9b..1d007144 100644 --- a/api/proto/api/gateway.proto +++ b/api/proto/api/gateway.proto @@ -65,6 +65,14 @@ service GatewayService { get: "/api/gateways/{gateway_id}/metrics" }; } + + // GetDutyCycleMetrics returns the duty-cycle metrics. + // Note that only the last 2 hours of data are stored. Currently only per minute aggregation is available. + rpc GetDutyCycleMetrics(GetGatewayDutyCycleMetricsRequest) returns (GetGatewayDutyCycleMetricsResponse) { + option(google.api.http) = { + get: "/api/gateways/{gateway_id}/duty-cycle-metrics" + }; + } } enum GatewayState { @@ -255,3 +263,22 @@ message GetGatewayMetricsResponse { // TX packets per status. common.Metric tx_packets_per_status = 7; } + +message GetGatewayDutyCycleMetricsRequest { + // Gateway ID (EUI64). + string gateway_id = 1; + + // Interval start timestamp. + google.protobuf.Timestamp start = 2; + + // Interval end timestamp. + google.protobuf.Timestamp end = 3; +} + +message GetGatewayDutyCycleMetricsResponse { + // Percentage relative to max load. + common.Metric max_load_percentage = 1; + + // Percentage relative to tracking window. + common.Metric window_percentage = 2; +} diff --git a/api/proto/common/common.proto b/api/proto/common/common.proto index ff4afa01..88808a9d 100644 --- a/api/proto/common/common.proto +++ b/api/proto/common/common.proto @@ -141,6 +141,9 @@ enum Aggregation { // Month. MONTH = 2; + + // Minute. + MINUTE = 3; } enum MetricKind { diff --git a/api/rust/proto/chirpstack/api/gateway.proto b/api/rust/proto/chirpstack/api/gateway.proto index 092cbb9b..1d007144 100644 --- a/api/rust/proto/chirpstack/api/gateway.proto +++ b/api/rust/proto/chirpstack/api/gateway.proto @@ -65,6 +65,14 @@ service GatewayService { get: "/api/gateways/{gateway_id}/metrics" }; } + + // GetDutyCycleMetrics returns the duty-cycle metrics. + // Note that only the last 2 hours of data are stored. Currently only per minute aggregation is available. + rpc GetDutyCycleMetrics(GetGatewayDutyCycleMetricsRequest) returns (GetGatewayDutyCycleMetricsResponse) { + option(google.api.http) = { + get: "/api/gateways/{gateway_id}/duty-cycle-metrics" + }; + } } enum GatewayState { @@ -255,3 +263,22 @@ message GetGatewayMetricsResponse { // TX packets per status. common.Metric tx_packets_per_status = 7; } + +message GetGatewayDutyCycleMetricsRequest { + // Gateway ID (EUI64). + string gateway_id = 1; + + // Interval start timestamp. + google.protobuf.Timestamp start = 2; + + // Interval end timestamp. + google.protobuf.Timestamp end = 3; +} + +message GetGatewayDutyCycleMetricsResponse { + // Percentage relative to max load. + common.Metric max_load_percentage = 1; + + // Percentage relative to tracking window. + common.Metric window_percentage = 2; +} diff --git a/api/rust/proto/chirpstack/common/common.proto b/api/rust/proto/chirpstack/common/common.proto index ff4afa01..88808a9d 100644 --- a/api/rust/proto/chirpstack/common/common.proto +++ b/api/rust/proto/chirpstack/common/common.proto @@ -141,6 +141,9 @@ enum Aggregation { // Month. MONTH = 2; + + // Minute. + MINUTE = 3; } enum MetricKind { diff --git a/chirpstack/src/api/gateway.rs b/chirpstack/src/api/gateway.rs index fa8c7d8c..deaa20b2 100644 --- a/chirpstack/src/api/gateway.rs +++ b/chirpstack/src/api/gateway.rs @@ -614,11 +614,180 @@ impl GatewayService for Gateway { Ok(resp) } + + async fn get_duty_cycle_metrics( + &self, + request: Request, + ) -> Result, Status> { + let req = request.get_ref(); + let gateway_id = EUI64::from_str(&req.gateway_id).map_err(|e| e.status())?; + + self.validator + .validate( + request.extensions(), + validator::ValidateGatewayAccess::new(validator::Flag::Read, gateway_id), + ) + .await?; + + let start = SystemTime::try_from( + req.start + .as_ref() + .ok_or_else(|| anyhow!("start is None")) + .map_err(|e| e.status())? + .clone(), + ) + .map_err(|e| e.status())?; + + let end = SystemTime::try_from( + req.end + .as_ref() + .ok_or_else(|| anyhow!("end is None")) + .map_err(|e| e.status())? + .clone(), + ) + .map_err(|e| e.status())?; + + let start: DateTime = start.into(); + let end: DateTime = end.into(); + + let dc_metrics = metrics::get( + &format!("gw:dc:{}", gateway_id), + metrics::Kind::COUNTER, + metrics::Aggregation::MINUTE, + start, + end, + ) + .await + .map_err(|e| e.status())?; + + let out = api::GetGatewayDutyCycleMetricsResponse { + max_load_percentage: Some({ + // discover all data-sets + let mut datasets: HashSet = HashSet::new(); + for m in &dc_metrics { + for k in m.metrics.keys() { + if k.starts_with("max_load_perc_") { + datasets.insert(k.to_string()); + } + } + } + + common::Metric { + name: "Percentage of max tx duty-cycle".into(), + timestamps: dc_metrics + .iter() + .map(|row| { + let ts: DateTime = row.time.into(); + let ts: pbjson_types::Timestamp = ts.into(); + ts + }) + .collect(), + datasets: datasets + .iter() + .map(|key| common::MetricDataset { + label: { + let s = key.strip_prefix("max_load_perc_").unwrap_or_default(); + let s: Vec<&str> = s.split('_').collect(); + + format!( + "{} ({:.2}MHz - {:.2}MHz: {:.2}%)", + s.first().unwrap_or(&""), + s.get(1) + .unwrap_or(&"") + .parse::() + .map(|v| v / 1_000_000.0) + .unwrap_or(0.0), + s.get(2) + .unwrap_or(&"") + .parse::() + .map(|v| v / 1_000_000.0) + .unwrap_or(0.0), + s.get(3) + .unwrap_or(&"") + .parse::() + .map(|v| v / 10.0) + .unwrap_or(0.0), + ) + }, + data: dc_metrics + .iter() + .map(|row| row.metrics.get(key).cloned().unwrap_or(0.0) as f32) + .collect(), + }) + .collect(), + kind: common::MetricKind::Absolute.into(), + } + }), + window_percentage: Some({ + // discover all data-sets + let mut datasets: HashSet = HashSet::new(); + for m in &dc_metrics { + for k in m.metrics.keys() { + if k.starts_with("window_perc_") { + datasets.insert(k.to_string()); + } + } + } + + common::Metric { + name: "Tx duty-cycle".into(), + timestamps: dc_metrics + .iter() + .map(|row| { + let ts: DateTime = row.time.into(); + let ts: pbjson_types::Timestamp = ts.into(); + ts + }) + .collect(), + datasets: datasets + .iter() + .map(|key| common::MetricDataset { + label: { + let s = key.strip_prefix("window_perc_").unwrap_or_default(); + let s: Vec<&str> = s.split('_').collect(); + + format!( + "{} ({:.2}MHz - {:.2}MHz: {:.2}%)", + s.first().unwrap_or(&""), + s.get(1) + .unwrap_or(&"") + .parse::() + .map(|v| v / 1_000_000.0) + .unwrap_or(0.0), + s.get(2) + .unwrap_or(&"") + .parse::() + .map(|v| v / 1_000_000.0) + .unwrap_or(0.0), + s.get(3) + .unwrap_or(&"") + .parse::() + .map(|v| v / 10.0) + .unwrap_or(0.0), + ) + }, + data: dc_metrics + .iter() + .map(|row| row.metrics.get(key).cloned().unwrap_or(0.0) as f32) + .collect(), + }) + .collect(), + kind: common::MetricKind::Absolute.into(), + } + }), + }; + + let mut resp = Response::new(out); + resp.metadata_mut() + .insert("x-log-gateway_id", req.gateway_id.parse().unwrap()); + + Ok(resp) + } } #[cfg(test)] pub mod test { - use chrono::{Datelike, Local, TimeZone}; + use chrono::{Datelike, Local, TimeZone, Timelike}; use std::collections::HashMap; use super::*; @@ -823,7 +992,13 @@ pub mod test { m.metrics.insert("tx_freq_868200000".into(), 5.0); m.metrics.insert("tx_dr_4".into(), 5.0); - metrics::save("gw:0102030405060708", &m).await.unwrap(); + metrics::save( + "gw:0102030405060708", + &m, + &metrics::Aggregation::default_aggregations(), + ) + .await + .unwrap(); // setup api let service = Gateway::new(RequestValidator::new()); @@ -862,4 +1037,130 @@ pub mod test { stats_resp.rx_packets ); } + + #[tokio::test] + async fn test_gateway_duty_cycle_stats() { + let _guard = test::prepare().await; + + // setup admin user + let u = user::User { + is_admin: true, + is_active: true, + email: "admin@admin".into(), + email_verified: true, + ..Default::default() + }; + let u = user::create(u).await.unwrap(); + + // create tenant + let t = tenant::create(tenant::Tenant { + name: "test-tenant".into(), + can_have_gateways: true, + max_gateway_count: 10, + ..Default::default() + }) + .await + .unwrap(); + + // create gateway + let _ = gateway::create(gateway::Gateway { + gateway_id: EUI64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]), + tenant_id: t.id.clone(), + name: "test-gw".into(), + ..Default::default() + }) + .await + .unwrap(); + + let now = Local::now(); + + // insert stats + let mut m = metrics::Record { + kind: metrics::Kind::COUNTER, + time: now.into(), + metrics: HashMap::new(), + }; + + m.metrics + .insert("window_perc_M_868000000_868600000_10".into(), 0.5); + m.metrics + .insert("max_load_perc_L_865000000_868000000_10".into(), 5.0); + + metrics::save( + "gw:dc:0102030405060708", + &m, + &[metrics::Aggregation::MINUTE], + ) + .await + .unwrap(); + + // setup api + let service = Gateway::new(RequestValidator::new()); + + // request stats + let now_st: SystemTime = now.into(); + let stats_req = api::GetGatewayDutyCycleMetricsRequest { + gateway_id: "0102030405060708".into(), + start: Some(now_st.into()), + end: Some(now_st.into()), + }; + let mut stats_req = Request::new(stats_req); + stats_req + .extensions_mut() + .insert(AuthID::User(u.id.clone())); + let stats_resp = service.get_duty_cycle_metrics(stats_req).await.unwrap(); + let stats_resp = stats_resp.get_ref(); + assert_eq!( + Some(common::Metric { + name: "Percentage of max tx duty-cycle".into(), + timestamps: vec![{ + let ts = Local + .with_ymd_and_hms( + now.year(), + now.month(), + now.day(), + now.hour(), + now.minute(), + 0, + ) + .unwrap(); + //let ts: SystemTime = ts.into(); + let ts: DateTime = ts.into(); + ts.into() + }], + datasets: vec![common::MetricDataset { + label: "L (865.00MHz - 868.00MHz: 1.00%)".into(), + data: vec![5.0], + }], + kind: common::MetricKind::Absolute.into(), + }), + stats_resp.max_load_percentage + ); + assert_eq!( + Some(common::Metric { + name: "Tx duty-cycle".into(), + timestamps: vec![{ + let ts = Local + .with_ymd_and_hms( + now.year(), + now.month(), + now.day(), + now.hour(), + now.minute(), + 0, + ) + .unwrap(); + //let ts: SystemTime = ts.into(); + let ts: DateTime = ts.into(); + ts.into() + }], + datasets: vec![common::MetricDataset { + label: "M (868.00MHz - 868.60MHz: 1.00%)".into(), + data: vec![0.5], + }], + kind: common::MetricKind::Absolute.into(), + }), + stats_resp.window_percentage + ); + } } diff --git a/chirpstack/src/api/helpers.rs b/chirpstack/src/api/helpers.rs index 9a982d02..427bd4db 100644 --- a/chirpstack/src/api/helpers.rs +++ b/chirpstack/src/api/helpers.rs @@ -169,6 +169,7 @@ impl FromProto for api::MeasurementKind { impl ToProto for Aggregation { fn to_proto(self) -> common::Aggregation { match self { + Aggregation::MINUTE => common::Aggregation::Minute, Aggregation::HOUR => common::Aggregation::Hour, Aggregation::DAY => common::Aggregation::Day, Aggregation::MONTH => common::Aggregation::Month, @@ -179,6 +180,7 @@ impl ToProto for Aggregation { impl FromProto for common::Aggregation { fn from_proto(self) -> Aggregation { match self { + common::Aggregation::Minute => Aggregation::MINUTE, common::Aggregation::Hour => Aggregation::HOUR, common::Aggregation::Day => Aggregation::DAY, common::Aggregation::Month => Aggregation::MONTH, diff --git a/chirpstack/src/storage/metrics.rs b/chirpstack/src/storage/metrics.rs index b5c3ae33..db5121ed 100644 --- a/chirpstack/src/storage/metrics.rs +++ b/chirpstack/src/storage/metrics.rs @@ -13,11 +13,18 @@ use crate::storage::{get_async_redis_conn, redis_key}; #[allow(non_camel_case_types)] #[derive(Deserialize, Serialize, Copy, Clone, Debug, Eq, PartialEq)] pub enum Aggregation { + MINUTE, HOUR, DAY, MONTH, } +impl Aggregation { + pub fn default_aggregations() -> Vec { + vec![Aggregation::HOUR, Aggregation::DAY, Aggregation::MONTH] + } +} + impl fmt::Display for Aggregation { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self) @@ -48,16 +55,13 @@ pub struct Record { fn get_ttl(a: Aggregation) -> Duration { match a { + Aggregation::MINUTE => Duration::from_secs(60 * 60 * 2), // two hours Aggregation::HOUR => Duration::from_secs(60 * 60 * 24 * 2), // two days Aggregation::DAY => Duration::from_secs(60 * 60 * 24 * 31 * 2), // two months Aggregation::MONTH => Duration::from_secs(60 * 60 * 24 * 365 * 2), // two years } } -fn get_aggregations() -> Vec { - vec![Aggregation::HOUR, Aggregation::DAY, Aggregation::MONTH] -} - fn get_key(name: &str, a: Aggregation, dt: DateTime) -> String { redis_key(format!( "metrics:{{{}}}:{}:{}", @@ -82,7 +86,7 @@ pub async fn save_state(name: &str, state: &str) -> Result<()> { Ok(()) } -pub async fn save(name: &str, record: &Record) -> Result<()> { +pub async fn save(name: &str, record: &Record, aggregations: &[Aggregation]) -> Result<()> { if record.metrics.is_empty() { return Ok(()); } @@ -90,10 +94,20 @@ pub async fn save(name: &str, record: &Record) -> Result<()> { let mut pipe = redis::pipe(); pipe.atomic(); - for a in get_aggregations() { - let ttl = get_ttl(a); + for a in aggregations { + let ttl = get_ttl(*a); let ts: DateTime = match a { + Aggregation::MINUTE => Local + .with_ymd_and_hms( + record.time.year(), + record.time.month(), + record.time.day(), + record.time.hour(), + record.time.minute(), + 0, + ) + .unwrap(), Aggregation::HOUR => Local .with_ymd_and_hms( record.time.year(), @@ -119,7 +133,7 @@ pub async fn save(name: &str, record: &Record) -> Result<()> { .unwrap(), }; - let key = get_key(name, a, ts); + let key = get_key(name, *a, ts); for (k, v) in &record.metrics { // Passing a reference to hincr will return a runtime error. @@ -178,6 +192,34 @@ pub async fn get( let mut timestamps: Vec> = Vec::new(); match a { + Aggregation::MINUTE => { + let mut ts = Local + .with_ymd_and_hms( + start.year(), + start.month(), + start.day(), + start.hour(), + start.minute(), + 0, + ) + .unwrap(); + let end = Local + .with_ymd_and_hms( + end.year(), + end.month(), + end.day(), + end.hour(), + end.minute(), + 0, + ) + .unwrap(); + + while ts.le(&end) { + timestamps.push(ts); + keys.push(get_key(name, a, ts)); + ts += ChronoDuration::minutes(1); + } + } Aggregation::HOUR => { let mut ts = Local .with_ymd_and_hms(start.year(), start.month(), start.day(), start.hour(), 0, 0) @@ -304,6 +346,41 @@ pub mod test { use super::*; use crate::test; + #[tokio::test] + async fn test_minute() { + let _guard = test::prepare().await; + + let records = vec![ + Record { + time: Local.with_ymd_and_hms(2018, 1, 1, 1, 1, 0).unwrap(), + kind: Kind::ABSOLUTE, + metrics: [("foo".into(), 1f64), ("bar".into(), 2f64)] + .iter() + .cloned() + .collect(), + }, + Record { + time: Local.with_ymd_and_hms(2018, 1, 1, 1, 1, 10).unwrap(), + kind: Kind::ABSOLUTE, + metrics: [("foo".into(), 4f64), ("bar".into(), 4f64)] + .iter() + .cloned() + .collect(), + }, + Record { + time: Local.with_ymd_and_hms(2018, 1, 1, 1, 2, 0).unwrap(), + kind: Kind::ABSOLUTE, + metrics: [("foo".into(), 5f64), ("bar".into(), 6f64)] + .iter() + .cloned() + .collect(), + }, + ]; + for r in &records { + save("test", r, &[Aggregation::MINUTE]).await.unwrap(); + } + } + #[tokio::test] async fn test_hour() { let _guard = test::prepare().await; @@ -335,7 +412,7 @@ pub mod test { }, ]; for r in &records { - save("test", r).await.unwrap(); + save("test", r, &[Aggregation::HOUR]).await.unwrap(); } let resp = get( @@ -402,7 +479,7 @@ pub mod test { }, ]; for r in &records { - save("test", r).await.unwrap(); + save("test", r, &[Aggregation::DAY]).await.unwrap(); } let resp = get( @@ -469,7 +546,7 @@ pub mod test { }, ]; for r in &records { - save("test", r).await.unwrap(); + save("test", r, &[Aggregation::DAY]).await.unwrap(); } let resp = get( @@ -536,7 +613,7 @@ pub mod test { }, ]; for r in &records { - save("test", r).await.unwrap(); + save("test", r, &[Aggregation::MONTH]).await.unwrap(); } let resp = get( @@ -595,7 +672,7 @@ pub mod test { }, ]; for r in &records { - save("test", r).await.unwrap(); + save("test", r, &[Aggregation::HOUR]).await.unwrap(); } let resp = get( @@ -644,7 +721,7 @@ pub mod test { }, ]; for r in &records { - save("test", r).await.unwrap(); + save("test", r, &[Aggregation::HOUR]).await.unwrap(); } let resp = get( @@ -693,7 +770,7 @@ pub mod test { }, ]; for r in &records { - save("test", r).await.unwrap(); + save("test", r, &[Aggregation::HOUR]).await.unwrap(); } let resp = get( diff --git a/chirpstack/src/uplink/data.rs b/chirpstack/src/uplink/data.rs index f265dbca..de534e5c 100644 --- a/chirpstack/src/uplink/data.rs +++ b/chirpstack/src/uplink/data.rs @@ -1045,7 +1045,12 @@ impl Data { metrics: [("value".to_string(), v)].iter().cloned().collect(), }; - metrics::save(&format!("device:{}:{}", dev.dev_eui, k), &record).await?; + metrics::save( + &format!("device:{}:{}", dev.dev_eui, k), + &record, + &metrics::Aggregation::default_aggregations(), + ) + .await?; } pbjson_types::value::Kind::StringValue(v) => { metrics::save_state( @@ -1215,7 +1220,12 @@ impl Data { let dev = self.device.as_ref().unwrap(); - metrics::save(&format!("device:{}", dev.dev_eui), &record).await?; + metrics::save( + &format!("device:{}", dev.dev_eui), + &record, + &metrics::Aggregation::default_aggregations(), + ) + .await?; Ok(()) } @@ -1246,7 +1256,12 @@ impl Data { let dev = self.device.as_ref().unwrap(); - metrics::save(&format!("device:{}", dev.dev_eui), &record).await?; + metrics::save( + &format!("device:{}", dev.dev_eui), + &record, + &metrics::Aggregation::default_aggregations(), + ) + .await?; Ok(()) } diff --git a/chirpstack/src/uplink/join.rs b/chirpstack/src/uplink/join.rs index 4337723d..09c87ca9 100644 --- a/chirpstack/src/uplink/join.rs +++ b/chirpstack/src/uplink/join.rs @@ -441,6 +441,7 @@ impl JoinRequest { .cloned() .collect(), }, + &metrics::Aggregation::default_aggregations(), ) .await?; @@ -490,6 +491,7 @@ impl JoinRequest { kind: metrics::Kind::ABSOLUTE, metrics: [("error_OTAA".into(), 1f64)].iter().cloned().collect(), }, + &metrics::Aggregation::default_aggregations(), ) .await?; diff --git a/chirpstack/src/uplink/join_sns.rs b/chirpstack/src/uplink/join_sns.rs index 99b6c901..98244a27 100644 --- a/chirpstack/src/uplink/join_sns.rs +++ b/chirpstack/src/uplink/join_sns.rs @@ -338,6 +338,7 @@ impl JoinRequest { .cloned() .collect(), }, + &metrics::Aggregation::default_aggregations(), ) .await?; @@ -387,6 +388,7 @@ impl JoinRequest { kind: metrics::Kind::ABSOLUTE, metrics: [("error_OTAA".into(), 1f64)].iter().cloned().collect(), }, + &metrics::Aggregation::default_aggregations(), ) .await?; diff --git a/chirpstack/src/uplink/stats.rs b/chirpstack/src/uplink/stats.rs index 1f82f4a0..c596548f 100644 --- a/chirpstack/src/uplink/stats.rs +++ b/chirpstack/src/uplink/stats.rs @@ -2,6 +2,7 @@ use std::collections::hash_map::DefaultHasher; use std::collections::HashMap; use std::hash::{Hash, Hasher}; use std::str::FromStr; +use std::time::Duration; use anyhow::{Context, Result}; use chrono::{DateTime, Local, Utc}; @@ -63,6 +64,7 @@ impl Stats { ctx.update_gateway_state().await?; ctx.save_stats().await?; + ctx.save_duty_cycle_stats().await?; ctx.update_gateway_configuration().await?; Ok(()) @@ -156,6 +158,7 @@ impl Stats { metrics::save( &format!("gw:{}", self.gateway.as_ref().unwrap().gateway_id), &m, + &metrics::Aggregation::default_aggregations(), ) .await .context("Save gateway stats")?; @@ -163,6 +166,73 @@ impl Stats { Ok(()) } + async fn save_duty_cycle_stats(&self) -> Result<()> { + trace!("Saving duty-cycle stats"); + + let duty_cycle_stats = match self.stats.duty_cycle_stats.as_ref() { + Some(v) => v, + None => { + // No stats, nothing to do. + return Ok(()); + } + }; + + let window: Duration = duty_cycle_stats + .window + .clone() + .map(|v| v.try_into().unwrap_or_default()) + .unwrap_or_default(); + + let mut m = metrics::Record { + time: match &self.stats.time { + Some(v) => DateTime::try_from(v.clone()) + .map_err(anyhow::Error::msg)? + .into(), + None => Local::now(), + }, + kind: metrics::Kind::COUNTER, + metrics: HashMap::new(), + }; + + for b in &duty_cycle_stats.bands { + let load_max: Duration = b + .load_max + .clone() + .map(|d| d.try_into().unwrap_or_default()) + .unwrap_or_default(); + let load_tracked: Duration = b + .load_tracked + .clone() + .map(|d| d.try_into().unwrap_or_default()) + .unwrap_or_default(); + + let permille = load_max.as_nanos() / (window.as_nanos() / 1000); + let key = format!( + "{}_{}_{}_{}", + b.name, b.frequency_min, b.frequency_max, permille + ); + let dc_max_load_perc_key = format!("max_load_perc_{}", key); + let dc_window_perc_key = format!("window_perc_{}", key); + + let dc_max_load_perc = + load_tracked.as_nanos() as f64 / load_max.as_nanos() as f64 * 100.0; + let dc_window_perc = load_tracked.as_nanos() as f64 / window.as_nanos() as f64 * 100.0; + + m.metrics.insert(dc_max_load_perc_key, dc_max_load_perc); + m.metrics.insert(dc_window_perc_key, dc_window_perc); + } + + metrics::save( + &format!("gw:dc:{}", self.gateway.as_ref().unwrap().gateway_id), + &m, + &[metrics::Aggregation::MINUTE], + ) + .await + .context("Save gateway duty-cycle stats")?; + + Ok(()) + } + async fn update_gateway_configuration(&self) -> Result<()> { trace!("Updating gateway configuration"); diff --git a/ui/package.json b/ui/package.json index f383150e..03488626 100644 --- a/ui/package.json +++ b/ui/package.json @@ -25,6 +25,7 @@ "chartjs-adapter-moment": "^1.0.1", "chartjs-chart-matrix": "^2.0.1", "codemirror": "5.65.3", + "google-palette": "^1.1.1", "history": "^5.3.0", "js-file-download": "^0.4.12", "leaflet": "^1.9.4", diff --git a/ui/src/components/MetricBar.tsx b/ui/src/components/MetricBar.tsx index e769e301..dae17094 100644 --- a/ui/src/components/MetricBar.tsx +++ b/ui/src/components/MetricBar.tsx @@ -3,6 +3,7 @@ import { Card } from "antd"; import { TimeUnit } from "chart.js"; import { Bar } from "react-chartjs-2"; import moment from "moment"; +import palette from "google-palette"; import { Metric, Aggregation } from "@chirpstack/chirpstack-api-grpc-web/common/common_pb"; @@ -19,24 +20,6 @@ function MetricBar(props: IProps) { unit = "month"; } - let backgroundColors = [ - "#8bc34a", - "#ff5722", - "#ff9800", - "#ffc107", - "#ffeb3b", - "#cddc39", - "#4caf50", - "#009688", - "#00bcd4", - "#03a9f4", - "#2196f3", - "#3f51b5", - "#673ab7", - "#9c27b0", - "#e91e63", - ]; - const animation: false = false; const options = { @@ -72,13 +55,13 @@ function MetricBar(props: IProps) { datasets: [], }; - for (let ds of props.metric.getDatasetsList()) { + props.metric.getDatasetsList().forEach((ds, i) => { data.datasets.push({ label: ds.getLabel(), data: ds.getDataList(), - backgroundColor: backgroundColors.shift()!, + backgroundColor: palette("cb-Paired", props.metric.getDatasetsList().length).map((hex: string) => "#" + hex)[i], }); - } + }); return ( diff --git a/ui/src/components/MetricChart.tsx b/ui/src/components/MetricChart.tsx index 45961d7d..a2a81890 100644 --- a/ui/src/components/MetricChart.tsx +++ b/ui/src/components/MetricChart.tsx @@ -3,6 +3,7 @@ import { Card } from "antd"; import { TimeUnit } from "chart.js"; import { Line } from "react-chartjs-2"; import moment from "moment"; +import palette from "google-palette"; import { Metric, Aggregation, MetricKind } from "@chirpstack/chirpstack-api-grpc-web/common/common_pb"; @@ -21,6 +22,9 @@ function MetricChart(props: IProps) { } else if (props.aggregation === Aggregation.MONTH) { unit = "month"; tooltipFormat = "MMM YYYY"; + } else if (props.aggregation === Aggregation.MINUTE) { + unit = "minute"; + tooltipFormat = "LT"; } const animation: false = false; @@ -50,31 +54,37 @@ function MetricChart(props: IProps) { let prevValue = 0; let data = { labels: props.metric.getTimestampsList().map(v => moment(v.toDate()).valueOf()), - datasets: props.metric.getDatasetsList().map(v => { - return { - label: v.getLabel(), - borderColor: "rgba(33, 150, 243, 1)", - backgroundColor: "rgba(0, 0, 0, 0)", - lineTension: 0, - pointBackgroundColor: "rgba(33, 150, 243, 1)", - data: v.getDataList().map(v => { - if (v === 0 && props.zeroToNull) { - return null; - } else { - if (props.metric.getKind() === MetricKind.COUNTER) { - let val = v - prevValue; - prevValue = v; - if (val < 0) { - return 0; - } - return val; + datasets: props.metric + .getDatasetsList() + .sort((a, b) => a.getLabel().localeCompare(b.getLabel())) + .map((v, i) => { + const colors = palette("cb-Paired", props.metric.getDatasetsList().length).map((hex: string) => "#" + hex); + console.log(v.getLabel()); + console.log(colors[i]); + + return { + label: v.getLabel(), + borderColor: colors[i], + pointBackgroundColor: colors[i], + lineTension: 0, + data: v.getDataList().map(v => { + if (v === 0 && props.zeroToNull) { + return null; } else { - return v; + if (props.metric.getKind() === MetricKind.COUNTER) { + let val = v - prevValue; + prevValue = v; + if (val < 0) { + return 0; + } + return val; + } else { + return v; + } } - } - }), - }; - }), + }), + }; + }), }; let name = props.metric.getName(); diff --git a/ui/src/index.css b/ui/src/index.css index 20e61c10..cf52e1a6 100644 --- a/ui/src/index.css +++ b/ui/src/index.css @@ -124,7 +124,7 @@ pre { .ant-drawer { padding-top: 64px; - z-index:1002; + z-index: 1002; } .ant-drawer-body { diff --git a/ui/src/stores/GatewayStore.ts b/ui/src/stores/GatewayStore.ts index 53c28e55..1f41d298 100644 --- a/ui/src/stores/GatewayStore.ts +++ b/ui/src/stores/GatewayStore.ts @@ -11,6 +11,8 @@ import { ListGatewaysResponse, GetGatewayMetricsRequest, GetGatewayMetricsResponse, + GetGatewayDutyCycleMetricsRequest, + GetGatewayDutyCycleMetricsResponse, GenerateGatewayClientCertificateRequest, GenerateGatewayClientCertificateResponse, } from "@chirpstack/chirpstack-api-grpc-web/api/gateway_pb"; @@ -107,6 +109,20 @@ class GatewayStore extends EventEmitter { }); }; + getDutyCycleMetrics = ( + req: GetGatewayDutyCycleMetricsRequest, + callbackFunc: (resp: GetGatewayDutyCycleMetricsResponse) => void, + ) => { + this.client.getDutyCycleMetrics(req, SessionStore.getMetadata(), (err, resp) => { + if (err !== null) { + HandleError(err); + return; + } + + callbackFunc(resp); + }); + }; + generateClientCertificate = ( req: GenerateGatewayClientCertificateRequest, callbackFunc: (resp: GenerateGatewayClientCertificateResponse) => void, diff --git a/ui/src/views/gateways/GatewayDashboard.tsx b/ui/src/views/gateways/GatewayDashboard.tsx index 83cc7c01..ab26f15c 100644 --- a/ui/src/views/gateways/GatewayDashboard.tsx +++ b/ui/src/views/gateways/GatewayDashboard.tsx @@ -8,6 +8,8 @@ import { Gateway, GetGatewayMetricsRequest, GetGatewayMetricsResponse, + GetGatewayDutyCycleMetricsRequest, + GetGatewayDutyCycleMetricsResponse, } from "@chirpstack/chirpstack-api-grpc-web/api/gateway_pb"; import { Aggregation } from "@chirpstack/chirpstack-api-grpc-web/common/common_pb"; @@ -25,6 +27,9 @@ interface IProps { function GatewayDashboard(props: IProps) { const [metricsAggregation] = useState(Aggregation.DAY); const [gatewayMetrics, setGatewayMetrics] = useState(undefined); + const [gatewayDutyCycleMetrics, setGatewayDutyCycleMetrics] = useState< + GetGatewayDutyCycleMetricsResponse | undefined + >(undefined); useEffect(() => { const agg = metricsAggregation; @@ -54,12 +59,29 @@ function GatewayDashboard(props: IProps) { GatewayStore.getMetrics(req, (resp: GetGatewayMetricsResponse) => { setGatewayMetrics(resp); }); + + const dcEnd = moment().subtract(1, "minute"); + let dcEndPb = new Timestamp(); + dcEndPb.fromDate(dcEnd.toDate()); + + const dcStart = dcEnd.subtract(1, "hours"); + let dcStartPb = new Timestamp(); + dcStartPb.fromDate(dcStart.toDate()); + + let dcReq = new GetGatewayDutyCycleMetricsRequest(); + dcReq.setGatewayId(props.gateway.getGatewayId()); + dcReq.setStart(dcStartPb); + dcReq.setEnd(dcEndPb); + + GatewayStore.getDutyCycleMetrics(dcReq, (resp: GetGatewayDutyCycleMetricsResponse) => { + setGatewayDutyCycleMetrics(resp); + }); }, [props, metricsAggregation]); const loc = props.gateway.getLocation()!; const location: [number, number] = [loc.getLatitude(), loc.getLongitude()]; - if (gatewayMetrics === undefined) { + if (gatewayMetrics === undefined || gatewayDutyCycleMetrics === undefined) { return null; } @@ -89,6 +111,17 @@ function GatewayDashboard(props: IProps) { + {gatewayDutyCycleMetrics.getMaxLoadPercentage()!.getDatasetsList().length !== 0 && + gatewayDutyCycleMetrics.getWindowPercentage()!.getDatasetsList().length !== 0 && ( + + + + + + + + + )} diff --git a/ui/tsconfig.json b/ui/tsconfig.json index 9d379a3c..91c4e85b 100644 --- a/ui/tsconfig.json +++ b/ui/tsconfig.json @@ -14,7 +14,8 @@ "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, - "jsx": "react-jsx" + "jsx": "react-jsx", + "typeRoots": ["./types", "./node_modules/@types"] }, "include": ["src"] } diff --git a/ui/types/google-palette/index.d.ts b/ui/types/google-palette/index.d.ts new file mode 100644 index 00000000..92710fbc --- /dev/null +++ b/ui/types/google-palette/index.d.ts @@ -0,0 +1 @@ +declare module "google-palette"; diff --git a/ui/yarn.lock b/ui/yarn.lock index ee3ec114..21b13dbe 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -1256,7 +1256,7 @@ integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== "@chirpstack/chirpstack-api-grpc-web@file:../api/grpc-web": - version "4.7.0" + version "4.8.0-test.2" dependencies: "@types/google-protobuf" "^3.15.12" google-protobuf "^3.21.2" @@ -5433,6 +5433,11 @@ globby@^11.0.4, globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" +google-palette@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/google-palette/-/google-palette-1.1.1.tgz#671b3b932c8393271b67da908ccd7f2c48e84cc7" + integrity sha512-yZiM5oLl8lCZzf06IMOGdDkxqvCMd9HNFcCiOMqWgGGiGzC22vWBVhKJNvykXXbeC0NAElNH97jA/y0bq6TCrA== + google-protobuf@^3.21.2: version "3.21.2" resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.21.2.tgz#4580a2bea8bbb291ee579d1fefb14d6fa3070ea4"