usage.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. package usage
  2. import (
  3. "sync"
  4. "time"
  5. "github.com/porter-dev/porter/api/server/shared/config/env"
  6. "github.com/porter-dev/porter/api/types"
  7. "github.com/porter-dev/porter/internal/adapter"
  8. "github.com/porter-dev/porter/internal/models"
  9. "github.com/porter-dev/porter/internal/oauth"
  10. "github.com/porter-dev/porter/internal/repository"
  11. "github.com/porter-dev/porter/internal/usage"
  12. "golang.org/x/oauth2"
  13. "gorm.io/gorm"
  14. rgorm "github.com/porter-dev/porter/internal/repository/gorm"
  15. )
  16. type UsageTracker struct {
  17. db *gorm.DB
  18. repo repository.Repository
  19. doConf *oauth2.Config
  20. whitelistedUsers map[uint]uint
  21. }
  22. type UsageTrackerOpts struct {
  23. DBConf *env.DBConf
  24. DOClientID string
  25. DOClientSecret string
  26. DOScopes []string
  27. ServerURL string
  28. WhitelistedUsers map[uint]uint
  29. }
  30. const stepSize = 100
  31. func NewUsageTracker(opts *UsageTrackerOpts) (*UsageTracker, error) {
  32. db, err := adapter.New(opts.DBConf)
  33. if err != nil {
  34. return nil, err
  35. }
  36. var key [32]byte
  37. for i, b := range []byte(opts.DBConf.EncryptionKey) {
  38. key[i] = b
  39. }
  40. repo := rgorm.NewRepository(db, &key)
  41. doConf := oauth.NewDigitalOceanClient(&oauth.Config{
  42. ClientID: opts.DOClientID,
  43. ClientSecret: opts.DOClientSecret,
  44. Scopes: opts.DOScopes,
  45. BaseURL: opts.ServerURL,
  46. })
  47. return &UsageTracker{db, repo, doConf, opts.WhitelistedUsers}, nil
  48. }
  49. type UsageTrackerResponse struct {
  50. CPULimit uint
  51. CPUUsage uint
  52. MemoryLimit uint
  53. MemoryUsage uint
  54. UserLimit uint
  55. UserUsage uint
  56. ClusterLimit uint
  57. ClusterUsage uint
  58. Exceeded bool
  59. ExceededSince time.Time
  60. Project models.Project
  61. AdminEmails []string
  62. }
  63. func (u *UsageTracker) GetProjectUsage() (map[uint]*UsageTrackerResponse, error) {
  64. res := make(map[uint]*UsageTrackerResponse)
  65. // get the count of the projects
  66. var count int64
  67. if err := u.db.Model(&models.Project{}).Count(&count).Error; err != nil {
  68. return nil, err
  69. }
  70. var mu sync.Mutex
  71. var wg sync.WaitGroup
  72. worker := func(project *models.Project) error {
  73. defer wg.Done()
  74. current, limit, cache, err := usage.GetUsage(&usage.GetUsageOpts{
  75. Repo: u.repo,
  76. DOConf: u.doConf,
  77. Project: project,
  78. WhitelistedUsers: u.whitelistedUsers,
  79. })
  80. if err != nil {
  81. return err
  82. }
  83. // get the admin emails for the project
  84. roles, err := u.repo.Project().ListProjectRoles(project.ID)
  85. if err != nil {
  86. return err
  87. }
  88. adminEmails := make([]string, 0)
  89. for _, role := range roles {
  90. if role.Kind == types.RoleAdmin {
  91. user, err := u.repo.User().ReadUser(role.UserID)
  92. if err != nil {
  93. continue
  94. }
  95. adminEmails = append(adminEmails, user.Email)
  96. }
  97. }
  98. exceededSince := cache.ExceededSince
  99. if exceededSince == nil {
  100. now := time.Now()
  101. exceededSince = &now
  102. }
  103. mu.Lock()
  104. res[project.ID] = &UsageTrackerResponse{
  105. CPUUsage: cache.ResourceCPU,
  106. CPULimit: limit.ResourceCPU,
  107. MemoryUsage: cache.ResourceMemory,
  108. MemoryLimit: limit.ResourceMemory,
  109. UserUsage: current.Users,
  110. UserLimit: limit.Users,
  111. ClusterUsage: current.Clusters,
  112. ClusterLimit: limit.Clusters,
  113. Exceeded: cache.Exceeded,
  114. ExceededSince: *exceededSince,
  115. Project: *project,
  116. AdminEmails: adminEmails,
  117. }
  118. mu.Unlock()
  119. return nil
  120. }
  121. // iterate (count / stepSize) + 1 times using Limit and Offset
  122. for i := 0; i < (int(count)/stepSize)+1; i++ {
  123. projects := []*models.Project{}
  124. if err := u.db.Order("id asc").Offset(i * stepSize).Limit(stepSize).Find(&projects).Error; err != nil {
  125. return nil, err
  126. }
  127. // go through each project
  128. for _, project := range projects {
  129. wg.Add(1)
  130. go worker(project)
  131. }
  132. wg.Wait()
  133. }
  134. return res, nil
  135. }