Exemple #1
0
// CLIInfo outputs information about the ring or builder such as node count,
// partition count, etc.
//
// Provide either the ring or the builder, but not both; set the other to nil.
// Normally the results from RingOrBuilder.
func CLIInfo(r Ring, b *Builder, output io.Writer) error {
	if r != nil {
		// TODO: Indication of how risky the assignments are: Replicas not in
		// distinct tiers, nodes.
		s := r.Stats()
		report := [][]string{
			[]string{brimtext.ThousandsSep(int64(s.PartitionCount), ","), "Partitions"},
			[]string{brimtext.ThousandsSep(int64(s.PartitionBitCount), ","), "Partition Bits"},
			[]string{brimtext.ThousandsSep(int64(r.ReplicaCount()), ","), "Replicas"},
			[]string{brimtext.ThousandsSep(int64(s.ActiveNodeCount), ","), "Active Nodes"},
			[]string{brimtext.ThousandsSep(int64(s.InactiveNodeCount), ","), "Inactive Nodes"},
			[]string{brimtext.ThousandsSepU(s.ActiveCapacity, ","), "Active Capacity"},
			[]string{brimtext.ThousandsSepU(s.InactiveCapacity, ","), "Inactive Capacity"},
			[]string{brimtext.ThousandsSep(int64(len(r.Tiers())), ","), "Tier Levels"},
			[]string{fmt.Sprintf("%.02f%%", s.MaxUnderNodePercentage), fmt.Sprintf("Worst Underweight Node (ID %d)", s.MaxUnderNodeID)},
			[]string{fmt.Sprintf("%.02f%%", s.MaxOverNodePercentage), fmt.Sprintf("Worst Overweight Node (ID %d)", s.MaxOverNodeID)},
			[]string{"Version", fmt.Sprintf("%d   %s", r.Version(), time.Unix(0, r.Version()).Format("2006-01-02 15:04:05.000"))},
		}
		reportOpts := brimtext.NewDefaultAlignOptions()
		reportOpts.Alignments = []brimtext.Alignment{brimtext.Right, brimtext.Left}
		fmt.Fprint(output, brimtext.Align(report, reportOpts))
	}
	if b != nil {
		var activeNodes int64
		var activeCapacity int64
		var inactiveNodes int64
		var inactiveCapacity int64
		for _, n := range b.Nodes() {
			if n.Active() {
				activeNodes++
				activeCapacity += int64(n.Capacity())
			} else {
				inactiveNodes++
				inactiveCapacity += int64(n.Capacity())
			}
		}
		report := [][]string{
			[]string{brimtext.ThousandsSep(activeNodes, ","), "Active Nodes"},
			[]string{brimtext.ThousandsSep(inactiveNodes, ","), "Inactive Nodes"},
			[]string{brimtext.ThousandsSep(activeCapacity, ","), "Active Capacity"},
			[]string{brimtext.ThousandsSep(inactiveCapacity, ","), "Inactive Capacity"},
			[]string{brimtext.ThousandsSep(int64(b.ReplicaCount()), ","), "Replicas"},
			[]string{brimtext.ThousandsSep(int64(len(b.Tiers())), ","), "Tier Levels"},
			[]string{brimtext.ThousandsSep(int64(b.PointsAllowed()), ","), "Points Allowed"},
			[]string{brimtext.ThousandsSep(int64(b.MaxPartitionBitCount()), ","), "Max Partition Bits"},
			[]string{brimtext.ThousandsSep(int64(b.MoveWait()), ","), "Move Wait"},
			[]string{brimtext.ThousandsSep(int64(b.IDBits()), ","), "ID Bits"},
		}
		reportOpts := brimtext.NewDefaultAlignOptions()
		reportOpts.Alignments = []brimtext.Alignment{brimtext.Right, brimtext.Left}
		fmt.Fprint(output, brimtext.Align(report, reportOpts))
		return nil
	}
	return nil
}
func (s *ValueLocMapStats) String() string {
	report := [][]string{
		{"ActiveCount", fmt.Sprintf("%d", s.ActiveCount)},
		{"ActiveBytes", fmt.Sprintf("%d", s.ActiveBytes)},
	}
	if s.statsDebug {
		depthCounts := fmt.Sprintf("%d", s.depthCounts[0])
		for i := 1; i < len(s.depthCounts); i++ {
			depthCounts += fmt.Sprintf(" %d", s.depthCounts[i])
		}
		report = append(report, [][]string{
			{"activePercentage", fmt.Sprintf("%.1f%%", 100*float64(s.ActiveCount)/float64(s.usedEntries))},
			{"inactiveMask", fmt.Sprintf("%016x", s.inactiveMask)},
			{"workers", fmt.Sprintf("%d", s.workers)},
			{"roots", fmt.Sprintf("%d (%d bytes)", s.roots, uint64(s.roots)*uint64(unsafe.Sizeof(valueLocMapNode{})))},
			{"usedRoots", fmt.Sprintf("%d", s.usedRoots)},
			{"entryPageSize", fmt.Sprintf("%d (%d bytes)", s.entryPageSize, uint64(s.entryPageSize)*uint64(unsafe.Sizeof(valueLocMapEntry{})))},
			{"entryLockPageSize", fmt.Sprintf("%d (%d bytes)", s.entryLockPageSize, uint64(s.entryLockPageSize)*uint64(unsafe.Sizeof(sync.RWMutex{})))},
			{"splitLevel", fmt.Sprintf("%d +-10%%", s.splitLevel)},
			{"nodes", fmt.Sprintf("%d", s.nodes)},
			{"depth", fmt.Sprintf("%d", len(s.depthCounts))},
			{"depthCounts", depthCounts},
			{"allocedEntries", fmt.Sprintf("%d", s.allocedEntries)},
			{"allocedInOverflow", fmt.Sprintf("%d %.1f%%", s.allocedInOverflow, 100*float64(s.allocedInOverflow)/float64(s.allocedEntries))},
			{"usedEntries", fmt.Sprintf("%d %.1f%%", s.usedEntries, 100*float64(s.usedEntries)/float64(s.allocedEntries))},
			{"usedInOverflow", fmt.Sprintf("%d %.1f%%", s.usedInOverflow, 100*float64(s.usedInOverflow)/float64(s.usedEntries))},
			{"inactive", fmt.Sprintf("%d %.1f%%", s.inactive, 100*float64(s.inactive)/float64(s.usedEntries))},
		}...)
	}
	return brimtext.Align(report, nil)
}
Exemple #3
0
func printNode(n *pb.Node) {
	report := [][]string{
		[]string{"ID:", fmt.Sprintf("%d", n.Id)},
		[]string{"Active:", fmt.Sprintf("%v", n.Active)},
		[]string{"Capacity:", fmt.Sprintf("%d", n.Capacity)},
		[]string{"Tiers:", strings.Join(n.Tiers, "\n")},
		[]string{"Addresses:", strings.Join(n.Addresses, "\n")},
		[]string{"Meta:", n.Meta},
		[]string{"Conf:", string(n.Conf)},
	}
	fmt.Print(brimtext.Align(report, nil))
}
Exemple #4
0
func CLINodeReport(n Node) string {
	report := [][]string{
		[]string{"ID:", fmt.Sprintf("%d", n.ID())},
		[]string{"Active:", fmt.Sprintf("%v", n.Active())},
		[]string{"Capacity:", fmt.Sprintf("%d", n.Capacity())},
		[]string{"Tiers:", strings.Join(qStrings(n.Tiers()), "\n")},
		[]string{"Addresses:", strings.Join(qStrings(n.Addresses()), "\n")},
		[]string{"Meta:", fmt.Sprintf("%q", n.Meta())},
		[]string{"Config:", fmt.Sprintf("%q", string(n.Config()))},
	}
	return brimtext.Align(report, nil)
}
func (s *SyndClient) setCapacityCmd(id uint64, capacity uint32) error {
	ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
	c, err := s.client.SetCapacity(ctx, &pb.Node{Id: id, Capacity: capacity})
	if err != nil {
		return err
	}
	report := [][]string{
		[]string{"Status:", fmt.Sprintf("%v", c.Status)},
		[]string{"Version:", fmt.Sprintf("%v", c.Version)},
	}
	fmt.Print(brimtext.Align(report, nil))
	return nil
}
func (s *SyndClient) setTierCmd(id uint64, tiers []string) error {
	ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
	c, err := s.client.ReplaceTiers(ctx, &pb.Node{Id: id, Tiers: tiers})
	if err != nil {
		return err
	}
	report := [][]string{
		[]string{"Status:", fmt.Sprintf("%v", c.Status)},
		[]string{"Version:", fmt.Sprintf("%v", c.Version)},
	}
	fmt.Print(brimtext.Align(report, nil))
	return nil
}
func (s *SyndClient) setReplicasCmd(count int) error {
	ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
	c, err := s.client.SetReplicas(ctx, &pb.RingOpts{Replicas: int32(count)})
	if err != nil {
		return err
	}
	report := [][]string{
		[]string{"Status:", fmt.Sprintf("%v", c.Status)},
		[]string{"Version:", fmt.Sprintf("%v", c.Version)},
	}
	fmt.Print(brimtext.Align(report, nil))
	return nil
}
func (s *SyndClient) printConfigCmd() error {
	ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
	c, err := s.client.GetGlobalConfig(ctx, &pb.EmptyMsg{})
	if err != nil {
		return err
	}
	report := [][]string{
		[]string{"Status:", fmt.Sprintf("%v", c.Status.Status)},
		[]string{"Version:", fmt.Sprintf("%v", c.Status.Version)},
		[]string{"Conf:", string(c.Conf.Conf)},
	}
	fmt.Print(brimtext.Align(report, nil))
	return nil
}
// SetConfig sets the global ring config to the provided bytes, and indicates
// whether the config change should trigger a restart.
func (s *SyndClient) SetConfig(config []byte, restart bool) (err error) {
	ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
	confMsg := &pb.Conf{
		Conf:            config,
		RestartRequired: restart,
	}
	status, err := s.client.SetConf(ctx, confMsg)
	if err != nil {
		return err
	}
	report := [][]string{
		[]string{"Status:", fmt.Sprintf("%v", status.Status)},
		[]string{"Version:", fmt.Sprintf("%v", status.Version)},
	}
	fmt.Print(brimtext.Align(report, nil))
	return nil
}
Exemple #10
0
// CLITier outputs a list of tiers in the ring or builder; see the output of
// CLIHelp for detailed information.
//
// Provide either the ring or the builder, but not both; set the other to nil.
// Normally the results from RingOrBuilder.
func CLITier(r Ring, b *Builder, args []string, output io.Writer) error {
	var tiers [][]string
	if r == nil {
		tiers = b.Tiers()
	} else {
		tiers = r.Tiers()
	}
	report := [][]string{
		[]string{"Tier", "Existing"},
		[]string{"Level", "Values"},
	}
	reportOpts := brimtext.NewDefaultAlignOptions()
	reportOpts.Alignments = []brimtext.Alignment{brimtext.Right, brimtext.Left}
	fmted := false
OUT:
	for _, values := range tiers {
		for _, value := range values {
			if strings.Contains(value, " ") {
				fmted = true
				break OUT
			}
		}
	}
	for level, values := range tiers {
		sort.Strings(values)
		var pvalue string
		if fmted {
			for _, value := range values {
				pvalue += fmt.Sprintf(" %#v", value)
			}
			pvalue = strings.Trim(pvalue, " ")
		} else {
			pvalue = strings.Join(values, " ")
		}
		report = append(report, []string{
			strconv.Itoa(level),
			strings.Trim(pvalue, " "),
		})
	}
	fmt.Fprint(output, brimtext.Align(report, reportOpts))
	return nil
}
Exemple #11
0
// CLINode outputs a list of nodes in the ring or builder, with optional
// filtering and also allows setting attributes on those nodes; see the output
// of CLIHelp for detailed information.
//
// Provide either the ring or the builder, but not both; set the other to nil.
// Normally the results from RingOrBuilder.
func CLINode(r Ring, b *Builder, args []string, full bool, output io.Writer) (changed bool, err error) {
	var nodes NodeSlice
	if r != nil {
		nodes = r.Nodes()
	} else {
		bnodes := b.Nodes()
		nodes = make(NodeSlice, len(bnodes))
		for i := len(nodes) - 1; i >= 0; i-- {
			nodes[i] = bnodes[i]
		}
	}
	filterArgs := make([]string, 0)
	setArgs := make([]string, 0)
	setFound := false
	for _, arg := range args {
		if arg == "set" {
			setFound = true
			continue
		}
		if setFound {
			setArgs = append(setArgs, arg)
		} else {
			filterArgs = append(filterArgs, arg)
		}
	}
	if len(setArgs) > 0 && b == nil {
		err = fmt.Errorf("set is only valid for builder files")
		return
	}
	if nodes, err = nodes.Filter(filterArgs); err != nil {
		return
	}
	if len(nodes) > 0 && len(setArgs) > 0 {
		if r != nil {
			return false, fmt.Errorf("cannot update nodes in a ring; use with a builder instead")
		}
		for _, n := range nodes {
			if err = CLIAddOrSet(b, setArgs, n.(BuilderNode), output); err != nil {
				return
			}
			changed = true
		}
	}
	if full || len(nodes) == 1 {
		first := true
		for _, n := range nodes {
			if first {
				first = false
			} else {
				fmt.Fprintln(output)
			}
			output.Write([]byte(CLINodeReport(n)))
		}
		return
	}
	header := []string{
		"ID",
		"Active",
		"Capacity",
		"Address",
		"Meta",
	}
	report := [][]string{header}
	reportAlign := brimtext.NewDefaultAlignOptions()
	reportAlign.Alignments = []brimtext.Alignment{
		brimtext.Left,
		brimtext.Right,
		brimtext.Right,
		brimtext.Right,
		brimtext.Left,
	}
	reportLine := func(n Node) []string {
		return []string{
			fmt.Sprintf("%d", n.ID()),
			fmt.Sprintf("%v", n.Active()),
			fmt.Sprintf("%d", n.Capacity()),
			n.Address(0),
			n.Meta(),
		}
	}
	for _, n := range nodes {
		if n.Active() {
			report = append(report, reportLine(n))
		}
	}
	for _, n := range nodes {
		if !n.Active() {
			report = append(report, reportLine(n))
		}
	}
	fmt.Fprint(output, brimtext.Align(report, reportAlign))
	return
}
func (stats *ValueStoreStats) String() string {
	report := [][]string{
		{"Values", fmt.Sprintf("%d", stats.Values)},
		{"ValueBytes", fmt.Sprintf("%d", stats.ValueBytes)},
		{"Lookups", fmt.Sprintf("%d", stats.Lookups)},
		{"LookupErrors", fmt.Sprintf("%d", stats.LookupErrors)},

		{"Reads", fmt.Sprintf("%d", stats.Reads)},
		{"ReadErrors", fmt.Sprintf("%d", stats.ReadErrors)},

		{"Writes", fmt.Sprintf("%d", stats.Writes)},
		{"WriteErrors", fmt.Sprintf("%d", stats.WriteErrors)},
		{"WritesOverridden", fmt.Sprintf("%d", stats.WritesOverridden)},
		{"Deletes", fmt.Sprintf("%d", stats.Deletes)},
		{"DeleteErrors", fmt.Sprintf("%d", stats.DeleteErrors)},
		{"DeletesOverridden", fmt.Sprintf("%d", stats.DeletesOverridden)},
		{"OutBulkSets", fmt.Sprintf("%d", stats.OutBulkSets)},
		{"OutBulkSetValues", fmt.Sprintf("%d", stats.OutBulkSetValues)},
		{"OutBulkSetPushes", fmt.Sprintf("%d", stats.OutBulkSetPushes)},
		{"OutBulkSetPushValues", fmt.Sprintf("%d", stats.OutBulkSetPushValues)},
		{"InBulkSets", fmt.Sprintf("%d", stats.InBulkSets)},
		{"InBulkSetDrops", fmt.Sprintf("%d", stats.InBulkSetDrops)},
		{"InBulkSetInvalids", fmt.Sprintf("%d", stats.InBulkSetInvalids)},
		{"InBulkSetWrites", fmt.Sprintf("%d", stats.InBulkSetWrites)},
		{"InBulkSetWriteErrors", fmt.Sprintf("%d", stats.InBulkSetWriteErrors)},
		{"InBulkSetWritesOverridden", fmt.Sprintf("%d", stats.InBulkSetWritesOverridden)},
		{"OutBulkSetAcks", fmt.Sprintf("%d", stats.OutBulkSetAcks)},
		{"InBulkSetAcks", fmt.Sprintf("%d", stats.InBulkSetAcks)},
		{"InBulkSetAckDrops", fmt.Sprintf("%d", stats.InBulkSetAckDrops)},
		{"InBulkSetAckInvalids", fmt.Sprintf("%d", stats.InBulkSetAckInvalids)},
		{"InBulkSetAckWrites", fmt.Sprintf("%d", stats.InBulkSetAckWrites)},
		{"InBulkSetAckWriteErrors", fmt.Sprintf("%d", stats.InBulkSetAckWriteErrors)},
		{"InBulkSetAckWritesOverridden", fmt.Sprintf("%d", stats.InBulkSetAckWritesOverridden)},
		{"OutPullReplications", fmt.Sprintf("%d", stats.OutPullReplications)},
		{"OutPullReplicationNanoseconds", fmt.Sprintf("%d", stats.OutPullReplicationNanoseconds)},
		{"InPullReplications", fmt.Sprintf("%d", stats.InPullReplications)},
		{"InPullReplicationDrops", fmt.Sprintf("%d", stats.InPullReplicationDrops)},
		{"InPullReplicationInvalids", fmt.Sprintf("%d", stats.InPullReplicationInvalids)},
		{"ExpiredDeletions", fmt.Sprintf("%d", stats.ExpiredDeletions)},
		{"Compactions", fmt.Sprintf("%d", stats.Compactions)},
		{"SmallFileCompactions", fmt.Sprintf("%d", stats.SmallFileCompactions)},
		{"DiskFree", fmt.Sprintf("%d", stats.DiskFree)},
		{"DiskUsed", fmt.Sprintf("%d", stats.DiskUsed)},
		{"DiskSize", fmt.Sprintf("%d", stats.DiskSize)},
		{"DiskFreeTOC", fmt.Sprintf("%d", stats.DiskFreeTOC)},
		{"DiskUsedTOC", fmt.Sprintf("%d", stats.DiskUsedTOC)},
		{"DiskSizeTOC", fmt.Sprintf("%d", stats.DiskSizeTOC)},
		{"MemFree", fmt.Sprintf("%d", stats.MemFree)},
		{"MemUsed", fmt.Sprintf("%d", stats.MemUsed)},
		{"MemSize", fmt.Sprintf("%d", stats.MemSize)},
	}
	if stats.debug {
		report = append(report, [][]string{
			nil,
			{"freeableMemBlockChansCap", fmt.Sprintf("%d", stats.freeableMemBlockChansCap)},
			{"freeableMemBlockChansIn", fmt.Sprintf("%d", stats.freeableMemBlockChansIn)},
			{"freeMemBlockChanCap", fmt.Sprintf("%d", stats.freeMemBlockChanCap)},
			{"freeMemBlockChanIn", fmt.Sprintf("%d", stats.freeMemBlockChanIn)},
			{"freeWriteReqChans", fmt.Sprintf("%d", stats.freeWriteReqChans)},
			{"freeWriteReqChansCap", fmt.Sprintf("%d", stats.freeWriteReqChansCap)},
			{"freeWriteReqChansIn", fmt.Sprintf("%d", stats.freeWriteReqChansIn)},
			{"pendingWriteReqChans", fmt.Sprintf("%d", stats.pendingWriteReqChans)},
			{"pendingWriteReqChansCap", fmt.Sprintf("%d", stats.pendingWriteReqChansCap)},
			{"pendingWriteReqChansIn", fmt.Sprintf("%d", stats.pendingWriteReqChansIn)},
			{"fileMemBlockChanCap", fmt.Sprintf("%d", stats.fileMemBlockChanCap)},
			{"fileMemBlockChanIn", fmt.Sprintf("%d", stats.fileMemBlockChanIn)},
			{"freeTOCBlockChanCap", fmt.Sprintf("%d", stats.freeTOCBlockChanCap)},
			{"freeTOCBlockChanIn", fmt.Sprintf("%d", stats.freeTOCBlockChanIn)},
			{"pendingTOCBlockChanCap", fmt.Sprintf("%d", stats.pendingTOCBlockChanCap)},
			{"pendingTOCBlockChanIn", fmt.Sprintf("%d", stats.pendingTOCBlockChanIn)},
			{"maxLocBlockID", fmt.Sprintf("%d", stats.maxLocBlockID)},
			{"path", stats.path},
			{"pathtoc", stats.pathtoc},
			{"workers", fmt.Sprintf("%d", stats.workers)},
			{"tombstoneDiscardInterval", fmt.Sprintf("%d", stats.tombstoneDiscardInterval)},
			{"outPullReplicationWorkers", fmt.Sprintf("%d", stats.outPullReplicationWorkers)},
			{"outPullReplicationInterval", fmt.Sprintf("%d", stats.outPullReplicationInterval)},
			{"pushReplicationWorkers", fmt.Sprintf("%d", stats.pushReplicationWorkers)},
			{"pushReplicationInterval", fmt.Sprintf("%d", stats.pushReplicationInterval)},
			{"valueCap", fmt.Sprintf("%d", stats.valueCap)},
			{"pageSize", fmt.Sprintf("%d", stats.pageSize)},
			{"minValueAlloc", fmt.Sprintf("%d", stats.minValueAlloc)},
			{"writePagesPerWorker", fmt.Sprintf("%d", stats.writePagesPerWorker)},
			{"tombstoneAge", fmt.Sprintf("%d", stats.tombstoneAge)},
			{"fileCap", fmt.Sprintf("%d", stats.fileCap)},
			{"fileReaders", fmt.Sprintf("%d", stats.fileReaders)},
			{"checksumInterval", fmt.Sprintf("%d", stats.checksumInterval)},
			{"replicationIgnoreRecent", fmt.Sprintf("%d", stats.replicationIgnoreRecent)},
			{"locmapDebugInfo", stats.locmapDebugInfo.String()},
		}...)
	}
	return brimtext.Align(report, nil)
}
Exemple #13
0
func main() {
	if len(os.Args) != 2 {
		fmt.Printf("%s <dir>\n", os.Args[0])
		os.Exit(1)
	}
	dirCount, otherCount, fileSizes := dirWalk(os.Args[1], nil)
	var fileSizesTotal int64
	for _, s := range fileSizes {
		fileSizesTotal += s
	}
	fileSizesMean := fileSizesTotal / int64(len(fileSizes))
	var fileSizesMedian int64
	if len(fileSizes) > 0 {
		sort.Sort(fileSizes)
		fileSizesMedian = fileSizes[len(fileSizes)/2]
	}
	report := [][]string{
		{fmt.Sprintf("%d", dirCount), "directories"},
		{fmt.Sprintf("%d", len(fileSizes)), "files"},
		{fmt.Sprintf("%d", otherCount), "items not files nor directories"},
		{fmt.Sprintf("%d", fileSizesTotal), "total file bytes"},
		{fmt.Sprintf("%d", fileSizesMean), "mean file size"},
		{fmt.Sprintf("%d", fileSizesMedian), "median file size"},
	}
	alignOptions := brimtext.NewDefaultAlignOptions()
	alignOptions.Alignments = []brimtext.Alignment{brimtext.Right, brimtext.Left}
	fmt.Println(brimtext.Align(report, alignOptions))

	var blockSizesMean int64
	var blockSizesMedian int64
	var blockSizesCount int64
	var blockSizesFull int64
	var blockSizesTarget int64
	blockSizesTarget = fileSizesMean
	var blockSizes int64Slice
	overshoot := true
	for {
		blockSizes = blockSizes[:0]
		blockSizesFull = 0
		for _, s := range fileSizes {
			if s < 1 {
				blockSizes = append(blockSizes, 0)
				continue
			}
			for s >= blockSizesTarget {
				blockSizes = append(blockSizes, blockSizesTarget)
				s -= blockSizesTarget
				blockSizesFull++
			}
			if s > 0 {
				blockSizes = append(blockSizes, s)
			}
		}
		blockSizesCount = int64(len(blockSizes))
		blockSizesMean = fileSizesTotal / blockSizesCount
		if blockSizesCount > 0 {
			sort.Sort(blockSizes)
			blockSizesMedian = blockSizes[blockSizesCount/2]
		}
		if blockSizesMedian > blockSizesMean && float64(blockSizesMedian-blockSizesMean)/float64(blockSizesMedian) < 0.01 {
			if !overshoot {
				break
			}
			if blockSizesTarget != math.MaxInt64 {
				if blockSizesTarget > math.MaxInt64/2 {
					blockSizesTarget = math.MaxInt64
				} else {
					blockSizesTarget *= 2
				}
				continue
			}
		}
		overshoot = false
		blockSizesTargetNew := blockSizesTarget - (blockSizesTarget-blockSizesMean+1)/2
		if blockSizesTargetNew == blockSizesTarget {
			break
		}
		blockSizesTarget = blockSizesTargetNew
	}
	report = [][]string{
		{fmt.Sprintf("%d", blockSizesMean), "mean block size"},
		{fmt.Sprintf("%d", blockSizesMedian), "median block size"},
		{fmt.Sprintf("%d", blockSizesCount), "block count"},
		{fmt.Sprintf("%d", blockSizesFull), fmt.Sprintf("full block count, %.0f%%", 100*float64(blockSizesFull)/float64(blockSizesCount))},
		{fmt.Sprintf("%d", blockSizesTarget), "\"best\" block size, median~=mean"},
	}
	fmt.Println(brimtext.Align(report, alignOptions))

	blockSizesTarget = math.MaxInt64
	var blockSizesTargetHighCap int64
	var blockSizesTargetLast int64
	overshoot = true
	done := false
	for {
		blockSizes = blockSizes[:0]
		blockSizesFull = 0
		for _, s := range fileSizes {
			if s < 1 {
				blockSizes = append(blockSizes, 0)
				continue
			}
			for s >= blockSizesTarget {
				blockSizes = append(blockSizes, blockSizesTarget)
				s -= blockSizesTarget
				blockSizesFull++
			}
			if s > 0 {
				blockSizes = append(blockSizes, s)
			}
		}
		blockSizesCount = int64(len(blockSizes))
		blockSizesMean = fileSizesTotal / blockSizesCount
		if blockSizesCount > 0 {
			sort.Sort(blockSizes)
			blockSizesMedian = blockSizes[blockSizesCount/2]
		}
		if done {
			break
		}
		if float64(blockSizesFull)/float64(blockSizesCount) < 0.5 {
			if !overshoot {
				blockSizesTarget = blockSizesTargetLast
				done = true
				continue
			}
			blockSizesTargetHighCap = blockSizesTarget
			blockSizesTarget /= 2
			if blockSizesTarget < 1 {
				blockSizesTarget = 1
				done = true
			}
			continue
		}
		overshoot = false
		blockSizesTargetNew := blockSizesTarget + (blockSizesTargetHighCap-blockSizesTarget+1)/2
		if blockSizesTargetNew == blockSizesTarget {
			break
		}
		blockSizesTargetLast = blockSizesTarget
		blockSizesTarget = blockSizesTargetNew
	}
	report = [][]string{
		{fmt.Sprintf("%d", blockSizesMean), "mean block size"},
		{fmt.Sprintf("%d", blockSizesMedian), "median block size"},
		{fmt.Sprintf("%d", blockSizesCount), "block count"},
		{fmt.Sprintf("%d", blockSizesFull), fmt.Sprintf("full block count, %.0f%%", 100*float64(blockSizesFull)/float64(blockSizesCount))},
		{fmt.Sprintf("%d", blockSizesTarget), "\"best\" block size, full~=50%"},
	}
	fmt.Println(brimtext.Align(report, alignOptions))
}
func (rend *renderer) Table(out *bytes.Buffer, header []byte, body []byte, columnData []int) {
	opts := &brimtext.AlignOptions{}
	*opts = *rend.tableAlignOptions
	opts.Widths = make([]int, len(columnData))
	opts.Alignments = make([]brimtext.Alignment, len(columnData))
	var data [][]string
	rows := bytes.Split(header[:len(header)-1], []byte{markTableRow})
	for _, row := range rows {
		var headerRow []string
		cells := bytes.Split(row[:len(row)-1], []byte{markTableCell})
		for c, cell := range cells {
			if columnData[c]&blackfriday.TABLE_ALIGNMENT_CENTER == blackfriday.TABLE_ALIGNMENT_CENTER {
				opts.Alignments[c] = brimtext.Center
			} else if columnData[c]&blackfriday.TABLE_ALIGNMENT_RIGHT != 0 {
				opts.Alignments[c] = brimtext.Right
			}
			cellString := string(cell)
			if len(cellString) > opts.Widths[c] {
				opts.Widths[c] = len(cellString)
			}
			headerRow = append(headerRow, cellString)
		}
		if len(headerRow) > 0 && headerRow[0] != "omit" {
			data = append(data, headerRow)
		}
	}
	if len(data) > 0 {
		data = append(data, nil)
	}
	rows = bytes.Split(body[:len(body)-1], []byte{markTableRow})
	for _, row := range rows {
		if len(row) == 0 {
			continue
		}
		var bodyRow []string
		cells := bytes.Split(row[:len(row)-1], []byte{markTableCell})
		for c, cell := range cells {
			cellString := string(cell)
			if len(cellString) > opts.Widths[c] {
				opts.Widths[c] = len(cellString)
			}
			bodyRow = append(bodyRow, cellString)
		}
		data = append(data, bodyRow)
	}
	aw := rend.width - rend.currentIndent - len(opts.RowFirstUD) - len(opts.RowLastUD)
	if len(columnData) > 1 {
		aw -= len(opts.RowSecondUD)
	}
	if len(columnData) > 2 {
		aw -= len(opts.RowUD)*len(columnData) - 2
	}
	cw := 0
	for _, w := range opts.Widths {
		cw += w
	}
	ocw := cw
	for cw > aw {
		for i := 0; i < len(opts.Widths); i++ {
			if opts.Widths[i] > 1 {
				opts.Widths[i]--
			}
		}
		cw = 0
		for _, w := range opts.Widths {
			cw += w
		}
		if cw == ocw {
			break
		}
		ocw = cw
	}
	var text string
	for {
		good := true
		text = brimtext.Align(data, opts)
		for _, line := range strings.Split(text, "\n") {
			if len(line) > aw {
				good = false
			}
		}
		if good {
			break
		}
		ocw = cw
		for i := 0; i < len(opts.Widths); i++ {
			if opts.Widths[i] > 1 {
				opts.Widths[i]--
			}
		}
		cw = 0
		for _, w := range opts.Widths {
			cw += w
		}
		if cw == ocw {
			break
		}
	}
	textBytes := []byte(text)
	textBytes = bytes.Replace(textBytes, []byte{' '}, []byte{markNBSP}, -1)
	textBytes = bytes.Replace(textBytes, []byte{'\n'}, []byte{markLineBreak}, -1)
	rend.ensureBlankLine(out)
	out.Write(textBytes)
}