datasource.go 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. package collector
  2. import (
  3. "context"
  4. "fmt"
  5. "strings"
  6. "time"
  7. "github.com/julienschmidt/httprouter"
  8. "github.com/opencost/opencost/core/pkg/clustercache"
  9. "github.com/opencost/opencost/core/pkg/clusters"
  10. "github.com/opencost/opencost/core/pkg/diagnostics"
  11. "github.com/opencost/opencost/core/pkg/external"
  12. "github.com/opencost/opencost/core/pkg/log"
  13. "github.com/opencost/opencost/core/pkg/nodestats"
  14. "github.com/opencost/opencost/core/pkg/source"
  15. "github.com/opencost/opencost/core/pkg/storage"
  16. "github.com/opencost/opencost/modules/collector-source/pkg/metric"
  17. "github.com/opencost/opencost/modules/collector-source/pkg/metric/synthetic"
  18. "github.com/opencost/opencost/modules/collector-source/pkg/scrape"
  19. "github.com/opencost/opencost/modules/collector-source/pkg/util"
  20. )
  21. type collectorDataSource struct {
  22. metricsQuerier *collectorMetricsQuerier
  23. clusterMap clusters.ClusterMap
  24. clusterInfo clusters.ClusterInfoProvider
  25. config CollectorConfig
  26. diagnosticsModule *metric.DiagnosticsModule
  27. wal *metric.Walinator
  28. }
  29. func NewDefaultCollectorDataSource(
  30. clusterUID string,
  31. store storage.Storage,
  32. clusterInfoProvider clusters.ClusterInfoProvider,
  33. clusterCache clustercache.ClusterCache,
  34. statSummaryClient nodestats.StatSummaryClient,
  35. externalLabelProvider external.LabelProvider,
  36. ) source.OpenCostDataSource {
  37. config := NewOpenCostCollectorConfigFromEnv(clusterUID)
  38. return NewCollectorDataSource(
  39. config,
  40. store,
  41. clusterInfoProvider,
  42. clusterCache,
  43. statSummaryClient,
  44. externalLabelProvider,
  45. )
  46. }
  47. func NewCollectorDataSource(
  48. config CollectorConfig,
  49. store storage.Storage,
  50. clusterInfoProvider clusters.ClusterInfoProvider,
  51. clusterCache clustercache.ClusterCache,
  52. statSummaryClient nodestats.StatSummaryClient,
  53. externalLabelProvider external.LabelProvider,
  54. ) source.OpenCostDataSource {
  55. var resolutions []*util.Resolution
  56. for _, resconf := range config.Resolutions {
  57. resolution, err := util.NewResolution(resconf)
  58. if err != nil {
  59. log.Errorf("failed to create resolution %s", err.Error())
  60. continue
  61. }
  62. resolutions = append(resolutions, resolution)
  63. }
  64. repo := metric.NewMetricRepository(
  65. resolutions,
  66. NewOpenCostMetricStore,
  67. )
  68. var updater metric.Updater
  69. updater = repo
  70. var walinator *metric.Walinator
  71. if store != nil {
  72. wal, err := metric.NewWalinator(
  73. config.ClusterName,
  74. config.ApplicationName,
  75. store,
  76. resolutions,
  77. updater,
  78. )
  79. if err != nil {
  80. log.Errorf("failed to initialize the walinator: %s", err.Error())
  81. } else {
  82. wal.Start()
  83. updater = wal
  84. walinator = wal
  85. }
  86. }
  87. // synthesizer collects specific metric types and generates new metrics to pass
  88. // along with the original metrics into the updater
  89. metricSynthesizer := synthetic.NewMetricSynthesizers(
  90. updater,
  91. synthetic.NewContainerMemoryAllocationSynthesizer(),
  92. synthetic.NewContainerCpuAllocationSynthesizer(),
  93. )
  94. updater = metricSynthesizer
  95. diagnosticsModule := metric.NewDiagnosticsModule()
  96. scrapeController := scrape.NewScrapeController(
  97. config.ClusterUID,
  98. config.ScrapeInterval,
  99. config.NetworkPort,
  100. updater,
  101. clusterInfoProvider,
  102. clusterCache,
  103. statSummaryClient,
  104. externalLabelProvider,
  105. )
  106. scrapeController.Start()
  107. metricQuerier := newCollectorMetricsQuerier(repo, config.Resolutions)
  108. // cluster info provider
  109. clusterInfo := clusterInfoProvider
  110. clusterMap := newCollectorClusterMap(clusterInfo)
  111. return &collectorDataSource{
  112. config: config,
  113. metricsQuerier: metricQuerier,
  114. clusterInfo: clusterInfo,
  115. clusterMap: clusterMap,
  116. diagnosticsModule: diagnosticsModule,
  117. wal: walinator,
  118. }
  119. }
  120. func (c *collectorDataSource) RegisterEndPoints(router *httprouter.Router) {
  121. }
  122. func (c *collectorDataSource) RegisterDiagnostics(diagService diagnostics.DiagnosticService) {
  123. const CollectorDiagnosticCategory = "collector"
  124. diagnosticDefinitions := c.diagnosticsModule.DiagnosticsDefinitions()
  125. for _, dd := range diagnosticDefinitions {
  126. err := diagService.Register(dd.MetricName, dd.Description, CollectorDiagnosticCategory, func(ctx context.Context) (map[string]any, error) {
  127. details, err := c.diagnosticsModule.DiagnosticsDetails(dd.ID)
  128. if err != nil {
  129. return nil, err
  130. }
  131. return details, nil
  132. })
  133. if err != nil {
  134. log.Warnf("Failed to register collector diagnostic %s: %s", dd.ID, err.Error())
  135. }
  136. }
  137. if c.wal != nil {
  138. err := diagService.Register(WALDiagnosticName, WALDiagnosticDescription, CollectorDiagnosticCategory, func(ctx context.Context) (map[string]any, error) {
  139. return walDiagnosticDetails(c.wal.Status())
  140. })
  141. if err != nil {
  142. log.Warnf("Failed to register collector diagnostic %s: %s", WALDiagnosticName, err.Error())
  143. }
  144. }
  145. }
  146. const (
  147. WALDiagnosticName = "Collector WAL"
  148. WALDiagnosticDescription = "Collector write-ahead log is persisting scrapes to storage and was fully restored at startup."
  149. )
  150. // walDiagnosticDetails converts a WAL status into diagnostic details, returning an error describing
  151. // the failure when writes are currently failing or the startup restore was incomplete.
  152. func walDiagnosticDetails(status source.WALStatus) (map[string]any, error) {
  153. var problems []string
  154. if status.ConsecutiveExportFailures > 0 {
  155. since := "no successful write since start"
  156. if !status.LastExportSuccess.IsZero() {
  157. since = "last successful write at " + status.LastExportSuccess.Format(time.RFC3339)
  158. }
  159. problems = append(problems, fmt.Sprintf("%d consecutive write failures, %s (last error: %s)",
  160. status.ConsecutiveExportFailures, since, status.LastExportError))
  161. }
  162. if status.RestoreListError != "" {
  163. problems = append(problems, fmt.Sprintf("restore could not list objects: %s", status.RestoreListError))
  164. }
  165. if status.RestoreErrors > 0 {
  166. problems = append(problems, fmt.Sprintf("restore failed to read %d of %d objects", status.RestoreErrors, status.RestoreObjectsSeen))
  167. }
  168. if len(problems) > 0 {
  169. return nil, fmt.Errorf("%s", strings.Join(problems, "; "))
  170. }
  171. return map[string]any{
  172. "lastExportSuccess": status.LastExportSuccess,
  173. "exportFailuresTotal": status.ExportFailuresTotal,
  174. "restoreCompleted": status.RestoreCompleted,
  175. "restoreObjectsApplied": status.RestoreObjectsApplied,
  176. "restoreDuration": status.RestoreDuration.String(),
  177. "restoreOldest": status.RestoreOldest,
  178. "restoreNewest": status.RestoreNewest,
  179. "restoreLargestGap": status.RestoreLargestGap.String(),
  180. "restoreLargestGapStart": status.RestoreLargestGapStart,
  181. "restoreTailGap": status.RestoreTailGap.String(),
  182. }, nil
  183. }
  184. // WALStatus implements source.WALStatusProvider, reporting the export and restore health of the
  185. // collector's write-ahead log.
  186. func (c *collectorDataSource) WALStatus() source.WALStatus {
  187. if c.wal == nil {
  188. return source.WALStatus{}
  189. }
  190. return c.wal.Status()
  191. }
  192. func (c *collectorDataSource) Metrics() source.MetricsQuerier {
  193. return c.metricsQuerier
  194. }
  195. func (c *collectorDataSource) ClusterMap() clusters.ClusterMap {
  196. return c.clusterMap
  197. }
  198. func (c *collectorDataSource) ClusterInfo() clusters.ClusterInfoProvider {
  199. return c.clusterInfo
  200. }
  201. // BatchDuration collector data source queries do not need to be broken up
  202. func (c *collectorDataSource) BatchDuration() time.Duration {
  203. var maxDuration time.Duration = 1<<63 - 1
  204. return maxDuration
  205. }
  206. func (c *collectorDataSource) Resolution() time.Duration {
  207. interval, _ := util.NewInterval(c.config.ScrapeInterval)
  208. current := interval.Truncate(time.Now().UTC())
  209. next := interval.Add(current, 1)
  210. return next.Sub(current)
  211. }