private void trackGroupCoverageChanges(Group group, boolean fullCoverage, long medianDocuments, long maxDocuments) {
        if ( ! hasInformationAboutAllNodes()) return; // Be silent until we know what we are talking about.
        boolean changed = group.fullCoverageStatusChanged(fullCoverage);
        if (changed || (!fullCoverage && System.currentTimeMillis() > nextLogTime)) {
            nextLogTime = System.currentTimeMillis() + 30 * 1000;
            if (fullCoverage) {
                log.info("Cluster " + clusterId + ": " + group + " has full coverage. " +
                         "Active documents: " + group.activeDocuments() + "/" + medianDocuments + ", " +
                         "Target active documents: " + group.targetActiveDocuments() + ", " +
                         "working nodes: " + group.workingNodesCount() + "/" + group.nodes().size());
            } else {
                StringBuilder unresponsive = new StringBuilder();
                for (var node : group.nodes()) {
                    if (node.isWorking() != Boolean.TRUE)
                        unresponsive.append('\n').append(node);
                }
                String message = "Cluster " + clusterId + ": " + group + " has reduced coverage: " +
                                 "Active documents: " + group.activeDocuments() + "/" + maxDocuments + ", " +
                                 "Target active documents: " + group.targetActiveDocuments() + ", " +
                                 "working nodes: " + group.workingNodesCount() + "/" + group.nodes().size() +
                                 ", unresponsive nodes: " + (unresponsive.toString().isEmpty() ? " none" : unresponsive);
                if (nonWorkingNodeCount() == 1) // That is normal
                    log.info(message);
                else
                    log.warning(message);
            }
        }
    }
Calculate whether a subset of nodes in a group has enough coverage
container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/SearchCluster.java