githubRateLimits returns rate limit information from a GitHub response. GitHub rate limits are on a per-user basis, and tracking each user as a prometheus label might be too much. So only track rate limits for unauthorized responses. Unauthorized responses have a much stricter rate limit of 60 per
(resp *http.Response, err error)
| 24 | // Unauthorized responses have a much stricter rate limit of 60 per hour. |
| 25 | // Tracking this is vital to ensure we do not hit the limit. |
| 26 | func githubRateLimits(resp *http.Response, err error) (rateLimits, bool) { |
| 27 | if err != nil || resp == nil { |
| 28 | return rateLimits{}, false |
| 29 | } |
| 30 | |
| 31 | // Only track 401 responses which indicates we are using the 60 per hour |
| 32 | // rate limit. |
| 33 | if resp.StatusCode != http.StatusUnauthorized { |
| 34 | return rateLimits{}, false |
| 35 | } |
| 36 | |
| 37 | p := headerParser{header: resp.Header} |
| 38 | // See |
| 39 | // https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api?apiVersion=2022-11-28#checking-the-status-of-your-rate-limit |
| 40 | limits := rateLimits{ |
| 41 | Limit: p.int("x-ratelimit-limit"), |
| 42 | Remaining: p.int("x-ratelimit-remaining"), |
| 43 | Used: p.int("x-ratelimit-used"), |
| 44 | Resource: p.string("x-ratelimit-resource") + "-unauthorized", |
| 45 | } |
| 46 | |
| 47 | if limits.Limit == 0 && |
| 48 | limits.Remaining == 0 && |
| 49 | limits.Used == 0 { |
| 50 | // For some requests, github has no rate limit. In which case, |
| 51 | // it returns all 0s. We can just omit these. |
| 52 | return limits, false |
| 53 | } |
| 54 | |
| 55 | // Reset is when the rate limit "used" will be reset to 0. |
| 56 | // If it's unix 0, then we do not know when it will reset. |
| 57 | // Change it to a zero time as that is easier to handle in golang. |
| 58 | unix := p.int("x-ratelimit-reset") |
| 59 | resetAt := time.Unix(int64(unix), 0) |
| 60 | if unix == 0 { |
| 61 | resetAt = time.Time{} |
| 62 | } |
| 63 | limits.Reset = resetAt |
| 64 | |
| 65 | if len(p.errors) > 0 { |
| 66 | // If we are missing any headers, then do not try and guess |
| 67 | // what the rate limits are. |
| 68 | return limits, false |
| 69 | } |
| 70 | return limits, true |
| 71 | } |
| 72 | |
| 73 | type headerParser struct { |
| 74 | errors map[string]error |