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  }