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.
This commit is contained in:
Orne Brocaar 2024-04-17 14:34:11 +01:00
parent 2889da37c2
commit a5ff416fa2
23 changed files with 1012 additions and 201 deletions

View File

@ -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,
},

View File

@ -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",

View File

@ -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 (

View File

@ -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;
}

View File

@ -141,6 +141,9 @@ enum Aggregation {
// Month.
MONTH = 2;
// Minute.
MINUTE = 3;
}
enum MetricKind {

View File

@ -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;
}

View File

@ -141,6 +141,9 @@ enum Aggregation {
// Month.
MONTH = 2;
// Minute.
MINUTE = 3;
}
enum MetricKind {

View File

@ -614,11 +614,180 @@ impl GatewayService for Gateway {
Ok(resp)
}
async fn get_duty_cycle_metrics(
&self,
request: Request<api::GetGatewayDutyCycleMetricsRequest>,
) -> Result<Response<api::GetGatewayDutyCycleMetricsResponse>, 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<Local> = start.into();
let end: DateTime<Local> = 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<String> = 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<Utc> = 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::<f64>()
.map(|v| v / 1_000_000.0)
.unwrap_or(0.0),
s.get(2)
.unwrap_or(&"")
.parse::<f64>()
.map(|v| v / 1_000_000.0)
.unwrap_or(0.0),
s.get(3)
.unwrap_or(&"")
.parse::<f64>()
.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<String> = 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<Utc> = 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::<f64>()
.map(|v| v / 1_000_000.0)
.unwrap_or(0.0),
s.get(2)
.unwrap_or(&"")
.parse::<f64>()
.map(|v| v / 1_000_000.0)
.unwrap_or(0.0),
s.get(3)
.unwrap_or(&"")
.parse::<f64>()
.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<Utc> = 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<Utc> = 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
);
}
}

View File

@ -169,6 +169,7 @@ impl FromProto<MeasurementKind> for api::MeasurementKind {
impl ToProto<common::Aggregation> 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<common::Aggregation> for Aggregation {
impl FromProto<Aggregation> 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,

View File

@ -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<Aggregation> {
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<Aggregation> {
vec![Aggregation::HOUR, Aggregation::DAY, Aggregation::MONTH]
}
fn get_key(name: &str, a: Aggregation, dt: DateTime<Local>) -> 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<Local> = 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<DateTime<Local>> = 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(

View File

@ -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(())
}

View File

@ -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?;

View File

@ -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?;

View File

@ -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");

View File

@ -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",

View File

@ -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 (
<Card title={props.metric.getName()} className="dashboard-chart">

View File

@ -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();

View File

@ -124,7 +124,7 @@ pre {
.ant-drawer {
padding-top: 64px;
z-index:1002;
z-index: 1002;
}
.ant-drawer-body {

View File

@ -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,

View File

@ -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>(Aggregation.DAY);
const [gatewayMetrics, setGatewayMetrics] = useState<GetGatewayMetricsResponse | undefined>(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) {
</Map>
</Col>
</Row>
{gatewayDutyCycleMetrics.getMaxLoadPercentage()!.getDatasetsList().length !== 0 &&
gatewayDutyCycleMetrics.getWindowPercentage()!.getDatasetsList().length !== 0 && (
<Row gutter={24}>
<Col span={12}>
<MetricChart metric={gatewayDutyCycleMetrics.getWindowPercentage()!} aggregation={Aggregation.MINUTE} />
</Col>
<Col span={12}>
<MetricChart metric={gatewayDutyCycleMetrics.getMaxLoadPercentage()!} aggregation={Aggregation.MINUTE} />
</Col>
</Row>
)}
<Row gutter={24}>
<Col span={8}>
<MetricChart metric={gatewayMetrics.getRxPackets()!} aggregation={metricsAggregation} />

View File

@ -14,7 +14,8 @@
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
"jsx": "react-jsx",
"typeRoots": ["./types", "./node_modules/@types"]
},
"include": ["src"]
}

1
ui/types/google-palette/index.d.ts vendored Normal file
View File

@ -0,0 +1 @@
declare module "google-palette";

View File

@ -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"