88import io .kafbat .ui .model .InternalConsumerGroup ;
99import io .kafbat .ui .model .InternalTopicConsumerGroup ;
1010import io .kafbat .ui .model .KafkaCluster ;
11+ import io .kafbat .ui .model .ServerStatusDTO ;
1112import io .kafbat .ui .model .SortOrderDTO ;
13+ import io .kafbat .ui .model .Statistics ;
1214import io .kafbat .ui .service .index .ConsumerGroupFilter ;
15+ import io .kafbat .ui .service .metrics .scrape .ScrapedClusterState ;
1316import io .kafbat .ui .service .rbac .AccessControlService ;
1417import io .kafbat .ui .util .ApplicationMetrics ;
1518import io .kafbat .ui .util .KafkaClientSslPropertiesUtil ;
1922import java .util .HashMap ;
2023import java .util .List ;
2124import java .util .Map ;
25+ import java .util .Optional ;
2226import java .util .Properties ;
27+ import java .util .Set ;
2328import java .util .function .ToIntFunction ;
2429import java .util .stream .Collectors ;
2530import java .util .stream .Stream ;
@@ -41,6 +46,7 @@ public class ConsumerGroupService {
4146 private final AdminClientService adminClientService ;
4247 private final AccessControlService accessControlService ;
4348 private final ClustersProperties clustersProperties ;
49+ private final StatisticsCache statisticsCache ;
4450
4551 private Mono <List <InternalConsumerGroup >> getConsumerGroups (
4652 ReactiveAdminClient ac ,
@@ -67,27 +73,63 @@ private Mono<List<InternalConsumerGroup>> getConsumerGroups(
6773 public Mono <List <InternalTopicConsumerGroup >> getConsumerGroupsForTopic (KafkaCluster cluster ,
6874 String topic ) {
6975 return adminClientService .get (cluster )
70- // 1. getting topic's end offsets
7176 .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 ());
91133 }
92134
93135 private boolean isConsumerGroupRelatesToTopic (String topic ,
@@ -208,13 +250,53 @@ private <T> Stream<T> sortAndPaginate(Collection<T> collection,
208250 .limit (perPage );
209251 }
210252
211- private Mono <List <ConsumerGroupDescription >> describeConsumerGroups (ReactiveAdminClient ac ) {
253+ private Mono <List <ConsumerGroupDescription >> describeConsumerGroups (
254+ KafkaCluster cluster ,
255+ ReactiveAdminClient ac ,
256+ boolean cache ) {
212257 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+ }
215295 }
216296
217297
298+
299+
218300 private Mono <List <ConsumerGroupDescription >> loadDescriptionsByInternalConsumerGroups (
219301 ReactiveAdminClient ac ,
220302 List <ConsumerGroupListing > groups ,
0 commit comments