repositoryquerier.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. package cloudcost
  2. import (
  3. "context"
  4. "fmt"
  5. "sort"
  6. "github.com/opencost/opencost/pkg/kubecost"
  7. "github.com/opencost/opencost/pkg/log"
  8. )
  9. // RepositoryQuerier is an implementation of Querier and ViewQuerier which pulls directly from a Repository
  10. type RepositoryQuerier struct {
  11. repo Repository
  12. }
  13. func NewRepositoryQuerier(repo Repository) *RepositoryQuerier {
  14. return &RepositoryQuerier{repo: repo}
  15. }
  16. func (rq *RepositoryQuerier) Query(request QueryRequest, ctx context.Context) (*kubecost.CloudCostSetRange, error) {
  17. repoKeys, err := rq.repo.Keys()
  18. if err != nil {
  19. return nil, fmt.Errorf("RepositoryQuerier: Query: failed to get list of keys from repository: %w", err)
  20. }
  21. // create filter
  22. compiler := kubecost.NewCloudCostMatchCompiler()
  23. matcher, err := compiler.Compile(request.Filter)
  24. if err != nil {
  25. return nil, fmt.Errorf("RepositoryQuerier: Query: failed to compile filters: %w", err)
  26. }
  27. // Create a Cloud Cost Set Range in the resolution of the repository
  28. ccsr, err := kubecost.NewCloudCostSetRange(request.Start, request.End, kubecost.AccumulateOptionDay, "")
  29. if err != nil {
  30. return nil, fmt.Errorf("RepositoryQuerier: Query: failed to create Cloud Cost Set Range: %w", err)
  31. }
  32. for _, cloudCostSet := range ccsr.CloudCostSets {
  33. // Setting this values creates
  34. cloudCostSet.AggregationProperties = request.AggregateBy
  35. for _, key := range repoKeys {
  36. ccs, err := rq.repo.Get(cloudCostSet.Window.Start().UTC(), key)
  37. if err != nil {
  38. log.Errorf("RepositoryQuerier: Query: %s", err.Error())
  39. continue
  40. }
  41. if ccs == nil {
  42. continue
  43. }
  44. for _, cc := range ccs.CloudCosts {
  45. if matcher.Matches(cc) {
  46. cloudCostSet.Insert(cc)
  47. }
  48. }
  49. }
  50. }
  51. if request.Accumulate != kubecost.AccumulateOptionNone {
  52. ccsr, err = ccsr.Accumulate(request.Accumulate)
  53. if err != nil {
  54. return nil, fmt.Errorf("RepositoryQuerier: Query: error accumulating: %w", err)
  55. }
  56. }
  57. return ccsr, nil
  58. }
  59. func (rq *RepositoryQuerier) QueryViewGraph(request ViewQueryRequest, ctx context.Context) (ViewGraphData, error) {
  60. ccasr, err := rq.Query(request.QueryRequest, ctx)
  61. if err != nil {
  62. return nil, fmt.Errorf("QueryViewGraph: query failed: %w", err)
  63. }
  64. if ccasr.IsEmpty() {
  65. return make([]*ViewGraphDataSet, 0), nil
  66. }
  67. var sets ViewGraphData
  68. for _, ccas := range ccasr.CloudCostSets {
  69. items := make([]ViewGraphDataSetItem, 0)
  70. for key, cc := range ccas.CloudCosts {
  71. costMetric, err := cc.GetCostMetric(request.CostMetricName)
  72. if err != nil {
  73. return nil, fmt.Errorf("QueryViewGraph: failed to get cost metric: %w", err)
  74. }
  75. items = append(items, ViewGraphDataSetItem{
  76. Name: key,
  77. Value: costMetric.Cost,
  78. })
  79. }
  80. sort.SliceStable(items, func(i, j int) bool {
  81. return items[i].Value > items[j].Value
  82. })
  83. if len(items) > request.ChartItemsLength {
  84. otherItems := items[request.ChartItemsLength:]
  85. newItems := items[:request.ChartItemsLength]
  86. // Rename last item other and add all other values into it
  87. newItems[request.ChartItemsLength-1].Name = "Other"
  88. for _, item := range otherItems {
  89. newItems[request.ChartItemsLength-1].Value += item.Value
  90. }
  91. items = newItems
  92. }
  93. sets = append(sets, &ViewGraphDataSet{
  94. Start: *ccas.Window.Start(),
  95. End: *ccas.Window.End(),
  96. Items: items,
  97. })
  98. }
  99. return sets, nil
  100. }
  101. func (rq *RepositoryQuerier) QueryViewTotals(request ViewQueryRequest, ctx context.Context) (*ViewTableRow, int, error) {
  102. ccasr, err := rq.Query(request.QueryRequest, ctx)
  103. if err != nil {
  104. return nil, -1, fmt.Errorf("QueryViewTotals: query failed: %w", err)
  105. }
  106. acc, err := ccasr.AccumulateAll()
  107. if err != nil {
  108. return nil, -1, fmt.Errorf("QueryViewTotals: accumulate failed: %w", err)
  109. }
  110. if acc.IsEmpty() {
  111. return nil, 0, nil
  112. }
  113. count := len(acc.CloudCosts)
  114. total, err := acc.Aggregate([]string{})
  115. if err != nil {
  116. return nil, -1, fmt.Errorf("QueryViewTotals: aggregate total failed: %w", err)
  117. }
  118. if total.IsEmpty() {
  119. return nil, -1, fmt.Errorf("QueryViewTotals: missing total: %w", err)
  120. }
  121. if len(total.CloudCosts) != 1 {
  122. return nil, -1, fmt.Errorf("QueryViewTotals: total did not aggregate: %w", err)
  123. }
  124. cm, err := total.CloudCosts[""].GetCostMetric(request.CostMetricName)
  125. if err != nil {
  126. return nil, -1, fmt.Errorf("QueryViewTotals: failed to retrieve cost metric: %w", err)
  127. }
  128. return &ViewTableRow{
  129. Name: "Totals",
  130. KubernetesPercent: cm.KubernetesPercent,
  131. Cost: cm.Cost,
  132. }, count, nil
  133. }
  134. func (rq *RepositoryQuerier) QueryViewTable(request ViewQueryRequest, ctx context.Context) (ViewTableRows, error) {
  135. ccasr, err := rq.Query(request.QueryRequest, ctx)
  136. if err != nil {
  137. return nil, fmt.Errorf("QueryViewTable: query failed: %w", err)
  138. }
  139. acc, err := ccasr.AccumulateAll()
  140. if err != nil {
  141. return nil, fmt.Errorf("QueryViewTable: accumulate failed: %w", err)
  142. }
  143. var rows ViewTableRows
  144. for key, cloudCost := range acc.CloudCosts {
  145. costMetric, err2 := cloudCost.GetCostMetric(request.CostMetricName)
  146. if err2 != nil {
  147. return nil, fmt.Errorf("QueryViewTable: failed to retrieve cost metric: %w", err)
  148. }
  149. vtr := &ViewTableRow{
  150. Name: key,
  151. KubernetesPercent: costMetric.KubernetesPercent,
  152. Cost: costMetric.Cost,
  153. }
  154. rows = append(rows, vtr)
  155. }
  156. // Sort Results
  157. // Sort by Name to ensure consistent return
  158. sort.SliceStable(rows, func(i, j int) bool {
  159. return rows[i].Name > rows[j].Name
  160. })
  161. switch request.SortColumn {
  162. case SortFieldName:
  163. if request.SortDirection == SortDirectionAscending {
  164. sort.SliceStable(rows, func(i, j int) bool {
  165. return rows[i].Name < rows[j].Name
  166. })
  167. }
  168. case SortFieldCost:
  169. if request.SortDirection == SortDirectionAscending {
  170. sort.SliceStable(rows, func(i, j int) bool {
  171. return rows[i].Cost < rows[j].Cost
  172. })
  173. } else {
  174. sort.SliceStable(rows, func(i, j int) bool {
  175. return rows[i].Cost > rows[j].Cost
  176. })
  177. }
  178. case SortFieldKubernetesPercent:
  179. if request.SortDirection == SortDirectionAscending {
  180. sort.SliceStable(rows, func(i, j int) bool {
  181. return rows[i].KubernetesPercent < rows[j].KubernetesPercent
  182. })
  183. } else {
  184. sort.SliceStable(rows, func(i, j int) bool {
  185. return rows[i].KubernetesPercent > rows[j].KubernetesPercent
  186. })
  187. }
  188. default:
  189. return nil, fmt.Errorf("invalid sort field '%s'", string(request.SortColumn))
  190. }
  191. // paginate sorted results
  192. if request.Offset > len(rows) {
  193. return make([]*ViewTableRow, 0), nil
  194. }
  195. if request.Limit > 0 {
  196. limit := request.Offset + request.Limit
  197. if limit > len(rows) {
  198. return rows[request.Offset:], nil
  199. }
  200. return rows[request.Offset:limit], nil
  201. }
  202. if request.Offset > 0 {
  203. return rows[request.Offset:], nil
  204. }
  205. return rows, nil
  206. }