Skip to content

Commit

Permalink
Merge pull request #127 from whywaita/feat/metrics-rate-limit
Browse files Browse the repository at this point in the history
Add rate limit metrics
  • Loading branch information
whywaita authored Jan 17, 2022
2 parents a18b50d + fc27460 commit f62d487
Show file tree
Hide file tree
Showing 8 changed files with 127 additions and 9 deletions.
10 changes: 10 additions & 0 deletions pkg/gh/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"net/url"
"path"
"strings"
"sync"
"time"

"github.com/bradleyfalzon/ghinstallation/v2"
Expand All @@ -25,11 +26,19 @@ var (

// ResponseCache is cache variable
responseCache *cache.Cache

// rateLimitRemain is remaining of Rate limit, for metrics
rateLimitRemain = sync.Map{}
// rateLimitLimit is limit of Rate limit, for metrics
rateLimitLimit = sync.Map{}
)

func init() {
c := cache.New(5*time.Minute, 10*time.Minute)
responseCache = c

rateLimitRemain = sync.Map{}
rateLimitLimit = sync.Map{}
}

// NewClient create a client of GitHub
Expand Down Expand Up @@ -170,6 +179,7 @@ func ListRunners(ctx context.Context, client *github.Client, owner, repo string)
if err != nil {
return nil, fmt.Errorf("failed to list runners: %w", err)
}
storeRateLimit(getRateLimitKey(owner, repo), resp.Rate)

rs = append(rs, runners.Runners...)
if resp.NextPage == 0 {
Expand Down
11 changes: 6 additions & 5 deletions pkg/gh/jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,12 @@ var (

// GenerateGitHubAppsToken generate token of GitHub Apps using private key
// clientApps needs to response of `NewClientGitHubApps()`
func GenerateGitHubAppsToken(ctx context.Context, clientApps *github.Client, installationID int64) (string, *time.Time, error) {
token, _, err := clientApps.Apps.CreateInstallationToken(ctx, installationID, nil)
func GenerateGitHubAppsToken(ctx context.Context, clientApps *github.Client, installationID int64, scope string) (string, *time.Time, error) {
token, resp, err := clientApps.Apps.CreateInstallationToken(ctx, installationID, nil)
if err != nil {
return "", nil, fmt.Errorf("failed to generate token from API: %w", err)
}
storeRateLimit(scope, resp.Rate)
return *token.Token, token.ExpiresAt, nil
}

Expand Down Expand Up @@ -97,7 +98,7 @@ func IsInstalledGitHubApp(ctx context.Context, gheDomain, inputScope string) (in
}

func isInstalledGitHubAppSelected(ctx context.Context, gheDomain, inputScope string, installationID int64) error {
lr, err := GHlistAppsInstalledRepo(ctx, gheDomain, installationID)
lr, err := GHlistAppsInstalledRepo(ctx, gheDomain, installationID, inputScope)
if err != nil {
return fmt.Errorf("failed to get list of installed repositories: %w", err)
}
Expand All @@ -123,12 +124,12 @@ func isInstalledGitHubAppSelected(ctx context.Context, gheDomain, inputScope str
return fmt.Errorf("not found")
}

func listAppsInstalledRepo(ctx context.Context, gheDomain string, installationID int64) (*github.ListRepositories, error) {
func listAppsInstalledRepo(ctx context.Context, gheDomain string, installationID int64, inputScope string) (*github.ListRepositories, error) {
clientApps, err := NewClientGitHubApps(gheDomain, config.Config.GitHub.AppID, config.Config.GitHub.PEMByte)
if err != nil {
return nil, fmt.Errorf("failed to create github.Client from installationID: %w", err)
}
token, _, err := GenerateGitHubAppsToken(ctx, clientApps, installationID)
token, _, err := GenerateGitHubAppsToken(ctx, clientApps, installationID, inputScope)
if err != nil {
return nil, fmt.Errorf("failed to generate GitHub Apps Token: %w", err)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/gh/jwt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func setStubFunctions() {
}, nil
}

GHlistAppsInstalledRepo = func(ctx context.Context, gheDomain string, installationID int64) (*github.ListRepositories, error) {
GHlistAppsInstalledRepo = func(ctx context.Context, gheDomain string, installationID int64, scope string) (*github.ListRepositories, error) {
total := 1
fullName1 := "example-selected/sample-registered"
return &github.ListRepositories{
Expand Down
75 changes: 75 additions & 0 deletions pkg/gh/ratelimit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package gh

import (
"fmt"
"sync"

"github.com/google/go-github/v35/github"
)

func storeRateLimit(scope string, rateLimit github.Rate) {
if rateLimit.Reset.IsZero() {
// Not configure rate limit, don't need to store (e.g. GHES)
return
}

rateLimitLimit.Store(scope, rateLimit.Limit)
rateLimitRemain.Store(scope, rateLimit.Remaining)
}

func getRateLimitKey(org, repo string) string {
if repo == "" {
return org
}
return fmt.Sprintf("%s/%s", org, repo)
}

// GetRateLimitRemain get a list of rate limit remaining
// key: scope, value: remain
func GetRateLimitRemain() map[string]int {
m := map[string]int{}
mu := sync.Mutex{}

rateLimitRemain.Range(func(key, value interface{}) bool {
k, ok := key.(string)
if !ok {
return false
}
v, ok := value.(int)
if !ok {
return false
}

mu.Lock()
m[k] = v
mu.Unlock()
return true
})

return m
}

// GetRateLimitLimit get a list of rate limit
// key: scope, value: remain
func GetRateLimitLimit() map[string]int {
m := map[string]int{}
mu := sync.Mutex{}

rateLimitLimit.Range(func(key, value interface{}) bool {
k, ok := key.(string)
if !ok {
return false
}
v, ok := value.(int)
if !ok {
return false
}

mu.Lock()
m[k] = v
mu.Unlock()
return true
})

return m
}
32 changes: 32 additions & 0 deletions pkg/metric/scrape_memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/whywaita/myshoes/internal/config"
"github.com/whywaita/myshoes/pkg/datastore"
"github.com/whywaita/myshoes/pkg/gh"
"github.com/whywaita/myshoes/pkg/starter"
)

Expand All @@ -29,6 +30,16 @@ var (
"waiting queue in starter",
[]string{"starter"}, nil,
)
memoryGitHubRateLimitRemaining = prometheus.NewDesc(
prometheus.BuildFQName(namespace, memoryName, "github_rate_limit_remaining"),
"The number of rate limit remaining",
[]string{"scope"}, nil,
)
memoryGitHubRateLimitLimiting = prometheus.NewDesc(
prometheus.BuildFQName(namespace, memoryName, "github_rate_limit_limiting"),
"The number of rate limit max",
[]string{"scope"}, nil,
)
)

// ScraperMemory is scraper implement for memory
Expand All @@ -49,6 +60,9 @@ func (ScraperMemory) Scrape(ctx context.Context, ds datastore.Datastore, ch chan
if err := scrapeStarterValues(ch); err != nil {
return fmt.Errorf("failed to scrape starter values: %w", err)
}
if err := scrapeGitHubValues(ch); err != nil {
return fmt.Errorf("failed to scrape GitHub values: %w", err)
}

return nil
}
Expand All @@ -72,4 +86,22 @@ func scrapeStarterValues(ch chan<- prometheus.Metric) error {
return nil
}

func scrapeGitHubValues(ch chan<- prometheus.Metric) error {
rateLimitRemain := gh.GetRateLimitRemain()
for scope, remain := range rateLimitRemain {
ch <- prometheus.MustNewConstMetric(
memoryGitHubRateLimitRemaining, prometheus.GaugeValue, float64(remain), scope,
)
}

rateLimitLimit := gh.GetRateLimitLimit()
for scope, limit := range rateLimitLimit {
ch <- prometheus.MustNewConstMetric(
memoryGitHubRateLimitLimiting, prometheus.GaugeValue, float64(limit), scope,
)
}

return nil
}

var _ Scraper = ScraperMemory{}
2 changes: 1 addition & 1 deletion pkg/runner/token_update.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func (m *Manager) doTargetToken(ctx context.Context) error {
logger.Logf(false, "failed to create client of GitHub installation: %+v", err)
continue
}
token, expiredAt, err := gh.GenerateGitHubAppsToken(ctx, clientApps, installationID)
token, expiredAt, err := gh.GenerateGitHubAppsToken(ctx, clientApps, installationID, target.Scope)
if err != nil {
logger.Logf(false, "failed to get Apps Token: %+v", err)
continue
Expand Down
2 changes: 1 addition & 1 deletion pkg/web/target_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func handleTargetCreate(w http.ResponseWriter, r *http.Request, ds datastore.Dat
outputErrorMsg(w, http.StatusInternalServerError, "failed to client GitHub Apps")
return
}
token, expiredAt, err := GHGenerateGitHubAppsToken(ctx, clientApps, installationID)
token, expiredAt, err := GHGenerateGitHubAppsToken(ctx, clientApps, installationID, inputTarget.Scope)
if err != nil {
logger.Logf(false, "failed to generate GitHub Apps Token: %+v", err)
outputErrorMsg(w, http.StatusInternalServerError, "failed to generate GitHub Apps token")
Expand Down
2 changes: 1 addition & 1 deletion pkg/web/target_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func setStubFunctions() {
return testInstallationID, nil
}

web.GHGenerateGitHubAppsToken = func(ctx context.Context, clientInstallation *github.Client, installationID int64) (string, *time.Time, error) {
web.GHGenerateGitHubAppsToken = func(ctx context.Context, clientInstallation *github.Client, installationID int64, scope string) (string, *time.Time, error) {
return testGitHubAppToken, &testTime, nil
}

Expand Down

0 comments on commit f62d487

Please sign in to comment.