package topology import ( "github.com/seaweedfs/seaweedfs/weed/storage" "github.com/seaweedfs/seaweedfs/weed/storage/erasure_coding" "github.com/seaweedfs/seaweedfs/weed/storage/needle" ) // CollectionStatistics is what a collection holds, summarised so that callers // tracking usage do not have to be sent every volume in the cluster to add it // up themselves. type CollectionStatistics struct { Collection string FileCount uint64 DeleteCount uint64 DeletedByteCount uint64 // Size counts one copy of the data: a single replica of a regular volume, // the data shards of an ec volume. Size uint64 // PhysicalSize counts what is on disk: every replica, and parity shards. PhysicalSize uint64 VolumeCount uint64 } type ecStatsKey struct { collection string volumeId needle.VolumeId } // ecFileCounts holds the per-volume counts that can only be resolved once every // shard holder has been seen. type ecFileCounts struct { collection string fileCount uint64 deleteCount uint64 } // CollectionStatistics summarises every collection in one pass over the // topology, allocating per collection rather than per volume. func (t *Topology) CollectionStatistics() []*CollectionStatistics { byCollection := make(map[string]*CollectionStatistics) statsFor := func(collection string) *CollectionStatistics { stats, found := byCollection[collection] if !found { stats = &CollectionStatistics{Collection: collection} byCollection[collection] = stats } return stats } // Regular volumes are counted once each for logical totals and once per // replica for physical, which the lookup index gives without a set of seen // ids: it is already keyed by volume. for _, c := range t.collectionMap.Items() { collection := c.(*Collection) for _, vl := range collection.GetAllVolumeLayouts() { vl.accessLock.RLock() for vid, locations := range vl.vid2location { stats := statsFor(collection.Name) // Replicas of one volume can disagree while a write is landing // or a heartbeat is late. Count the one holding the most live // data, so the answer does not depend on which replica is // looked at first and usage is never reported lower than some // replica already holds. Quotas are enforced on size less // deletions, so that is what has to be the largest -- a replica // with the biggest raw size can be the one that has deleted the // most, and picking it would leave an over-quota bucket // writable. var largest storage.VolumeInfo var largestLive uint64 found := false for _, dn := range locations.list { v, err := dn.GetVolumesById(vid) if err != nil { continue } stats.PhysicalSize += v.Size live := v.Size if v.DeletedByteCount < live { live -= v.DeletedByteCount } else { live = 0 } if !found || live > largestLive { largest, largestLive, found = v, live, true } } if !found { continue } stats.Size += largest.Size stats.FileCount += uint64(largest.FileCount) stats.DeleteCount += uint64(largest.DeleteCount) // Never more deletions than the volume holds. Live usage is // read as the collection's size less its deletions, so a volume // reporting more deleted bytes than it has would cancel live // bytes belonging to other volumes and report the bucket // smaller than it is. stats.DeletedByteCount += largest.Size - largestLive stats.VolumeCount++ } vl.accessLock.RUnlock() } } // Ec shards are node-local rather than replicated, so their sizes sum // across holders. The file and delete counts describe the volume rather // than the shard, so they resolve once every holder has been seen. perEcVolume := make(map[ecStatsKey]*ecFileCounts) for _, dcNode := range t.Children() { for _, rackNode := range dcNode.(*DataCenter).Children() { for _, dnNode := range rackNode.(*Rack).Children() { for _, ecInfo := range dnNode.(*DataNode).GetEcShards() { message := ecInfo.ToVolumeEcShardInformationMessage() stats := statsFor(ecInfo.Collection) stats.PhysicalSize += uint64(erasure_coding.EcShardsTotalSize(message)) stats.Size += uint64(erasure_coding.EcShardsDataSize(message, 0)) key := ecStatsKey{collection: ecInfo.Collection, volumeId: ecInfo.VolumeId} counts, found := perEcVolume[key] if !found { counts = &ecFileCounts{collection: ecInfo.Collection} perEcVolume[key] = counts stats.VolumeCount++ } if message.FileCount > counts.fileCount { counts.fileCount = message.FileCount } counts.deleteCount += message.DeleteCount } } } } for _, counts := range perEcVolume { stats := byCollection[counts.collection] if stats == nil { continue } stats.FileCount += counts.fileCount stats.DeleteCount += counts.deleteCount } ret := make([]*CollectionStatistics, 0, len(byCollection)) for _, stats := range byCollection { ret = append(ret, stats) } return ret }