8
8
import io .kafbat .ui .model .InternalConsumerGroup ;
9
9
import io .kafbat .ui .model .InternalTopicConsumerGroup ;
10
10
import io .kafbat .ui .model .KafkaCluster ;
11
+ import io .kafbat .ui .model .ServerStatusDTO ;
11
12
import io .kafbat .ui .model .SortOrderDTO ;
13
+ import io .kafbat .ui .model .Statistics ;
12
14
import io .kafbat .ui .service .index .ConsumerGroupFilter ;
15
+ import io .kafbat .ui .service .metrics .scrape .ScrapedClusterState ;
13
16
import io .kafbat .ui .service .rbac .AccessControlService ;
14
17
import io .kafbat .ui .util .ApplicationMetrics ;
15
18
import io .kafbat .ui .util .KafkaClientSslPropertiesUtil ;
19
22
import java .util .HashMap ;
20
23
import java .util .List ;
21
24
import java .util .Map ;
25
+ import java .util .Optional ;
22
26
import java .util .Properties ;
27
+ import java .util .Set ;
23
28
import java .util .function .ToIntFunction ;
24
29
import java .util .stream .Collectors ;
25
30
import java .util .stream .Stream ;
@@ -41,6 +46,7 @@ public class ConsumerGroupService {
41
46
private final AdminClientService adminClientService ;
42
47
private final AccessControlService accessControlService ;
43
48
private final ClustersProperties clustersProperties ;
49
+ private final StatisticsCache statisticsCache ;
44
50
45
51
private Mono <List <InternalConsumerGroup >> getConsumerGroups (
46
52
ReactiveAdminClient ac ,
@@ -67,27 +73,63 @@ private Mono<List<InternalConsumerGroup>> getConsumerGroups(
67
73
public Mono <List <InternalTopicConsumerGroup >> getConsumerGroupsForTopic (KafkaCluster cluster ,
68
74
String topic ) {
69
75
return adminClientService .get (cluster )
70
- // 1. getting topic's end offsets
71
76
.flatMap (ac -> ac .listTopicOffsets (topic , OffsetSpec .latest (), false )
72
- .flatMap (endOffsets -> {
73
- var tps = new ArrayList <>(endOffsets .keySet ());
74
- // 2. getting all consumer groups
75
- return describeConsumerGroups (ac )
76
- .flatMap ((List <ConsumerGroupDescription > groups ) -> {
77
- // 3. trying to find committed offsets for topic
78
- var groupNames = groups .stream ().map (ConsumerGroupDescription ::groupId ).toList ();
79
- return ac .listConsumerGroupOffsets (groupNames , tps ).map (offsets ->
80
- groups .stream ()
81
- // 4. keeping only groups that relates to topic
82
- .filter (g -> isConsumerGroupRelatesToTopic (topic , g , offsets .containsRow (g .groupId ())))
83
- .map (g ->
84
- // 5. constructing results
85
- InternalTopicConsumerGroup .create (topic , g , offsets .row (g .groupId ()), endOffsets ))
86
- .toList ()
87
- );
88
- }
89
- );
90
- }));
77
+ .flatMap (endOffsets ->
78
+ describeConsumerGroups (cluster , ac , true ).flatMap (groups ->
79
+ filterConsumerGroups (cluster , ac , groups , topic , endOffsets )
80
+ )
81
+ )
82
+ );
83
+ }
84
+
85
+ private Mono <List <InternalTopicConsumerGroup >> filterConsumerGroups (
86
+ KafkaCluster cluster ,
87
+ ReactiveAdminClient ac ,
88
+ List <ConsumerGroupDescription > groups ,
89
+ String topic ,
90
+ Map <TopicPartition , Long > endOffsets ) {
91
+
92
+ Set <ConsumerGroupState > inactiveStates = Set .of (
93
+ ConsumerGroupState .DEAD ,
94
+ ConsumerGroupState .EMPTY
95
+ );
96
+
97
+ Map <Boolean , List <ConsumerGroupDescription >> partitioned = groups .stream ().collect (
98
+ Collectors .partitioningBy ((g ) -> !inactiveStates .contains (g .state ()))
99
+ );
100
+
101
+ List <ConsumerGroupDescription > stable = partitioned .get (true ).stream ()
102
+ .filter (g -> isConsumerGroupRelatesToTopic (topic , g , false ))
103
+ .toList ();
104
+
105
+ List <ConsumerGroupDescription > dead = partitioned .get (false );
106
+ if (!dead .isEmpty ()) {
107
+ Statistics statistics = statisticsCache .get (cluster );
108
+ if (statistics .getStatus ().equals (ServerStatusDTO .ONLINE )) {
109
+ Map <String , ScrapedClusterState .ConsumerGroupState > consumerGroupsStates =
110
+ statistics .getClusterState ().getConsumerGroupsStates ();
111
+ dead = dead .stream ().filter (g ->
112
+ Optional .ofNullable (consumerGroupsStates .get (g .groupId ()))
113
+ .map (s ->
114
+ s .committedOffsets ().keySet ().stream ().anyMatch (tp -> tp .topic ().equals (topic ))
115
+ ).orElse (false )
116
+ ).toList ();
117
+ }
118
+ }
119
+
120
+ List <ConsumerGroupDescription > filtered = new ArrayList <>(stable .size () + dead .size ());
121
+ filtered .addAll (stable );
122
+ filtered .addAll (dead );
123
+
124
+ List <TopicPartition > partitions = new ArrayList <>(endOffsets .keySet ());
125
+
126
+ List <String > groupIds = filtered .stream ().map (ConsumerGroupDescription ::groupId ).toList ();
127
+ return ac .listConsumerGroupOffsets (groupIds , partitions ).map (offsets ->
128
+ filtered .stream ().filter (g ->
129
+ isConsumerGroupRelatesToTopic (topic , g , offsets .containsRow (g .groupId ()))
130
+ ).map (g ->
131
+ InternalTopicConsumerGroup .create (topic , g , offsets .row (g .groupId ()), endOffsets )
132
+ ).toList ());
91
133
}
92
134
93
135
private boolean isConsumerGroupRelatesToTopic (String topic ,
@@ -208,13 +250,53 @@ private <T> Stream<T> sortAndPaginate(Collection<T> collection,
208
250
.limit (perPage );
209
251
}
210
252
211
- private Mono <List <ConsumerGroupDescription >> describeConsumerGroups (ReactiveAdminClient ac ) {
253
+ private Mono <List <ConsumerGroupDescription >> describeConsumerGroups (
254
+ KafkaCluster cluster ,
255
+ ReactiveAdminClient ac ,
256
+ boolean cache ) {
212
257
return ac .listConsumerGroupNames ()
213
- .flatMap (ac ::describeConsumerGroups )
214
- .map (cgs -> new ArrayList <>(cgs .values ()));
258
+ .flatMap (names -> describeConsumerGroups (names , cluster , ac , cache ));
259
+ }
260
+
261
+ private Mono <List <ConsumerGroupDescription >> describeConsumerGroups (
262
+ List <String > groupNames ,
263
+ KafkaCluster cluster ,
264
+ ReactiveAdminClient ac ,
265
+ boolean cache ) {
266
+
267
+ Statistics statistics = statisticsCache .get (cluster );
268
+
269
+ if (cache && statistics .getStatus ().equals (ServerStatusDTO .ONLINE )) {
270
+ List <ConsumerGroupDescription > result = new ArrayList <>();
271
+ List <String > notFound = new ArrayList <>();
272
+ Map <String , ScrapedClusterState .ConsumerGroupState > consumerGroupsStates =
273
+ statistics .getClusterState ().getConsumerGroupsStates ();
274
+ for (String groupName : groupNames ) {
275
+ ScrapedClusterState .ConsumerGroupState consumerGroupState = consumerGroupsStates .get (groupName );
276
+ if (consumerGroupState != null ) {
277
+ result .add (consumerGroupState .description ());
278
+ } else {
279
+ notFound .add (groupName );
280
+ }
281
+ }
282
+ if (!notFound .isEmpty ()) {
283
+ return ac .describeConsumerGroups (notFound )
284
+ .map (descriptions -> {
285
+ result .addAll (descriptions .values ());
286
+ return result ;
287
+ });
288
+ } else {
289
+ return Mono .just (result );
290
+ }
291
+ } else {
292
+ return ac .describeConsumerGroups (groupNames )
293
+ .map (descriptions -> List .copyOf (descriptions .values ()));
294
+ }
215
295
}
216
296
217
297
298
+
299
+
218
300
private Mono <List <ConsumerGroupDescription >> loadDescriptionsByInternalConsumerGroups (
219
301
ReactiveAdminClient ac ,
220
302
List <ConsumerGroupListing > groups ,
0 commit comments