querier.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. package customcost
  2. import (
  3. "context"
  4. "fmt"
  5. "time"
  6. "github.com/opencost/opencost/core/pkg/opencost"
  7. "github.com/opencost/opencost/core/pkg/util/timeutil"
  8. "github.com/opencost/opencost/pkg/env"
  9. )
  10. type Querier interface {
  11. QueryTotal(ctx context.Context, request CostTotalRequest) (*CostResponse, error)
  12. QueryTimeseries(ctx context.Context, request CostTimeseriesRequest) (*CostTimeseriesResponse, error)
  13. }
  14. func GetCustomCostWindowAccumulation(window opencost.Window, accumulate opencost.AccumulateOption) (opencost.Window, opencost.AccumulateOption, error) {
  15. var err error
  16. if accumulate == opencost.AccumulateOptionNone {
  17. accumulate, err = getCustomCostAccumulateOption(window, nil)
  18. if err != nil {
  19. return opencost.Window{}, opencost.AccumulateOptionNone, fmt.Errorf("failed to determine custom cost accumulation option: %v", err)
  20. }
  21. }
  22. window, err = window.GetAccumulateWindow(accumulate)
  23. if err != nil {
  24. return opencost.Window{}, opencost.AccumulateOptionNone, fmt.Errorf("failed to determine custom cost accumulation option: %v", err)
  25. }
  26. return window, accumulate, nil
  27. }
  28. // getCustomCostAccumulateOption determines defaults in a way that matches options presented in the UI
  29. func getCustomCostAccumulateOption(window opencost.Window, from []opencost.AccumulateOption) (opencost.AccumulateOption, error) {
  30. if window.IsOpen() || window.IsNegative() {
  31. return opencost.AccumulateOptionNone, fmt.Errorf("invalid window '%s'", window.String())
  32. }
  33. if len(from) == 0 {
  34. from = allSteppedAccumulateOptions
  35. }
  36. hourlyStoreHours := env.GetDataRetentionHourlyResolutionHours()
  37. hourlySteps := time.Duration(hourlyStoreHours) * time.Hour
  38. oldestHourly := time.Now().Add(-1 * hourlySteps)
  39. // Use hourly if...
  40. // (1) hourly is an option;
  41. // (2) we have hourly store coverage; and
  42. // (3) the window duration is less than the hourly break point.
  43. if hasHourly(from) && oldestHourly.Before(*window.Start()) && window.Duration() <= hourlySteps {
  44. return opencost.AccumulateOptionHour, nil
  45. }
  46. dailyStoreDays := env.GetDataRetentionDailyResolutionDays()
  47. dailySteps := time.Duration(dailyStoreDays) * timeutil.Day
  48. oldestDaily := time.Now().Add(-1 * dailySteps)
  49. // Use daily if...
  50. // (1) daily is an option
  51. // It is acceptable to query a range for which we only have partial data
  52. if hasDaily(from) {
  53. return opencost.AccumulateOptionDay, nil
  54. }
  55. if oldestDaily.After(*window.Start()) {
  56. return opencost.AccumulateOptionDay, fmt.Errorf("data store does not have coverage for %v", window)
  57. }
  58. return opencost.AccumulateOptionNone, fmt.Errorf("no valid accumulate option in %v for %s", from, window)
  59. }
  60. var allSteppedAccumulateOptions = []opencost.AccumulateOption{
  61. opencost.AccumulateOptionHour,
  62. opencost.AccumulateOptionDay,
  63. }
  64. func hasHourly(opts []opencost.AccumulateOption) bool {
  65. for _, opt := range opts {
  66. if opt == opencost.AccumulateOptionHour {
  67. return true
  68. }
  69. }
  70. return false
  71. }
  72. func hasDaily(opts []opencost.AccumulateOption) bool {
  73. for _, opt := range opts {
  74. if opt == opencost.AccumulateOptionDay {
  75. return true
  76. }
  77. }
  78. return false
  79. }