github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/stats/stats.go (about) 1 // Package stats defines a stats provider service, wrapping a cache & stats 2 // component calculation 3 package stats 4 5 import ( 6 "context" 7 "fmt" 8 9 logger "github.com/ipfs/go-log" 10 "github.com/qri-io/dataset" 11 "github.com/qri-io/dataset/detect" 12 "github.com/qri-io/dataset/dsio" 13 "github.com/qri-io/dataset/dsstats" 14 ) 15 16 var log = logger.Logger("stats") 17 18 // Service can generate an array of statistical info for a dataset 19 type Service struct { 20 cache Cache 21 } 22 23 // New allocates a Stats service 24 func New(cache Cache) *Service { 25 if cache == nil { 26 cache = nilCache(false) 27 } 28 29 return &Service{ 30 cache: cache, 31 } 32 } 33 34 // Stats gets the stats component for a dataset, possibly calculating 35 // by consuming the open dataset body file 36 func (s *Service) Stats(ctx context.Context, ds *dataset.Dataset) (*dataset.Stats, error) { 37 if ds.Stats != nil { 38 return ds.Stats, nil 39 } 40 41 key, err := s.cacheKey(ds) 42 if err != nil { 43 return nil, err 44 } 45 46 if sa, err := s.cache.GetStats(ctx, key); err == nil { 47 log.Debugw("found cached stats", "key", key) 48 return sa, nil 49 } 50 51 body := ds.BodyFile() 52 if body == nil { 53 return nil, fmt.Errorf("can't calculate stats. dataset has no body") 54 } 55 56 if ds.Structure == nil || ds.Structure.IsEmpty() { 57 log.Debugw("inferring structure to calculate stats") 58 ds.Structure = &dataset.Structure{} 59 if err := detect.Structure(ds); err != nil { 60 return nil, fmt.Errorf("inferring structure: %w", err) 61 } 62 } 63 64 rdr, err := dsio.NewEntryReader(ds.Structure, ds.BodyFile()) 65 if err != nil { 66 return nil, err 67 } 68 69 acc := dsstats.NewAccumulator(ds.Structure) 70 err = dsio.EachEntry(rdr, func(i int, ent dsio.Entry, e error) error { 71 return acc.WriteEntry(ent) 72 }) 73 if err != nil { 74 return nil, err 75 } 76 if err = acc.Close(); err != nil { 77 return nil, err 78 } 79 80 sa := &dataset.Stats{ 81 Qri: dataset.KindStats.String(), 82 Stats: dsstats.ToMap(acc), 83 } 84 85 if cacheErr := s.cache.PutStats(ctx, key, sa); cacheErr != nil { 86 log.Debugw("error caching stats", "path", ds.Path, "error", cacheErr) 87 } 88 89 return sa, nil 90 } 91 92 func (s *Service) cacheKey(ds *dataset.Dataset) (string, error) { 93 return ds.Path, nil 94 }