mirror of
https://github.com/amir20/dozzle.git
synced 2026-08-07 10:14:44 +00:00
fix: sync notification stats across agents (#4525)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -541,6 +541,31 @@ func (c *Client) UpdateNotificationConfig(ctx context.Context, subscriptions []t
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) GetNotificationStats(ctx context.Context) ([]types.SubscriptionStats, error) {
|
||||
resp, err := c.client.GetNotificationStats(ctx, &pb.GetNotificationStatsRequest{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stats := make([]types.SubscriptionStats, len(resp.Stats))
|
||||
for i, s := range resp.Stats {
|
||||
var lastTriggered *time.Time
|
||||
if s.LastTriggeredAt != nil {
|
||||
t := s.LastTriggeredAt.AsTime()
|
||||
if !t.IsZero() {
|
||||
lastTriggered = &t
|
||||
}
|
||||
}
|
||||
stats[i] = types.SubscriptionStats{
|
||||
SubscriptionID: int(s.SubscriptionId),
|
||||
TriggerCount: s.TriggerCount,
|
||||
LastTriggeredAt: lastTriggered,
|
||||
TriggeredContainerIDs: s.TriggeredContainerIds,
|
||||
}
|
||||
}
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
func jsonBytesToOrderedMap(b []byte) *orderedmap.OrderedMap[string, any] {
|
||||
var data *orderedmap.OrderedMap[string, any]
|
||||
reader := bytes.NewReader(b)
|
||||
|
||||
+155
-65
@@ -1358,6 +1358,86 @@ func (*UpdateNotificationConfigResponse) Descriptor() ([]byte, []int) {
|
||||
return file_rpc_proto_rawDescGZIP(), []int{26}
|
||||
}
|
||||
|
||||
type GetNotificationStatsRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *GetNotificationStatsRequest) Reset() {
|
||||
*x = GetNotificationStatsRequest{}
|
||||
mi := &file_rpc_proto_msgTypes[27]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *GetNotificationStatsRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*GetNotificationStatsRequest) ProtoMessage() {}
|
||||
|
||||
func (x *GetNotificationStatsRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_rpc_proto_msgTypes[27]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use GetNotificationStatsRequest.ProtoReflect.Descriptor instead.
|
||||
func (*GetNotificationStatsRequest) Descriptor() ([]byte, []int) {
|
||||
return file_rpc_proto_rawDescGZIP(), []int{27}
|
||||
}
|
||||
|
||||
type GetNotificationStatsResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Stats []*NotificationSubscriptionStats `protobuf:"bytes,1,rep,name=stats,proto3" json:"stats,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *GetNotificationStatsResponse) Reset() {
|
||||
*x = GetNotificationStatsResponse{}
|
||||
mi := &file_rpc_proto_msgTypes[28]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *GetNotificationStatsResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*GetNotificationStatsResponse) ProtoMessage() {}
|
||||
|
||||
func (x *GetNotificationStatsResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_rpc_proto_msgTypes[28]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use GetNotificationStatsResponse.ProtoReflect.Descriptor instead.
|
||||
func (*GetNotificationStatsResponse) Descriptor() ([]byte, []int) {
|
||||
return file_rpc_proto_rawDescGZIP(), []int{28}
|
||||
}
|
||||
|
||||
func (x *GetNotificationStatsResponse) GetStats() []*NotificationSubscriptionStats {
|
||||
if x != nil {
|
||||
return x.Stats
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_rpc_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_rpc_proto_rawDesc = "" +
|
||||
@@ -1437,7 +1517,10 @@ const file_rpc_proto_rawDesc = "" +
|
||||
"\x1fUpdateNotificationConfigRequest\x12H\n" +
|
||||
"\rsubscriptions\x18\x01 \x03(\v2\".protobuf.NotificationSubscriptionR\rsubscriptions\x12B\n" +
|
||||
"\vdispatchers\x18\x02 \x03(\v2 .protobuf.NotificationDispatcherR\vdispatchers\"\"\n" +
|
||||
" UpdateNotificationConfigResponse2\x96\t\n" +
|
||||
" UpdateNotificationConfigResponse\"\x1d\n" +
|
||||
"\x1bGetNotificationStatsRequest\"]\n" +
|
||||
"\x1cGetNotificationStatsResponse\x12=\n" +
|
||||
"\x05stats\x18\x01 \x03(\v2'.protobuf.NotificationSubscriptionStatsR\x05stats2\xff\t\n" +
|
||||
"\fAgentService\x12U\n" +
|
||||
"\x0eListContainers\x12\x1f.protobuf.ListContainersRequest\x1a .protobuf.ListContainersResponse\"\x00\x12R\n" +
|
||||
"\rFindContainer\x12\x1e.protobuf.FindContainerRequest\x1a\x1f.protobuf.FindContainerResponse\"\x00\x12K\n" +
|
||||
@@ -1452,7 +1535,8 @@ const file_rpc_proto_rawDesc = "" +
|
||||
"\x0fContainerAction\x12 .protobuf.ContainerActionRequest\x1a!.protobuf.ContainerActionResponse\"\x00\x12V\n" +
|
||||
"\rContainerExec\x12\x1e.protobuf.ContainerExecRequest\x1a\x1f.protobuf.ContainerExecResponse\"\x00(\x010\x01\x12\\\n" +
|
||||
"\x0fContainerAttach\x12 .protobuf.ContainerAttachRequest\x1a!.protobuf.ContainerAttachResponse\"\x00(\x010\x01\x12s\n" +
|
||||
"\x18UpdateNotificationConfig\x12).protobuf.UpdateNotificationConfigRequest\x1a*.protobuf.UpdateNotificationConfigResponse\"\x00B\x13Z\x11internal/agent/pbb\x06proto3"
|
||||
"\x18UpdateNotificationConfig\x12).protobuf.UpdateNotificationConfigRequest\x1a*.protobuf.UpdateNotificationConfigResponse\"\x00\x12g\n" +
|
||||
"\x14GetNotificationStats\x12%.protobuf.GetNotificationStatsRequest\x1a&.protobuf.GetNotificationStatsResponse\"\x00B\x13Z\x11internal/agent/pbb\x06proto3"
|
||||
|
||||
var (
|
||||
file_rpc_proto_rawDescOnce sync.Once
|
||||
@@ -1466,7 +1550,7 @@ func file_rpc_proto_rawDescGZIP() []byte {
|
||||
return file_rpc_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_rpc_proto_msgTypes = make([]protoimpl.MessageInfo, 29)
|
||||
var file_rpc_proto_msgTypes = make([]protoimpl.MessageInfo, 31)
|
||||
var file_rpc_proto_goTypes = []any{
|
||||
(*ListContainersRequest)(nil), // 0: protobuf.ListContainersRequest
|
||||
(*RepeatedString)(nil), // 1: protobuf.RepeatedString
|
||||
@@ -1495,71 +1579,77 @@ var file_rpc_proto_goTypes = []any{
|
||||
(*ContainerAttachResponse)(nil), // 24: protobuf.ContainerAttachResponse
|
||||
(*UpdateNotificationConfigRequest)(nil), // 25: protobuf.UpdateNotificationConfigRequest
|
||||
(*UpdateNotificationConfigResponse)(nil), // 26: protobuf.UpdateNotificationConfigResponse
|
||||
nil, // 27: protobuf.ListContainersRequest.FilterEntry
|
||||
nil, // 28: protobuf.FindContainerRequest.FilterEntry
|
||||
(*Container)(nil), // 29: protobuf.Container
|
||||
(*timestamppb.Timestamp)(nil), // 30: google.protobuf.Timestamp
|
||||
(*LogEvent)(nil), // 31: protobuf.LogEvent
|
||||
(*ContainerEvent)(nil), // 32: protobuf.ContainerEvent
|
||||
(*ContainerStat)(nil), // 33: protobuf.ContainerStat
|
||||
(*Host)(nil), // 34: protobuf.Host
|
||||
(ContainerAction)(0), // 35: protobuf.ContainerAction
|
||||
(*NotificationSubscription)(nil), // 36: protobuf.NotificationSubscription
|
||||
(*NotificationDispatcher)(nil), // 37: protobuf.NotificationDispatcher
|
||||
(*GetNotificationStatsRequest)(nil), // 27: protobuf.GetNotificationStatsRequest
|
||||
(*GetNotificationStatsResponse)(nil), // 28: protobuf.GetNotificationStatsResponse
|
||||
nil, // 29: protobuf.ListContainersRequest.FilterEntry
|
||||
nil, // 30: protobuf.FindContainerRequest.FilterEntry
|
||||
(*Container)(nil), // 31: protobuf.Container
|
||||
(*timestamppb.Timestamp)(nil), // 32: google.protobuf.Timestamp
|
||||
(*LogEvent)(nil), // 33: protobuf.LogEvent
|
||||
(*ContainerEvent)(nil), // 34: protobuf.ContainerEvent
|
||||
(*ContainerStat)(nil), // 35: protobuf.ContainerStat
|
||||
(*Host)(nil), // 36: protobuf.Host
|
||||
(ContainerAction)(0), // 37: protobuf.ContainerAction
|
||||
(*NotificationSubscription)(nil), // 38: protobuf.NotificationSubscription
|
||||
(*NotificationDispatcher)(nil), // 39: protobuf.NotificationDispatcher
|
||||
(*NotificationSubscriptionStats)(nil), // 40: protobuf.NotificationSubscriptionStats
|
||||
}
|
||||
var file_rpc_proto_depIdxs = []int32{
|
||||
27, // 0: protobuf.ListContainersRequest.filter:type_name -> protobuf.ListContainersRequest.FilterEntry
|
||||
29, // 1: protobuf.ListContainersResponse.containers:type_name -> protobuf.Container
|
||||
28, // 2: protobuf.FindContainerRequest.filter:type_name -> protobuf.FindContainerRequest.FilterEntry
|
||||
29, // 3: protobuf.FindContainerResponse.container:type_name -> protobuf.Container
|
||||
30, // 4: protobuf.StreamLogsRequest.since:type_name -> google.protobuf.Timestamp
|
||||
31, // 5: protobuf.StreamLogsResponse.event:type_name -> protobuf.LogEvent
|
||||
30, // 6: protobuf.LogsBetweenDatesRequest.since:type_name -> google.protobuf.Timestamp
|
||||
30, // 7: protobuf.LogsBetweenDatesRequest.until:type_name -> google.protobuf.Timestamp
|
||||
30, // 8: protobuf.StreamRawBytesRequest.since:type_name -> google.protobuf.Timestamp
|
||||
30, // 9: protobuf.StreamRawBytesRequest.until:type_name -> google.protobuf.Timestamp
|
||||
32, // 10: protobuf.StreamEventsResponse.event:type_name -> protobuf.ContainerEvent
|
||||
33, // 11: protobuf.StreamStatsResponse.stat:type_name -> protobuf.ContainerStat
|
||||
34, // 12: protobuf.HostInfoResponse.host:type_name -> protobuf.Host
|
||||
29, // 13: protobuf.StreamContainerStartedResponse.container:type_name -> protobuf.Container
|
||||
35, // 14: protobuf.ContainerActionRequest.action:type_name -> protobuf.ContainerAction
|
||||
29, // 0: protobuf.ListContainersRequest.filter:type_name -> protobuf.ListContainersRequest.FilterEntry
|
||||
31, // 1: protobuf.ListContainersResponse.containers:type_name -> protobuf.Container
|
||||
30, // 2: protobuf.FindContainerRequest.filter:type_name -> protobuf.FindContainerRequest.FilterEntry
|
||||
31, // 3: protobuf.FindContainerResponse.container:type_name -> protobuf.Container
|
||||
32, // 4: protobuf.StreamLogsRequest.since:type_name -> google.protobuf.Timestamp
|
||||
33, // 5: protobuf.StreamLogsResponse.event:type_name -> protobuf.LogEvent
|
||||
32, // 6: protobuf.LogsBetweenDatesRequest.since:type_name -> google.protobuf.Timestamp
|
||||
32, // 7: protobuf.LogsBetweenDatesRequest.until:type_name -> google.protobuf.Timestamp
|
||||
32, // 8: protobuf.StreamRawBytesRequest.since:type_name -> google.protobuf.Timestamp
|
||||
32, // 9: protobuf.StreamRawBytesRequest.until:type_name -> google.protobuf.Timestamp
|
||||
34, // 10: protobuf.StreamEventsResponse.event:type_name -> protobuf.ContainerEvent
|
||||
35, // 11: protobuf.StreamStatsResponse.stat:type_name -> protobuf.ContainerStat
|
||||
36, // 12: protobuf.HostInfoResponse.host:type_name -> protobuf.Host
|
||||
31, // 13: protobuf.StreamContainerStartedResponse.container:type_name -> protobuf.Container
|
||||
37, // 14: protobuf.ContainerActionRequest.action:type_name -> protobuf.ContainerAction
|
||||
21, // 15: protobuf.ContainerExecRequest.resize:type_name -> protobuf.ResizePayload
|
||||
21, // 16: protobuf.ContainerAttachRequest.resize:type_name -> protobuf.ResizePayload
|
||||
36, // 17: protobuf.UpdateNotificationConfigRequest.subscriptions:type_name -> protobuf.NotificationSubscription
|
||||
37, // 18: protobuf.UpdateNotificationConfigRequest.dispatchers:type_name -> protobuf.NotificationDispatcher
|
||||
1, // 19: protobuf.ListContainersRequest.FilterEntry.value:type_name -> protobuf.RepeatedString
|
||||
1, // 20: protobuf.FindContainerRequest.FilterEntry.value:type_name -> protobuf.RepeatedString
|
||||
0, // 21: protobuf.AgentService.ListContainers:input_type -> protobuf.ListContainersRequest
|
||||
3, // 22: protobuf.AgentService.FindContainer:input_type -> protobuf.FindContainerRequest
|
||||
5, // 23: protobuf.AgentService.StreamLogs:input_type -> protobuf.StreamLogsRequest
|
||||
7, // 24: protobuf.AgentService.LogsBetweenDates:input_type -> protobuf.LogsBetweenDatesRequest
|
||||
8, // 25: protobuf.AgentService.StreamRawBytes:input_type -> protobuf.StreamRawBytesRequest
|
||||
10, // 26: protobuf.AgentService.StreamEvents:input_type -> protobuf.StreamEventsRequest
|
||||
12, // 27: protobuf.AgentService.StreamStats:input_type -> protobuf.StreamStatsRequest
|
||||
16, // 28: protobuf.AgentService.StreamContainerStarted:input_type -> protobuf.StreamContainerStartedRequest
|
||||
14, // 29: protobuf.AgentService.HostInfo:input_type -> protobuf.HostInfoRequest
|
||||
18, // 30: protobuf.AgentService.ContainerAction:input_type -> protobuf.ContainerActionRequest
|
||||
20, // 31: protobuf.AgentService.ContainerExec:input_type -> protobuf.ContainerExecRequest
|
||||
23, // 32: protobuf.AgentService.ContainerAttach:input_type -> protobuf.ContainerAttachRequest
|
||||
25, // 33: protobuf.AgentService.UpdateNotificationConfig:input_type -> protobuf.UpdateNotificationConfigRequest
|
||||
2, // 34: protobuf.AgentService.ListContainers:output_type -> protobuf.ListContainersResponse
|
||||
4, // 35: protobuf.AgentService.FindContainer:output_type -> protobuf.FindContainerResponse
|
||||
6, // 36: protobuf.AgentService.StreamLogs:output_type -> protobuf.StreamLogsResponse
|
||||
6, // 37: protobuf.AgentService.LogsBetweenDates:output_type -> protobuf.StreamLogsResponse
|
||||
9, // 38: protobuf.AgentService.StreamRawBytes:output_type -> protobuf.StreamRawBytesResponse
|
||||
11, // 39: protobuf.AgentService.StreamEvents:output_type -> protobuf.StreamEventsResponse
|
||||
13, // 40: protobuf.AgentService.StreamStats:output_type -> protobuf.StreamStatsResponse
|
||||
17, // 41: protobuf.AgentService.StreamContainerStarted:output_type -> protobuf.StreamContainerStartedResponse
|
||||
15, // 42: protobuf.AgentService.HostInfo:output_type -> protobuf.HostInfoResponse
|
||||
19, // 43: protobuf.AgentService.ContainerAction:output_type -> protobuf.ContainerActionResponse
|
||||
22, // 44: protobuf.AgentService.ContainerExec:output_type -> protobuf.ContainerExecResponse
|
||||
24, // 45: protobuf.AgentService.ContainerAttach:output_type -> protobuf.ContainerAttachResponse
|
||||
26, // 46: protobuf.AgentService.UpdateNotificationConfig:output_type -> protobuf.UpdateNotificationConfigResponse
|
||||
34, // [34:47] is the sub-list for method output_type
|
||||
21, // [21:34] is the sub-list for method input_type
|
||||
21, // [21:21] is the sub-list for extension type_name
|
||||
21, // [21:21] is the sub-list for extension extendee
|
||||
0, // [0:21] is the sub-list for field type_name
|
||||
38, // 17: protobuf.UpdateNotificationConfigRequest.subscriptions:type_name -> protobuf.NotificationSubscription
|
||||
39, // 18: protobuf.UpdateNotificationConfigRequest.dispatchers:type_name -> protobuf.NotificationDispatcher
|
||||
40, // 19: protobuf.GetNotificationStatsResponse.stats:type_name -> protobuf.NotificationSubscriptionStats
|
||||
1, // 20: protobuf.ListContainersRequest.FilterEntry.value:type_name -> protobuf.RepeatedString
|
||||
1, // 21: protobuf.FindContainerRequest.FilterEntry.value:type_name -> protobuf.RepeatedString
|
||||
0, // 22: protobuf.AgentService.ListContainers:input_type -> protobuf.ListContainersRequest
|
||||
3, // 23: protobuf.AgentService.FindContainer:input_type -> protobuf.FindContainerRequest
|
||||
5, // 24: protobuf.AgentService.StreamLogs:input_type -> protobuf.StreamLogsRequest
|
||||
7, // 25: protobuf.AgentService.LogsBetweenDates:input_type -> protobuf.LogsBetweenDatesRequest
|
||||
8, // 26: protobuf.AgentService.StreamRawBytes:input_type -> protobuf.StreamRawBytesRequest
|
||||
10, // 27: protobuf.AgentService.StreamEvents:input_type -> protobuf.StreamEventsRequest
|
||||
12, // 28: protobuf.AgentService.StreamStats:input_type -> protobuf.StreamStatsRequest
|
||||
16, // 29: protobuf.AgentService.StreamContainerStarted:input_type -> protobuf.StreamContainerStartedRequest
|
||||
14, // 30: protobuf.AgentService.HostInfo:input_type -> protobuf.HostInfoRequest
|
||||
18, // 31: protobuf.AgentService.ContainerAction:input_type -> protobuf.ContainerActionRequest
|
||||
20, // 32: protobuf.AgentService.ContainerExec:input_type -> protobuf.ContainerExecRequest
|
||||
23, // 33: protobuf.AgentService.ContainerAttach:input_type -> protobuf.ContainerAttachRequest
|
||||
25, // 34: protobuf.AgentService.UpdateNotificationConfig:input_type -> protobuf.UpdateNotificationConfigRequest
|
||||
27, // 35: protobuf.AgentService.GetNotificationStats:input_type -> protobuf.GetNotificationStatsRequest
|
||||
2, // 36: protobuf.AgentService.ListContainers:output_type -> protobuf.ListContainersResponse
|
||||
4, // 37: protobuf.AgentService.FindContainer:output_type -> protobuf.FindContainerResponse
|
||||
6, // 38: protobuf.AgentService.StreamLogs:output_type -> protobuf.StreamLogsResponse
|
||||
6, // 39: protobuf.AgentService.LogsBetweenDates:output_type -> protobuf.StreamLogsResponse
|
||||
9, // 40: protobuf.AgentService.StreamRawBytes:output_type -> protobuf.StreamRawBytesResponse
|
||||
11, // 41: protobuf.AgentService.StreamEvents:output_type -> protobuf.StreamEventsResponse
|
||||
13, // 42: protobuf.AgentService.StreamStats:output_type -> protobuf.StreamStatsResponse
|
||||
17, // 43: protobuf.AgentService.StreamContainerStarted:output_type -> protobuf.StreamContainerStartedResponse
|
||||
15, // 44: protobuf.AgentService.HostInfo:output_type -> protobuf.HostInfoResponse
|
||||
19, // 45: protobuf.AgentService.ContainerAction:output_type -> protobuf.ContainerActionResponse
|
||||
22, // 46: protobuf.AgentService.ContainerExec:output_type -> protobuf.ContainerExecResponse
|
||||
24, // 47: protobuf.AgentService.ContainerAttach:output_type -> protobuf.ContainerAttachResponse
|
||||
26, // 48: protobuf.AgentService.UpdateNotificationConfig:output_type -> protobuf.UpdateNotificationConfigResponse
|
||||
28, // 49: protobuf.AgentService.GetNotificationStats:output_type -> protobuf.GetNotificationStatsResponse
|
||||
36, // [36:50] is the sub-list for method output_type
|
||||
22, // [22:36] is the sub-list for method input_type
|
||||
22, // [22:22] is the sub-list for extension type_name
|
||||
22, // [22:22] is the sub-list for extension extendee
|
||||
0, // [0:22] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_rpc_proto_init() }
|
||||
@@ -1582,7 +1672,7 @@ func file_rpc_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_rpc_proto_rawDesc), len(file_rpc_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 29,
|
||||
NumMessages: 31,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
|
||||
@@ -32,6 +32,7 @@ const (
|
||||
AgentService_ContainerExec_FullMethodName = "/protobuf.AgentService/ContainerExec"
|
||||
AgentService_ContainerAttach_FullMethodName = "/protobuf.AgentService/ContainerAttach"
|
||||
AgentService_UpdateNotificationConfig_FullMethodName = "/protobuf.AgentService/UpdateNotificationConfig"
|
||||
AgentService_GetNotificationStats_FullMethodName = "/protobuf.AgentService/GetNotificationStats"
|
||||
)
|
||||
|
||||
// AgentServiceClient is the client API for AgentService service.
|
||||
@@ -51,6 +52,7 @@ type AgentServiceClient interface {
|
||||
ContainerExec(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[ContainerExecRequest, ContainerExecResponse], error)
|
||||
ContainerAttach(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[ContainerAttachRequest, ContainerAttachResponse], error)
|
||||
UpdateNotificationConfig(ctx context.Context, in *UpdateNotificationConfigRequest, opts ...grpc.CallOption) (*UpdateNotificationConfigResponse, error)
|
||||
GetNotificationStats(ctx context.Context, in *GetNotificationStatsRequest, opts ...grpc.CallOption) (*GetNotificationStatsResponse, error)
|
||||
}
|
||||
|
||||
type agentServiceClient struct {
|
||||
@@ -251,6 +253,16 @@ func (c *agentServiceClient) UpdateNotificationConfig(ctx context.Context, in *U
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *agentServiceClient) GetNotificationStats(ctx context.Context, in *GetNotificationStatsRequest, opts ...grpc.CallOption) (*GetNotificationStatsResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(GetNotificationStatsResponse)
|
||||
err := c.cc.Invoke(ctx, AgentService_GetNotificationStats_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// AgentServiceServer is the server API for AgentService service.
|
||||
// All implementations must embed UnimplementedAgentServiceServer
|
||||
// for forward compatibility.
|
||||
@@ -268,6 +280,7 @@ type AgentServiceServer interface {
|
||||
ContainerExec(grpc.BidiStreamingServer[ContainerExecRequest, ContainerExecResponse]) error
|
||||
ContainerAttach(grpc.BidiStreamingServer[ContainerAttachRequest, ContainerAttachResponse]) error
|
||||
UpdateNotificationConfig(context.Context, *UpdateNotificationConfigRequest) (*UpdateNotificationConfigResponse, error)
|
||||
GetNotificationStats(context.Context, *GetNotificationStatsRequest) (*GetNotificationStatsResponse, error)
|
||||
mustEmbedUnimplementedAgentServiceServer()
|
||||
}
|
||||
|
||||
@@ -317,6 +330,9 @@ func (UnimplementedAgentServiceServer) ContainerAttach(grpc.BidiStreamingServer[
|
||||
func (UnimplementedAgentServiceServer) UpdateNotificationConfig(context.Context, *UpdateNotificationConfigRequest) (*UpdateNotificationConfigResponse, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method UpdateNotificationConfig not implemented")
|
||||
}
|
||||
func (UnimplementedAgentServiceServer) GetNotificationStats(context.Context, *GetNotificationStatsRequest) (*GetNotificationStatsResponse, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method GetNotificationStats not implemented")
|
||||
}
|
||||
func (UnimplementedAgentServiceServer) mustEmbedUnimplementedAgentServiceServer() {}
|
||||
func (UnimplementedAgentServiceServer) testEmbeddedByValue() {}
|
||||
|
||||
@@ -508,6 +524,24 @@ func _AgentService_UpdateNotificationConfig_Handler(srv interface{}, ctx context
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _AgentService_GetNotificationStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(GetNotificationStatsRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(AgentServiceServer).GetNotificationStats(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: AgentService_GetNotificationStats_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(AgentServiceServer).GetNotificationStats(ctx, req.(*GetNotificationStatsRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// AgentService_ServiceDesc is the grpc.ServiceDesc for AgentService service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
@@ -535,6 +569,10 @@ var AgentService_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "UpdateNotificationConfig",
|
||||
Handler: _AgentService_UpdateNotificationConfig_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetNotificationStats",
|
||||
Handler: _AgentService_GetNotificationStats_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{
|
||||
{
|
||||
|
||||
+109
-34
@@ -1012,6 +1012,74 @@ func (x *NotificationDispatcher) GetHeaders() map[string]string {
|
||||
return nil
|
||||
}
|
||||
|
||||
type NotificationSubscriptionStats struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
SubscriptionId int32 `protobuf:"varint,1,opt,name=subscriptionId,proto3" json:"subscriptionId,omitempty"`
|
||||
TriggerCount int64 `protobuf:"varint,2,opt,name=triggerCount,proto3" json:"triggerCount,omitempty"`
|
||||
LastTriggeredAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=lastTriggeredAt,proto3" json:"lastTriggeredAt,omitempty"`
|
||||
TriggeredContainerIds []string `protobuf:"bytes,4,rep,name=triggeredContainerIds,proto3" json:"triggeredContainerIds,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *NotificationSubscriptionStats) Reset() {
|
||||
*x = NotificationSubscriptionStats{}
|
||||
mi := &file_types_proto_msgTypes[11]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *NotificationSubscriptionStats) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*NotificationSubscriptionStats) ProtoMessage() {}
|
||||
|
||||
func (x *NotificationSubscriptionStats) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_types_proto_msgTypes[11]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use NotificationSubscriptionStats.ProtoReflect.Descriptor instead.
|
||||
func (*NotificationSubscriptionStats) Descriptor() ([]byte, []int) {
|
||||
return file_types_proto_rawDescGZIP(), []int{11}
|
||||
}
|
||||
|
||||
func (x *NotificationSubscriptionStats) GetSubscriptionId() int32 {
|
||||
if x != nil {
|
||||
return x.SubscriptionId
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *NotificationSubscriptionStats) GetTriggerCount() int64 {
|
||||
if x != nil {
|
||||
return x.TriggerCount
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *NotificationSubscriptionStats) GetLastTriggeredAt() *timestamppb.Timestamp {
|
||||
if x != nil {
|
||||
return x.LastTriggeredAt
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *NotificationSubscriptionStats) GetTriggeredContainerIds() []string {
|
||||
if x != nil {
|
||||
return x.TriggeredContainerIds
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_types_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_types_proto_rawDesc = "" +
|
||||
@@ -1110,7 +1178,12 @@ const file_types_proto_rawDesc = "" +
|
||||
"\aheaders\x18\x06 \x03(\v2-.protobuf.NotificationDispatcher.HeadersEntryR\aheaders\x1a:\n" +
|
||||
"\fHeadersEntry\x12\x10\n" +
|
||||
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
||||
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01*3\n" +
|
||||
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xe7\x01\n" +
|
||||
"\x1dNotificationSubscriptionStats\x12&\n" +
|
||||
"\x0esubscriptionId\x18\x01 \x01(\x05R\x0esubscriptionId\x12\"\n" +
|
||||
"\ftriggerCount\x18\x02 \x01(\x03R\ftriggerCount\x12D\n" +
|
||||
"\x0flastTriggeredAt\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\x0flastTriggeredAt\x124\n" +
|
||||
"\x15triggeredContainerIds\x18\x04 \x03(\tR\x15triggeredContainerIds*3\n" +
|
||||
"\x0fContainerAction\x12\t\n" +
|
||||
"\x05Start\x10\x00\x12\b\n" +
|
||||
"\x04Stop\x10\x01\x12\v\n" +
|
||||
@@ -1129,43 +1202,45 @@ func file_types_proto_rawDescGZIP() []byte {
|
||||
}
|
||||
|
||||
var file_types_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||
var file_types_proto_msgTypes = make([]protoimpl.MessageInfo, 14)
|
||||
var file_types_proto_msgTypes = make([]protoimpl.MessageInfo, 15)
|
||||
var file_types_proto_goTypes = []any{
|
||||
(ContainerAction)(0), // 0: protobuf.ContainerAction
|
||||
(*Container)(nil), // 1: protobuf.Container
|
||||
(*ContainerStat)(nil), // 2: protobuf.ContainerStat
|
||||
(*LogFragment)(nil), // 3: protobuf.LogFragment
|
||||
(*LogEvent)(nil), // 4: protobuf.LogEvent
|
||||
(*SingleMessage)(nil), // 5: protobuf.SingleMessage
|
||||
(*GroupMessage)(nil), // 6: protobuf.GroupMessage
|
||||
(*ComplexMessage)(nil), // 7: protobuf.ComplexMessage
|
||||
(*ContainerEvent)(nil), // 8: protobuf.ContainerEvent
|
||||
(*Host)(nil), // 9: protobuf.Host
|
||||
(*NotificationSubscription)(nil), // 10: protobuf.NotificationSubscription
|
||||
(*NotificationDispatcher)(nil), // 11: protobuf.NotificationDispatcher
|
||||
nil, // 12: protobuf.Container.LabelsEntry
|
||||
nil, // 13: protobuf.Host.LabelsEntry
|
||||
nil, // 14: protobuf.NotificationDispatcher.HeadersEntry
|
||||
(*timestamppb.Timestamp)(nil), // 15: google.protobuf.Timestamp
|
||||
(*anypb.Any)(nil), // 16: google.protobuf.Any
|
||||
(ContainerAction)(0), // 0: protobuf.ContainerAction
|
||||
(*Container)(nil), // 1: protobuf.Container
|
||||
(*ContainerStat)(nil), // 2: protobuf.ContainerStat
|
||||
(*LogFragment)(nil), // 3: protobuf.LogFragment
|
||||
(*LogEvent)(nil), // 4: protobuf.LogEvent
|
||||
(*SingleMessage)(nil), // 5: protobuf.SingleMessage
|
||||
(*GroupMessage)(nil), // 6: protobuf.GroupMessage
|
||||
(*ComplexMessage)(nil), // 7: protobuf.ComplexMessage
|
||||
(*ContainerEvent)(nil), // 8: protobuf.ContainerEvent
|
||||
(*Host)(nil), // 9: protobuf.Host
|
||||
(*NotificationSubscription)(nil), // 10: protobuf.NotificationSubscription
|
||||
(*NotificationDispatcher)(nil), // 11: protobuf.NotificationDispatcher
|
||||
(*NotificationSubscriptionStats)(nil), // 12: protobuf.NotificationSubscriptionStats
|
||||
nil, // 13: protobuf.Container.LabelsEntry
|
||||
nil, // 14: protobuf.Host.LabelsEntry
|
||||
nil, // 15: protobuf.NotificationDispatcher.HeadersEntry
|
||||
(*timestamppb.Timestamp)(nil), // 16: google.protobuf.Timestamp
|
||||
(*anypb.Any)(nil), // 17: google.protobuf.Any
|
||||
}
|
||||
var file_types_proto_depIdxs = []int32{
|
||||
15, // 0: protobuf.Container.created:type_name -> google.protobuf.Timestamp
|
||||
15, // 1: protobuf.Container.started:type_name -> google.protobuf.Timestamp
|
||||
12, // 2: protobuf.Container.labels:type_name -> protobuf.Container.LabelsEntry
|
||||
16, // 0: protobuf.Container.created:type_name -> google.protobuf.Timestamp
|
||||
16, // 1: protobuf.Container.started:type_name -> google.protobuf.Timestamp
|
||||
13, // 2: protobuf.Container.labels:type_name -> protobuf.Container.LabelsEntry
|
||||
2, // 3: protobuf.Container.stats:type_name -> protobuf.ContainerStat
|
||||
15, // 4: protobuf.Container.finished:type_name -> google.protobuf.Timestamp
|
||||
16, // 5: protobuf.LogEvent.message:type_name -> google.protobuf.Any
|
||||
15, // 6: protobuf.LogEvent.timestamp:type_name -> google.protobuf.Timestamp
|
||||
16, // 4: protobuf.Container.finished:type_name -> google.protobuf.Timestamp
|
||||
17, // 5: protobuf.LogEvent.message:type_name -> google.protobuf.Any
|
||||
16, // 6: protobuf.LogEvent.timestamp:type_name -> google.protobuf.Timestamp
|
||||
3, // 7: protobuf.GroupMessage.fragments:type_name -> protobuf.LogFragment
|
||||
15, // 8: protobuf.ContainerEvent.timestamp:type_name -> google.protobuf.Timestamp
|
||||
13, // 9: protobuf.Host.labels:type_name -> protobuf.Host.LabelsEntry
|
||||
14, // 10: protobuf.NotificationDispatcher.headers:type_name -> protobuf.NotificationDispatcher.HeadersEntry
|
||||
11, // [11:11] is the sub-list for method output_type
|
||||
11, // [11:11] is the sub-list for method input_type
|
||||
11, // [11:11] is the sub-list for extension type_name
|
||||
11, // [11:11] is the sub-list for extension extendee
|
||||
0, // [0:11] is the sub-list for field type_name
|
||||
16, // 8: protobuf.ContainerEvent.timestamp:type_name -> google.protobuf.Timestamp
|
||||
14, // 9: protobuf.Host.labels:type_name -> protobuf.Host.LabelsEntry
|
||||
15, // 10: protobuf.NotificationDispatcher.headers:type_name -> protobuf.NotificationDispatcher.HeadersEntry
|
||||
16, // 11: protobuf.NotificationSubscriptionStats.lastTriggeredAt:type_name -> google.protobuf.Timestamp
|
||||
12, // [12:12] is the sub-list for method output_type
|
||||
12, // [12:12] is the sub-list for method input_type
|
||||
12, // [12:12] is the sub-list for extension type_name
|
||||
12, // [12:12] is the sub-list for extension extendee
|
||||
0, // [0:12] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_types_proto_init() }
|
||||
@@ -1179,7 +1254,7 @@ func file_types_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_types_proto_rawDesc), len(file_types_proto_rawDesc)),
|
||||
NumEnums: 1,
|
||||
NumMessages: 14,
|
||||
NumMessages: 15,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
|
||||
@@ -30,6 +30,7 @@ import (
|
||||
// NotificationConfigHandler handles notification config updates received from the main server
|
||||
type NotificationConfigHandler interface {
|
||||
HandleNotificationConfig(subscriptions []types.SubscriptionConfig, dispatchers []types.DispatcherConfig) error
|
||||
GetNotificationStats() []types.SubscriptionStats
|
||||
}
|
||||
|
||||
// ClientService is the interface for container operations used by the agent server
|
||||
@@ -443,6 +444,29 @@ func (s *server) UpdateNotificationConfig(ctx context.Context, req *pb.UpdateNot
|
||||
return &pb.UpdateNotificationConfigResponse{}, nil
|
||||
}
|
||||
|
||||
func (s *server) GetNotificationStats(ctx context.Context, req *pb.GetNotificationStatsRequest) (*pb.GetNotificationStatsResponse, error) {
|
||||
if s.notificationConfigHandler == nil {
|
||||
return &pb.GetNotificationStatsResponse{}, nil
|
||||
}
|
||||
|
||||
stats := s.notificationConfigHandler.GetNotificationStats()
|
||||
|
||||
pbStats := make([]*pb.NotificationSubscriptionStats, len(stats))
|
||||
for i, s := range stats {
|
||||
pbStat := &pb.NotificationSubscriptionStats{
|
||||
SubscriptionId: int32(s.SubscriptionID),
|
||||
TriggerCount: s.TriggerCount,
|
||||
TriggeredContainerIds: s.TriggeredContainerIDs,
|
||||
}
|
||||
if s.LastTriggeredAt != nil {
|
||||
pbStat.LastTriggeredAt = timestamppb.New(*s.LastTriggeredAt)
|
||||
}
|
||||
pbStats[i] = pbStat
|
||||
}
|
||||
|
||||
return &pb.GetNotificationStatsResponse{Stats: pbStats}, nil
|
||||
}
|
||||
|
||||
func NewServer(service ClientService, certificates tls.Certificate, dozzleVersion string, notificationHandler NotificationConfigHandler) (*grpc.Server, error) {
|
||||
caCertPool := x509.NewCertPool()
|
||||
c, err := x509.ParseCertificate(certificates.Certificate[0])
|
||||
|
||||
@@ -72,8 +72,25 @@ func (m *Manager) LoadConfig(r io.Reader) error {
|
||||
// HandleNotificationConfig implements agent.NotificationConfigHandler interface
|
||||
// It atomically replaces all subscriptions and dispatchers with new state from the main server
|
||||
func (m *Manager) HandleNotificationConfig(subscriptions []types.SubscriptionConfig, dispatchers []types.DispatcherConfig) error {
|
||||
// Clear existing state
|
||||
m.subscriptions.Clear()
|
||||
// Snapshot existing subscriptions to preserve runtime stats
|
||||
existing := make(map[int]*Subscription)
|
||||
m.subscriptions.Range(func(id int, sub *Subscription) bool {
|
||||
existing[id] = sub
|
||||
return true
|
||||
})
|
||||
|
||||
// Build set of incoming IDs and remove stale subscriptions
|
||||
incomingIDs := make(map[int]struct{}, len(subscriptions))
|
||||
for _, sub := range subscriptions {
|
||||
incomingIDs[sub.ID] = struct{}{}
|
||||
}
|
||||
for id := range existing {
|
||||
if _, ok := incomingIDs[id]; !ok {
|
||||
m.subscriptions.Delete(id)
|
||||
}
|
||||
}
|
||||
|
||||
// Clear dispatchers (no stats to preserve)
|
||||
m.dispatchers.Clear()
|
||||
|
||||
// Find max IDs to initialize counters
|
||||
@@ -91,7 +108,7 @@ func (m *Manager) HandleNotificationConfig(subscriptions []types.SubscriptionCon
|
||||
m.subscriptionCounter.Store(int32(maxSubID))
|
||||
m.dispatcherCounter.Store(int32(maxDispatcherID))
|
||||
|
||||
// Load subscriptions (convert from types.SubscriptionConfig to Subscription)
|
||||
// Load subscriptions, preserving runtime stats from existing ones
|
||||
for _, sub := range subscriptions {
|
||||
s := &Subscription{
|
||||
ID: sub.ID,
|
||||
@@ -104,6 +121,32 @@ func (m *Manager) HandleNotificationConfig(subscriptions []types.SubscriptionCon
|
||||
Cooldown: sub.Cooldown,
|
||||
SampleWindow: sub.SampleWindow,
|
||||
}
|
||||
|
||||
if old, ok := existing[sub.ID]; ok {
|
||||
s.TriggerCount.Store(old.TriggerCount.Load())
|
||||
s.LastTriggeredAt.Store(old.LastTriggeredAt.Load())
|
||||
|
||||
// Clone TriggeredContainerIDs to avoid sharing with old subscription
|
||||
s.TriggeredContainerIDs = xsync.NewMap[string, struct{}]()
|
||||
if old.TriggeredContainerIDs != nil {
|
||||
old.TriggeredContainerIDs.Range(func(id string, v struct{}) bool {
|
||||
s.TriggeredContainerIDs.Store(id, v)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// Clone MetricCooldowns to avoid sharing with old subscription
|
||||
s.MetricCooldowns = xsync.NewMap[string, time.Time]()
|
||||
if old.MetricCooldowns != nil {
|
||||
old.MetricCooldowns.Range(func(id string, t time.Time) bool {
|
||||
s.MetricCooldowns.Store(id, t)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// MetricSampleBuffers: start fresh since ring buffers can't be safely cloned
|
||||
}
|
||||
|
||||
if err := m.loadSubscription(s); err != nil {
|
||||
return fmt.Errorf("failed to load subscription %s: %w", sub.Name, err)
|
||||
}
|
||||
|
||||
@@ -298,6 +298,34 @@ func (m *Manager) Subscriptions() []*Subscription {
|
||||
return result
|
||||
}
|
||||
|
||||
// GetNotificationStats returns runtime stats for all subscriptions
|
||||
func (m *Manager) GetNotificationStats() []types.SubscriptionStats {
|
||||
var stats []types.SubscriptionStats
|
||||
m.subscriptions.Range(func(_ int, sub *Subscription) bool {
|
||||
var containerIDs []string
|
||||
if sub.TriggeredContainerIDs != nil {
|
||||
sub.TriggeredContainerIDs.Range(func(id string, _ struct{}) bool {
|
||||
containerIDs = append(containerIDs, id)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
var lastTriggered *time.Time
|
||||
if t := sub.LastTriggeredAt.Load(); t != nil && !t.IsZero() {
|
||||
lastTriggered = t
|
||||
}
|
||||
|
||||
stats = append(stats, types.SubscriptionStats{
|
||||
SubscriptionID: sub.ID,
|
||||
TriggerCount: sub.TriggerCount.Load(),
|
||||
LastTriggeredAt: lastTriggered,
|
||||
TriggeredContainerIDs: containerIDs,
|
||||
})
|
||||
return true
|
||||
})
|
||||
return stats
|
||||
}
|
||||
|
||||
// Dispatchers returns all dispatchers as DispatcherConfig sorted by ID
|
||||
func (m *Manager) Dispatchers() []DispatcherConfig {
|
||||
result := make([]DispatcherConfig, 0)
|
||||
|
||||
@@ -29,6 +29,10 @@ type persistingNotificationHandler struct {
|
||||
configPath string
|
||||
}
|
||||
|
||||
func (h *persistingNotificationHandler) GetNotificationStats() []types.SubscriptionStats {
|
||||
return h.manager.GetNotificationStats()
|
||||
}
|
||||
|
||||
func (h *persistingNotificationHandler) HandleNotificationConfig(subscriptions []types.SubscriptionConfig, dispatchers []types.DispatcherConfig) error {
|
||||
// Update the manager
|
||||
if err := h.manager.HandleNotificationConfig(subscriptions, dispatchers); err != nil {
|
||||
|
||||
@@ -87,3 +87,7 @@ func (a *agentService) Exec(ctx context.Context, c container.Container, cmd []st
|
||||
func (a *agentService) UpdateNotificationConfig(ctx context.Context, subscriptions []types.SubscriptionConfig, dispatchers []types.DispatcherConfig) error {
|
||||
return a.client.UpdateNotificationConfig(ctx, subscriptions, dispatchers)
|
||||
}
|
||||
|
||||
func (a *agentService) GetNotificationStats(ctx context.Context) ([]types.SubscriptionStats, error) {
|
||||
return a.client.GetNotificationStats(ctx)
|
||||
}
|
||||
|
||||
@@ -351,3 +351,78 @@ func (m *MultiHostService) Subscriptions() []*notification.Subscription {
|
||||
func (m *MultiHostService) Dispatchers() []notification.DispatcherConfig {
|
||||
return m.notificationManager.Dispatchers()
|
||||
}
|
||||
|
||||
// NotificationStatsProvider is an interface for clients that can report notification stats
|
||||
type NotificationStatsProvider interface {
|
||||
GetNotificationStats(ctx context.Context) ([]types.SubscriptionStats, error)
|
||||
}
|
||||
|
||||
// FetchAgentNotificationStats fetches and aggregates notification stats from all agent clients
|
||||
func (m *MultiHostService) FetchAgentNotificationStats() map[int]types.SubscriptionStats {
|
||||
// Collect providers
|
||||
var providers []NotificationStatsProvider
|
||||
for _, client := range m.manager.List() {
|
||||
if provider, ok := client.(NotificationStatsProvider); ok {
|
||||
providers = append(providers, provider)
|
||||
}
|
||||
}
|
||||
|
||||
if len(providers) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Fetch stats from all agents in parallel
|
||||
allStats := lop.Map(providers, func(provider NotificationStatsProvider, _ int) []types.SubscriptionStats {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), m.timeout)
|
||||
defer cancel()
|
||||
stats, err := provider.GetNotificationStats(ctx)
|
||||
if err != nil {
|
||||
log.Debug().Err(err).Msg("Failed to fetch notification stats from agent")
|
||||
return nil
|
||||
}
|
||||
return stats
|
||||
})
|
||||
|
||||
// Aggregate sequentially
|
||||
aggregated := make(map[int]types.SubscriptionStats)
|
||||
for _, stats := range allStats {
|
||||
for _, s := range stats {
|
||||
existing, ok := aggregated[s.SubscriptionID]
|
||||
if !ok {
|
||||
// Dedup container IDs from this agent
|
||||
seen := make(map[string]struct{}, len(s.TriggeredContainerIDs))
|
||||
deduped := make([]string, 0, len(s.TriggeredContainerIDs))
|
||||
for _, id := range s.TriggeredContainerIDs {
|
||||
if _, exists := seen[id]; !exists {
|
||||
seen[id] = struct{}{}
|
||||
deduped = append(deduped, id)
|
||||
}
|
||||
}
|
||||
s.TriggeredContainerIDs = deduped
|
||||
aggregated[s.SubscriptionID] = s
|
||||
continue
|
||||
}
|
||||
|
||||
existing.TriggerCount += s.TriggerCount
|
||||
|
||||
if s.LastTriggeredAt != nil && (existing.LastTriggeredAt == nil || s.LastTriggeredAt.After(*existing.LastTriggeredAt)) {
|
||||
existing.LastTriggeredAt = s.LastTriggeredAt
|
||||
}
|
||||
|
||||
// Dedup container IDs across agents
|
||||
seen := make(map[string]struct{}, len(existing.TriggeredContainerIDs))
|
||||
for _, id := range existing.TriggeredContainerIDs {
|
||||
seen[id] = struct{}{}
|
||||
}
|
||||
for _, id := range s.TriggeredContainerIDs {
|
||||
if _, exists := seen[id]; !exists {
|
||||
seen[id] = struct{}{}
|
||||
existing.TriggeredContainerIDs = append(existing.TriggeredContainerIDs, id)
|
||||
}
|
||||
}
|
||||
aggregated[s.SubscriptionID] = existing
|
||||
}
|
||||
}
|
||||
|
||||
return aggregated
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/amir20/dozzle/internal/notification"
|
||||
"github.com/amir20/dozzle/internal/notification/dispatcher"
|
||||
container_support "github.com/amir20/dozzle/internal/support/container"
|
||||
"github.com/amir20/dozzle/types"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
@@ -173,3 +174,7 @@ func (m *K8sClusterService) RemoveDispatcher(id int) {
|
||||
func (m *K8sClusterService) Dispatchers() []notification.DispatcherConfig {
|
||||
return []notification.DispatcherConfig{}
|
||||
}
|
||||
|
||||
func (m *K8sClusterService) FetchAgentNotificationStats() map[int]types.SubscriptionStats {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -106,12 +106,38 @@ type TestWebhookResult struct {
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
func subscriptionToResponse(sub *notification.Subscription, dispatchers []notification.DispatcherConfig) *NotificationRuleResponse {
|
||||
func subscriptionToResponse(sub *notification.Subscription, dispatchers []notification.DispatcherConfig, agentStats map[int]types.SubscriptionStats) *NotificationRuleResponse {
|
||||
var lastTriggeredAt *time.Time
|
||||
if t := sub.LastTriggeredAt.Load(); t != nil && !t.IsZero() {
|
||||
lastTriggeredAt = t
|
||||
}
|
||||
|
||||
triggerCount := sub.TriggerCount.Load()
|
||||
triggeredContainers := sub.TriggeredContainersCount()
|
||||
|
||||
// Merge agent stats if available
|
||||
if as, ok := agentStats[sub.ID]; ok {
|
||||
triggerCount += as.TriggerCount
|
||||
|
||||
if as.LastTriggeredAt != nil && (lastTriggeredAt == nil || as.LastTriggeredAt.After(*lastTriggeredAt)) {
|
||||
lastTriggeredAt = as.LastTriggeredAt
|
||||
}
|
||||
|
||||
// Count unique container IDs from agents (deduplicated with local)
|
||||
agentContainerSet := make(map[string]struct{}, len(as.TriggeredContainerIDs))
|
||||
for _, id := range as.TriggeredContainerIDs {
|
||||
agentContainerSet[id] = struct{}{}
|
||||
}
|
||||
// Subtract containers already counted locally
|
||||
if sub.TriggeredContainerIDs != nil {
|
||||
sub.TriggeredContainerIDs.Range(func(id string, _ struct{}) bool {
|
||||
delete(agentContainerSet, id)
|
||||
return true
|
||||
})
|
||||
}
|
||||
triggeredContainers += len(agentContainerSet)
|
||||
}
|
||||
|
||||
var disp *DispatcherResponse
|
||||
for _, d := range dispatchers {
|
||||
if d.ID == sub.DispatcherID {
|
||||
@@ -130,9 +156,9 @@ func subscriptionToResponse(sub *notification.Subscription, dispatchers []notifi
|
||||
MetricExpression: sub.MetricExpression,
|
||||
Cooldown: sub.Cooldown,
|
||||
SampleWindow: sub.SampleWindow,
|
||||
TriggerCount: sub.TriggerCount.Load(),
|
||||
TriggerCount: triggerCount,
|
||||
LastTriggeredAt: lastTriggeredAt,
|
||||
TriggeredContainers: sub.TriggeredContainersCount(),
|
||||
TriggeredContainers: triggeredContainers,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,9 +209,10 @@ func writeError(w http.ResponseWriter, status int, message string) {
|
||||
func (h *handler) listNotificationRules(w http.ResponseWriter, r *http.Request) {
|
||||
subscriptions := h.hostService.Subscriptions()
|
||||
dispatchers := h.hostService.Dispatchers()
|
||||
agentStats := h.hostService.FetchAgentNotificationStats()
|
||||
rules := make([]*NotificationRuleResponse, len(subscriptions))
|
||||
for i, sub := range subscriptions {
|
||||
rules[i] = subscriptionToResponse(sub, dispatchers)
|
||||
rules[i] = subscriptionToResponse(sub, dispatchers, agentStats)
|
||||
}
|
||||
writeJSON(w, http.StatusOK, rules)
|
||||
}
|
||||
@@ -198,9 +225,10 @@ func (h *handler) getNotificationRule(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
dispatchers := h.hostService.Dispatchers()
|
||||
agentStats := h.hostService.FetchAgentNotificationStats()
|
||||
for _, sub := range h.hostService.Subscriptions() {
|
||||
if sub.ID == id {
|
||||
writeJSON(w, http.StatusOK, subscriptionToResponse(sub, dispatchers))
|
||||
writeJSON(w, http.StatusOK, subscriptionToResponse(sub, dispatchers, agentStats))
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -230,7 +258,7 @@ func (h *handler) createNotificationRule(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusCreated, subscriptionToResponse(sub, h.hostService.Dispatchers()))
|
||||
writeJSON(w, http.StatusCreated, subscriptionToResponse(sub, h.hostService.Dispatchers(), nil))
|
||||
}
|
||||
|
||||
func (h *handler) replaceNotificationRule(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -263,7 +291,7 @@ func (h *handler) replaceNotificationRule(w http.ResponseWriter, r *http.Request
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, subscriptionToResponse(sub, h.hostService.Dispatchers()))
|
||||
writeJSON(w, http.StatusOK, subscriptionToResponse(sub, h.hostService.Dispatchers(), nil))
|
||||
}
|
||||
|
||||
func (h *handler) updateNotificationRule(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -312,9 +340,10 @@ func (h *handler) updateNotificationRule(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
// Fetch the updated subscription
|
||||
dispatchers := h.hostService.Dispatchers()
|
||||
agentStats := h.hostService.FetchAgentNotificationStats()
|
||||
for _, sub := range h.hostService.Subscriptions() {
|
||||
if sub.ID == id {
|
||||
writeJSON(w, http.StatusOK, subscriptionToResponse(sub, dispatchers))
|
||||
writeJSON(w, http.StatusOK, subscriptionToResponse(sub, dispatchers, agentStats))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/amir20/dozzle/internal/notification"
|
||||
"github.com/amir20/dozzle/internal/notification/dispatcher"
|
||||
container_support "github.com/amir20/dozzle/internal/support/container"
|
||||
"github.com/amir20/dozzle/types"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
@@ -85,6 +86,7 @@ type HostService interface {
|
||||
UpdateDispatcher(id int, d dispatcher.Dispatcher)
|
||||
RemoveDispatcher(id int)
|
||||
Dispatchers() []notification.DispatcherConfig
|
||||
FetchAgentNotificationStats() map[int]types.SubscriptionStats
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
|
||||
@@ -21,6 +21,7 @@ service AgentService {
|
||||
rpc ContainerExec(stream ContainerExecRequest) returns (stream ContainerExecResponse) {}
|
||||
rpc ContainerAttach(stream ContainerAttachRequest) returns (stream ContainerAttachResponse) {}
|
||||
rpc UpdateNotificationConfig(UpdateNotificationConfigRequest) returns (UpdateNotificationConfigResponse) {}
|
||||
rpc GetNotificationStats(GetNotificationStatsRequest) returns (GetNotificationStatsResponse) {}
|
||||
}
|
||||
|
||||
message ListContainersRequest {
|
||||
@@ -135,3 +136,9 @@ message UpdateNotificationConfigRequest {
|
||||
}
|
||||
|
||||
message UpdateNotificationConfigResponse {}
|
||||
|
||||
message GetNotificationStatsRequest {}
|
||||
|
||||
message GetNotificationStatsResponse {
|
||||
repeated NotificationSubscriptionStats stats = 1;
|
||||
}
|
||||
|
||||
@@ -113,3 +113,10 @@ message NotificationDispatcher {
|
||||
string template = 5;
|
||||
map<string, string> headers = 6;
|
||||
}
|
||||
|
||||
message NotificationSubscriptionStats {
|
||||
int32 subscriptionId = 1;
|
||||
int64 triggerCount = 2;
|
||||
google.protobuf.Timestamp lastTriggeredAt = 3;
|
||||
repeated string triggeredContainerIds = 4;
|
||||
}
|
||||
|
||||
@@ -64,6 +64,14 @@ type SubscriptionConfig struct {
|
||||
SampleWindow int `json:"sampleWindow,omitempty"`
|
||||
}
|
||||
|
||||
// SubscriptionStats represents runtime stats for a notification subscription
|
||||
type SubscriptionStats struct {
|
||||
SubscriptionID int `json:"subscriptionId"`
|
||||
TriggerCount int64 `json:"triggerCount"`
|
||||
LastTriggeredAt *time.Time `json:"lastTriggeredAt,omitempty"`
|
||||
TriggeredContainerIDs []string `json:"triggeredContainerIds"`
|
||||
}
|
||||
|
||||
// DispatcherConfig represents a notification dispatcher configuration
|
||||
type DispatcherConfig struct {
|
||||
ID int
|
||||
|
||||
Reference in New Issue
Block a user