|
|
@@ -2,44 +2,29 @@ package metric
|
|
|
|
|
|
import (
|
|
|
"fmt"
|
|
|
- "path"
|
|
|
- "sort"
|
|
|
- "strings"
|
|
|
"sync"
|
|
|
"time"
|
|
|
|
|
|
- "github.com/opencost/opencost/core/pkg/exporter"
|
|
|
- "github.com/opencost/opencost/core/pkg/exporter/pathing"
|
|
|
"github.com/opencost/opencost/core/pkg/log"
|
|
|
- "github.com/opencost/opencost/core/pkg/storage"
|
|
|
- "github.com/opencost/opencost/core/pkg/util/json"
|
|
|
"github.com/opencost/opencost/modules/collector-source/pkg/util"
|
|
|
)
|
|
|
|
|
|
-const ControllerEventName = "controller"
|
|
|
-
|
|
|
-type RepositoryConfig struct {
|
|
|
-}
|
|
|
-
|
|
|
// MetricRepository is an MetricUpdater which applies calls to update to all resolutions being tracked. It holds the
|
|
|
// MetricStore instances for each resolution.
|
|
|
type MetricRepository struct {
|
|
|
lock sync.Mutex
|
|
|
resolutionStores map[string]*resolutionStores
|
|
|
- exporter exporter.EventExporter[UpdateSet]
|
|
|
}
|
|
|
|
|
|
func NewMetricRepository(
|
|
|
- clusterID string,
|
|
|
- resolutions []util.ResolutionConfiguration,
|
|
|
- store storage.Storage,
|
|
|
+ resolutions []*util.Resolution,
|
|
|
storeFactory MetricStoreFactory,
|
|
|
) *MetricRepository {
|
|
|
resoluationCollectors := make(map[string]*resolutionStores)
|
|
|
- for _, resconf := range resolutions {
|
|
|
- resolution, err := util.NewResolution(resconf)
|
|
|
- if err != nil {
|
|
|
- log.Errorf("failed to create resolution %s", err.Error())
|
|
|
+ var limitResolution *util.Resolution
|
|
|
+ for _, resolution := range resolutions {
|
|
|
+ if limitResolution == nil || resolution.Limit().Before(limitResolution.Limit()) {
|
|
|
+ limitResolution = resolution
|
|
|
}
|
|
|
resCollector, err := newResolutionStores(resolution, storeFactory)
|
|
|
if err != nil {
|
|
|
@@ -49,76 +34,9 @@ func NewMetricRepository(
|
|
|
resoluationCollectors[resolution.Interval()] = resCollector
|
|
|
}
|
|
|
|
|
|
- repo := &MetricRepository{
|
|
|
+ return &MetricRepository{
|
|
|
resolutionStores: resoluationCollectors,
|
|
|
}
|
|
|
-
|
|
|
- if store != nil {
|
|
|
- pathFormatter, err := pathing.NewEventStoragePathFormatter("", clusterID, ControllerEventName)
|
|
|
- if err != nil {
|
|
|
- log.Errorf("filed to create path formatter for scrape controller: %s", err.Error())
|
|
|
- return repo
|
|
|
- }
|
|
|
- encoder := exporter.NewJSONEncoder[UpdateSet]()
|
|
|
- repo.exporter = exporter.NewEventStorageExporter(
|
|
|
- pathFormatter,
|
|
|
- encoder,
|
|
|
- store,
|
|
|
- )
|
|
|
- // attempt to restore state from files
|
|
|
- // get path of saved files
|
|
|
- dirPath := path.Dir(pathFormatter.ToFullPath("", time.Time{}, ""))
|
|
|
- files, err := store.List(dirPath)
|
|
|
- if err != nil {
|
|
|
- log.Errorf("failed to list files in scrape controller: %s", err.Error())
|
|
|
- }
|
|
|
- // find oldest limit
|
|
|
- limit := time.Now().UTC()
|
|
|
- for _, resStore := range repo.resolutionStores {
|
|
|
- if limit.After(resStore.resolution.Limit()) {
|
|
|
- limit = resStore.resolution.Limit()
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // find files that are within limit
|
|
|
- var filesToRun []string
|
|
|
- for _, file := range files {
|
|
|
- fileName := path.Base(file.Name)
|
|
|
- timeString := strings.TrimSuffix(fileName, "."+encoder.FileExt())
|
|
|
- timestamp, err := time.Parse(pathing.EventStorageTimeFormat, timeString)
|
|
|
- if err != nil {
|
|
|
- log.Errorf("failed to parse fileName %s: %s", fileName, err.Error())
|
|
|
- continue
|
|
|
- }
|
|
|
- if timestamp.After(limit) {
|
|
|
- filesToRun = append(filesToRun, pathFormatter.ToFullPath("", timestamp, encoder.FileExt()))
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // sort files
|
|
|
- sort.Strings(filesToRun)
|
|
|
-
|
|
|
- // open files and run updates
|
|
|
- for _, fileName := range filesToRun {
|
|
|
- b, err := store.Read(fileName)
|
|
|
- if err != nil {
|
|
|
- log.Errorf("failed to load file contents for '%s': %s", fileName, err.Error())
|
|
|
- continue
|
|
|
- }
|
|
|
- updateSet := UpdateSet{}
|
|
|
- err = json.Unmarshal(b, &updateSet)
|
|
|
- if err != nil {
|
|
|
- log.Errorf("failed to unmarshal file %s: %s", fileName, err.Error())
|
|
|
- continue
|
|
|
- }
|
|
|
- filePrefix := path.Base(fileName)
|
|
|
- timeString := strings.TrimSuffix(filePrefix, "."+encoder.FileExt())
|
|
|
- timestamp, err := time.Parse(pathing.EventStorageTimeFormat, timeString)
|
|
|
- repo.Update(updateSet.Updates, timestamp)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return repo
|
|
|
}
|
|
|
|
|
|
func (r *MetricRepository) GetCollector(interval string, t time.Time) (MetricStore, error) {
|
|
|
@@ -147,26 +65,6 @@ func (r *MetricRepository) Update(
|
|
|
resCollector.update(update.Name, update.Labels, update.Value, timestamp, update.AdditionalInfo)
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- if r.exporter != nil {
|
|
|
- err := r.exporter.Export(timestamp, &UpdateSet{
|
|
|
- Updates: updates,
|
|
|
- })
|
|
|
- if err != nil {
|
|
|
- log.Errorf("failed to export update results: %s", err.Error())
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-type UpdateSet struct {
|
|
|
- Updates []Update `json:"updates"`
|
|
|
-}
|
|
|
-
|
|
|
-type Update struct {
|
|
|
- Name string `json:"name"`
|
|
|
- Labels map[string]string `json:"labels"`
|
|
|
- Value float64 `json:"value"`
|
|
|
- AdditionalInfo map[string]string `json:"additionalInfo"`
|
|
|
}
|
|
|
|
|
|
func (r *MetricRepository) Coverage() map[string][]time.Time {
|