private void publishSnapshot(MetadataImage newImage) {
        metrics.setGlobalTopicCount(newImage.topics().topicsById().size());
        int fencedBrokers = 0;
        int activeBrokers = 0;
        int zkBrokers = 0;
        for (BrokerRegistration broker : newImage.cluster().brokers().values()) {
            if (broker.fenced()) {
                fencedBrokers++;
            } else {
                activeBrokers++;
            }
            if (broker.isMigratingZkBroker()) {
                zkBrokers++;
            }
        }
        metrics.setFencedBrokerCount(fencedBrokers);
        metrics.setActiveBrokerCount(activeBrokers);
        metrics.setMigratingZkBrokerCount(zkBrokers);

        int totalPartitions = 0;
        int offlinePartitions = 0;
        int partitionsWithoutPreferredLeader = 0;
        for (TopicImage topicImage : newImage.topics().topicsById().values()) {
            for (PartitionRegistration partition : topicImage.partitions().values()) {
                if (!partition.hasLeader()) {
                    offlinePartitions++;
                }
                if (!partition.hasPreferredLeader()) {
                    partitionsWithoutPreferredLeader++;
                }
                totalPartitions++;
            }
        }
        metrics.setGlobalPartitionCount(totalPartitions);
        metrics.setOfflinePartitionCount(offlinePartitions);
        metrics.setPreferredReplicaImbalanceCount(partitionsWithoutPreferredLeader);
        metrics.setZkMigrationState(newImage.features().zkMigrationState().value());
    }
This publisher translates metadata updates sent by MetadataLoader into changes to controller
metrics. Like all MetadataPublisher objects, it only receives notifications about events that
have been persisted to the metadata log. So on the active controller, it will run slightly
behind the latest in-memory state which has not yet been fully persisted to the log. This is
reasonable for metrics, which don't need up-to-the-millisecond update latency.

NOTE: the ZK controller has some special rules for calculating preferredReplicaImbalanceCount
which we haven't implemented here. Specifically, the ZK controller considers reassigning
partitions to always have their preferred leader, even if they don't.
All other metrics should be the same, as far as is possible.
metadata/src/main/java/org/apache/kafka/controller/metrics/ControllerMetadataMetricsPublisher.java