public static Multimap<String, String> generateReportForSessionManagerSessionInfos(
        @Nonnull final Cache<String, ClientSessionInfo> sessionInfoCache,
        @Nonnull final Collection<ClientSession> localSessions,
        @Nonnull final Map<NodeID, Set<String>> sessionInfoKeysByClusterNode
    ) {
        final Set<NodeID> clusterNodeIDs = ClusterManager.getNodesInfo().stream().map(ClusterNodeInfo::getNodeID).collect(Collectors.toSet());

        // Take snapshots of all data structures at as much the same time as possible.
        final ConcurrentMap<String, ClientSessionInfo> cache = new ConcurrentHashMap<>(sessionInfoCache);
        final List<String> localSessionInfosJids = localSessions.stream().map(s -> s.getAddress().toString()).collect(Collectors.toList());
        final List<String> remoteSessionInfosJids = sessionInfoKeysByClusterNode.values().stream().flatMap(Collection::stream).collect(Collectors.toList());
        final List<String> remoteSessionInfosJidsWithNodeId = new ArrayList<>();
        for (Map.Entry<NodeID, Set<String>> entry : sessionInfoKeysByClusterNode.entrySet()) {
            for (String item : entry.getValue()) {
                remoteSessionInfosJidsWithNodeId.add(item + " (" + entry.getKey() + ")");
            }
        }

        // Duplicates detection
        final Set<String> localSessionInfosDuplicates = CollectionUtils.findDuplicates(localSessionInfosJids);
        final Set<String> remoteSessionInfosDuplicates = CollectionUtils.findDuplicates(remoteSessionInfosJids);
        final Set<String> sessionInfosBothLocalAndRemote = CollectionUtils.findDuplicates(localSessionInfosJids, remoteSessionInfosJids);

        // Detection of other inconsistencies
        final Set<String> nonLocallyStoredCachedSessionInfos = cache.keySet()
            .stream()
            .filter(v -> !localSessionInfosJids.contains(v))
            .filter(v -> !remoteSessionInfosJids.contains(v))
            .collect(Collectors.toSet());
        final Set<String> nonCachedLocalSessionInfos = localSessionInfosJids.stream().filter(v -> !cache.containsKey(v)).collect(Collectors.toSet());
        final Set<String> nonCachedRemoteSessionInfos = remoteSessionInfosJids.stream().filter(v -> !cache.containsKey(v)).collect(Collectors.toSet());

        // Generate report
        final Multimap<String, String> result = HashMultimap.create();

        result.put("info", String.format("The cache named %s is used to share data in the cluster, which contains %d session infos.", sessionInfoCache.getName(), cache.size()));
        result.put("info", String.format("RoutingTable's getClientsRoutes response is used to track 'local' data to be restored after a cache switch-over. It tracks %d session infos.", localSessionInfosJids.size()));
        result.put("info", String.format("The field sessionInfosByClusterNode is used to track data in the cache from every other cluster node. It contains %d routes for %d cluster nodes.", sessionInfoKeysByClusterNode.values().stream().reduce(0, (subtotal, values) -> subtotal + values.size(), Integer::sum), sessionInfoKeysByClusterNode.size()));

        result.put("data", String.format("%s contains these entries (these are shared in the cluster):\n%s", sessionInfoCache.getName(), String.join("\n", cache.keySet())));
        result.put("data", String.format("RoutingTable's getClientsRoutes contains these entries (these represent 'local' data):\n%s", String.join("\n", localSessionInfosJids)));
        result.put("data", String.format("sessionInfosByClusterNode contains these entries (these represent 'remote' data):\n%s", String.join("\n", remoteSessionInfosJidsWithNodeId)));

        if (!sessionInfoKeysByClusterNode.containsKey(XMPPServer.getInstance().getNodeID())) {
            result.put("pass", "sessionInfoKeysByClusterNode does not track data for the local cluster node.");
        } else {
            result.put("fail", "sessionInfoKeysByClusterNode tracks data for the local cluster node.");
        }

        if (clusterNodeIDs.containsAll(sessionInfoKeysByClusterNode.keySet())) {
            result.put("pass", "sessionInfoKeysByClusterNode tracks data for cluster nodes that are recognized in the cluster.");
        } else {
            result.put("fail", String.format("sessionInfoKeysByClusterNode tracks data for cluster nodes that are not recognized. All cluster nodeIDs as recognized: %s All cluster nodeIDs for which data is tracked: %s.", clusterNodeIDs.stream().map(NodeID::toString).collect(Collectors.joining(", ")), sessionInfoKeysByClusterNode.keySet().stream().map(NodeID::toString).collect(Collectors.joining(", "))));
        }

        if (localSessionInfosDuplicates.isEmpty()) {
            result.put("pass", "There is no overlap in local session infos (they are all unique values).");
        } else {
            result.put("fail", String.format("There is overlap in local session infos (they are not all unique values). These %d values are duplicated: %s", localSessionInfosDuplicates.size(), String.join(", ", localSessionInfosDuplicates)));
        }

        if (remoteSessionInfosDuplicates.isEmpty()) {
            result.put("pass", "There is no overlap in sessionInfosByClusterNode (they are all unique values).");
        } else {
            result.put("fail", String.format("There is overlap in sessionInfosByClusterNode (they are not all unique values). These %d values are duplicated: %s", remoteSessionInfosDuplicates.size(), String.join(", ", remoteSessionInfosDuplicates)));
        }

        if (sessionInfosBothLocalAndRemote.isEmpty()) {
            result.put("pass", "There are no elements that are both 'remote' (in sessionInfosByClusterNode) as well as 'local' (in SessionManager's localSessionManager).");
        } else {
            result.put("fail", String.format("There are %d elements that are both 'remote' (in sessionInfosByClusterNode) as well as 'local' (in SessionManager's localSessionManager): %s", sessionInfosBothLocalAndRemote.size(), String.join(", ", sessionInfosBothLocalAndRemote)));
        }

        if (!ClusterManager.isClusteringStarted()) {
            if (!cache.isEmpty()) {
                result.put("fail", String.format("Clustering is not started. The Cache named %s is expected to be unused when clustering is not used, but is not empty!",  sessionInfoCache.getName()));
            }
        } else {
            if (nonCachedLocalSessionInfos.isEmpty()) {
                result.put("pass", String.format("All elements in SessionManager's localSessionManager exist in %s.", sessionInfoCache.getName()));
            } else {
                result.put("fail", String.format("Not all elements in SessionManager's localSessionManager exist in %s. These %d entries do not: %s", sessionInfoCache.getName(), nonCachedLocalSessionInfos.size(), String.join(", ", nonCachedLocalSessionInfos)));
            }

            if (nonCachedRemoteSessionInfos.isEmpty()) {
                result.put("pass", String.format("All elements in sessionInfosByClusterNode exist in %s.", sessionInfoCache.getName()));
            } else {
                result.put("fail", String.format("Not all elements in sessionInfosByClusterNode exist in %s. These %d entries do not: %s", sessionInfoCache.getName(), nonCachedRemoteSessionInfos.size(), String.join(", ", nonCachedRemoteSessionInfos)));
            }

            if (nonLocallyStoredCachedSessionInfos.isEmpty()) {
                result.put("pass", String.format("All cache entries of %s exist in sessionInfosByClusterNode and/or RoutingTable's getClientsRoutes.", sessionInfoCache.getName()));
            } else {
                result.put("fail", String.format("Not all cache entries of %s exist in sessionInfosByClusterNode and/or RoutingTable's getClientsRoutes. These %d entries do not: %s", sessionInfoCache.getName(), nonLocallyStoredCachedSessionInfos.size(), String.join(", ", nonLocallyStoredCachedSessionInfos)));
            }
        }

        return result;
    }
Verifies that #sessionInfoCache, #localSessionInfos and #sessionInfoKeysByClusterNode
of {@link org.jivesoftware.openfire.SessionManager} are in a consistent state.
<p>
Note that this operation can be costly in terms of resource usage. Use with caution in large / busy systems.
<p>
The returned multi-map can contain up to four keys: info, fail, pass, data. All entry values are a human readable
description of a checked characteristic. When the state is consistent, no 'fail' entries will be returned.

@param sessionInfoCache             The cache that is used to share data across cluster nodes. (note that unlike its siblings, this is empty when not clustering!)
@param localSessions                The data structure that keeps track of what data was added to the cache by the local cluster node.
@param sessionInfoKeysByClusterNode The data structure that keeps track of what data was added to the cache by the remote cluster nodes.
@return A consistency state report.
xmppserver/src/main/java/org/jivesoftware/util/cache/ConsistencyChecks.java