'swift-ring-builder <builder_file> dispersion <search_filter> [options]
Output report on dispersion.
--verbose option will display dispersion graph broken down by tier
You can filter which tiers are evaluated to drill down using a regex
in the optional search_filter argument.  i.e.
swift-ring-builder <builder_file> dispersion "r\d+z\d+$" -v
... would only display rows for the zone tiers
swift-ring-builder <builder_file> dispersion ".*\-[^/]*$" -v
... would only display rows for the server tiers
The reports columns are:
Tier  : the name of the tier
parts : the total number of partitions with assignment in the tier
%     : the percentage of parts in the tier with replicas over assigned
max   : maximum replicas a part should have assigned at the tier
0 - N : the number of parts with that many replicas assigned
e.g.
Tier:  parts      %   max   0    1    2   3
r1z1    1022  79.45     1   2  210  784  28
r1z1 has 1022 total parts assigned, 79% of them have more than the
recommend max replica count of 1 assigned.  Only 2 parts in the ring
are *not* assigned in this tier (0 replica count), 210 parts have
the recommend replica count of 1, 784 have 2 replicas, and 28 sadly
have all three replicas in this tier.'

@staticmethod
def dispersion():

    status = EXIT_SUCCESS
    if (not builder._replica2part2dev):
        print(('Specified    builder    file    "%s"    is    not    rebalanced    yet.    Please    rebalance    first.' % builder_file))
        exit(EXIT_ERROR)
    usage = Commands.dispersion.__doc__.strip()
    parser = optparse.OptionParser(usage)
    parser.add_option('-v', '--verbose', action='store_true', help='Display    dispersion    report    for    tiers')
    (options, args) = parser.parse_args(argv)
    if args[3:]:
        search_filter = args[3]
    else:
        search_filter = None
    report = dispersion_report(builder, search_filter=search_filter, verbose=options.verbose)
    print(('Dispersion    is    %.06f,    Balance    is    %.06f,    Overload    is    %0.2f%%' % (builder.dispersion, builder.get_balance(), (builder.overload * 100))))
    print(('Required    overload    is    %.6f%%' % (builder.get_required_overload() * 100)))
    if report['worst_tier']:
        status = EXIT_WARNING
        print(('Worst    tier    is    %.06f    (%s)' % (report['max_dispersion'], report['worst_tier'])))
    if report['graph']:
        replica_range = range(int(math.ceil((builder.replicas + 1))))
        part_count_width = ('%%%ds' % max(len(str(builder.parts)), 5))
        replica_counts_tmpl = '    '.join((part_count_width for i in replica_range))
        tiers = (tier for (tier, _junk) in report['graph'])
        tier_width = max(max(map(len, tiers)), 30)
        header_line = (((((('%-' + str(tier_width)) + 's    ') + part_count_width) + '    %6s    %6s    ') + replica_counts_tmpl) % tuple((['Tier', 'Parts', '%', 'Max'] + replica_range)))
        underline = ('-' * len(header_line))
        print(underline)
        print(header_line)
        print(underline)
        for (tier_name, dispersion) in report['graph']:
            replica_counts_repr = (replica_counts_tmpl % tuple(dispersion['replicas']))
            template = ''.join(['%-', str(tier_width), 's    ', part_count_width, '    %6.02f    %6d    %s'])
            args = (tier_name, dispersion['placed_parts'], dispersion['dispersion'], dispersion['max_replicas'], replica_counts_repr)
            print((template % args))
    exit(status)
