| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112 |
- package collector
- import (
- "fmt"
- "time"
- "github.com/opencost/opencost/core/pkg/log"
- "github.com/opencost/opencost/core/pkg/util/timeutil"
- "github.com/opencost/opencost/modules/collector-source/pkg/metric"
- "github.com/opencost/opencost/modules/collector-source/pkg/util"
- )
- // StoreProvider returns an appropriate collector for the given window. This is meant to bridge the mismatch of a system
- // that was designed to make queries against a continuous datasource with now stores its data in discrete blocks
- type StoreProvider interface {
- GetStore(start, end time.Time) metric.MetricStore
- GetDailyDataCoverage(limitDays int) (time.Time, time.Time, error)
- }
- // repoStoreProvider is a StoreProvider implementation which uses a Repository and the Intervals of its Resolutions that it is
- // configured with to return the most appropriate time.
- type repoStoreProvider struct {
- repo *metric.MetricRepository
- intervals map[string]util.Interval
- }
- func newRepoStoreProvider(repo *metric.MetricRepository, resoluationConfigs []util.ResolutionConfiguration) *repoStoreProvider {
- intervals := make(map[string]util.Interval)
- for _, resConf := range resoluationConfigs {
- interval, err := util.NewInterval(resConf.Interval)
- if err != nil {
- continue
- }
- intervals[resConf.Interval] = interval
- }
- return &repoStoreProvider{
- repo: repo,
- intervals: intervals,
- }
- }
- func (r *repoStoreProvider) GetStore(start, end time.Time) metric.MetricStore {
- resKey, start := r.getStoreKeys(start, end)
- store, err := r.repo.GetCollector(resKey, start)
- if err != nil {
- log.Debugf("failed to get Store for window '%s - %s': %s", start, end, err)
- }
- return store
- }
- // getStoreKeys compares the given start and end against each resolution by truncating the start time and
- // add one interval to the truncated value. The duration between start and end is compared with the duration
- // between the interval generated times, with the lowest
- func (r *repoStoreProvider) getStoreKeys(start, end time.Time) (string, time.Time) {
- windowDuration := int64(end.Sub(start))
- var minDiff *int64
- var minKey string
- var minStart time.Time
- for key, interval := range r.intervals {
- intStart := interval.Truncate(start)
- intEnd := interval.Add(start, 1)
- intDuration := int64(intEnd.Sub(intStart))
- diffDuration := windowDuration - intDuration
- if diffDuration < 0 {
- diffDuration = -diffDuration
- }
- if minDiff == nil || diffDuration < *minDiff {
- minDiff = &diffDuration
- minKey = key
- minStart = intStart
- }
- }
- return minKey, minStart
- }
- // GetDailyDataCoverage this is a bit of a hacky add-on to help fulfill the metricsquerier interface
- func (r *repoStoreProvider) GetDailyDataCoverage(limitDays int) (time.Time, time.Time, error) {
- coverage := r.repo.Coverage()
- dailyCoverage, ok := coverage["1d"]
- if !ok {
- return time.Time{}, time.Time{}, fmt.Errorf("daily resolution is not configured")
- }
- if len(dailyCoverage) == 0 {
- // If daily coverage is not available, fallback to a reasonable time range
- // This prevents CSV export from failing when the metric doesn't exist yet
- log.Warnf("GetDailyDataCoverage: daily coverage not available, using fallback time range")
- // Use a reasonable fallback: start from 1 day ago to account for metric collection delay
- fallbackEnd := time.Now().UTC().Truncate(timeutil.Day)
- fallbackStart := fallbackEnd.AddDate(0, 0, -1) // 1 day ago
- return fallbackStart, fallbackEnd, nil
- }
- start := dailyCoverage[0]
- end := dailyCoverage[0]
- for _, window := range dailyCoverage {
- if start.After(window) {
- start = window
- }
- if end.Before(window) {
- end = window
- }
- }
- limit := time.Now().UTC().Truncate(timeutil.Day).Add(-timeutil.Day * time.Duration(limitDays))
- if start.Before(limit) {
- start = limit
- }
- // since all times that we have been looking at are window start times,
- // add a day to end time to create the actual coverage
- end = end.Add(timeutil.Day)
- return start, end, nil
- }
|