@@ -16,6 +16,8 @@ def initialize(app, options = {})
16
16
@last_request_time = Time . now
17
17
@mutex = Mutex . new
18
18
@debug_enabled = !ENV [ 'THROTTLE_DEBUG' ] . nil? && !ENV [ 'THROTTLE_DEBUG' ] . empty?
19
+ @github_rate_limit_remaining = nil
20
+ @github_rate_limit_reset = nil
19
21
end
20
22
21
23
def call ( env )
@@ -24,11 +26,43 @@ def call(env)
24
26
log_throttle_status
25
27
end
26
28
27
- @app . call ( env )
29
+ response = @app . call ( env )
30
+
31
+ # Update GitHub rate limit info from response headers
32
+ @mutex . synchronize do
33
+ update_github_rate_limit ( response )
34
+ end
35
+
36
+ response
28
37
end
29
38
30
39
private
31
40
41
+ def update_github_rate_limit ( response )
42
+ if response . headers [ 'x-ratelimit-remaining' ]
43
+ @github_rate_limit_remaining = response . headers [ 'x-ratelimit-remaining' ] . to_i
44
+ @github_rate_limit_reset = response . headers [ 'x-ratelimit-reset' ] . to_i if response . headers [ 'x-ratelimit-reset' ]
45
+ end
46
+ end
47
+
48
+ def calculate_dynamic_delay
49
+ return MIN_DELAY_SECONDS unless @github_rate_limit_remaining && @github_rate_limit_reset
50
+
51
+ # Calculate time until rate limit resets
52
+ current_time = Time . now . to_i
53
+ time_until_reset = [ @github_rate_limit_reset - current_time , 1 ] . max
54
+
55
+ # Calculate required delay to not exceed remaining requests
56
+ if @github_rate_limit_remaining > 0
57
+ required_delay = time_until_reset . to_f / @github_rate_limit_remaining
58
+ # Use the more conservative delay (either our standard delay or the calculated one)
59
+ [ MIN_DELAY_SECONDS , required_delay ] . max
60
+ else
61
+ # No requests remaining, wait until reset
62
+ time_until_reset
63
+ end
64
+ end
65
+
32
66
def throttle_request
33
67
current_time = Time . now
34
68
@@ -39,11 +73,16 @@ def throttle_request
39
73
@last_request_time = current_time
40
74
end
41
75
42
- # Ensure minimum delay between requests to maintain steady rate under 5000/hour
76
+ # Use dynamic delay based on actual GitHub rate limit if available
77
+ required_delay = @github_rate_limit_remaining ? calculate_dynamic_delay : MIN_DELAY_SECONDS
78
+
79
+ # Ensure minimum delay between requests
43
80
time_since_last = current_time - @last_request_time
44
- if time_since_last < MIN_DELAY_SECONDS
45
- sleep_time = MIN_DELAY_SECONDS - time_since_last
81
+ if time_since_last < required_delay
82
+ sleep_time = required_delay - time_since_last
46
83
if sleep_time > 0
84
+ #delay_reason = @github_rate_limit_remaining ? "dynamic" : "standard"
85
+ #$stderr.print "Throttling: waiting #{sleep_time.round(2)}s (#{delay_reason} delay)\n"
47
86
sleep ( sleep_time )
48
87
end
49
88
end
@@ -55,7 +94,8 @@ def throttle_request
55
94
if @request_count % 1000 == 0
56
95
elapsed_hour = @last_request_time - @hour_start_time
57
96
current_rate = elapsed_hour > 0 ? ( @request_count / elapsed_hour * 3600 ) . round ( 1 ) : 0
58
- $stderr. print "Throttling status: #{ @request_count } requests in #{ elapsed_hour . round ( 1 ) } s (#{ current_rate } /hour rate)\n "
97
+ github_info = @github_rate_limit_remaining ? " GitHub: #{ @github_rate_limit_remaining } remaining" : ""
98
+ $stderr. print "Throttling status: #{ @request_count } requests in #{ elapsed_hour . round ( 1 ) } s (#{ current_rate } /hour rate)#{ github_info } \n "
59
99
end
60
100
end
61
101
@@ -264,8 +304,22 @@ def member_activity
264
304
265
305
# for each repo
266
306
@repositories . each do |repo |
267
- rate_limit = @client . rate_limit
268
- info "rate limit remaining: #{ rate_limit . remaining } /#{ rate_limit . limit } "
307
+ # Show rate limit from last response headers (more efficient than API call)
308
+ if @client . last_response
309
+ remaining = @client . last_response . headers [ 'x-ratelimit-remaining' ]
310
+ limit = @client . last_response . headers [ 'x-ratelimit-limit' ]
311
+ if remaining && limit
312
+ reset_time = @client . last_response . headers [ 'x-ratelimit-reset' ]
313
+ if reset_time
314
+ minutes_until_reset = [ ( reset_time . to_i - Time . now . to_i ) / 60.0 , 0 ] . max . round ( 1 )
315
+ reset_info = " (resets in #{ minutes_until_reset } min)"
316
+ else
317
+ reset_info = ""
318
+ end
319
+ info "#{ remaining } requests remaining#{ reset_info } "
320
+ end
321
+ end
322
+
269
323
info "analyzing #{ repo } "
270
324
271
325
commit_activity ( repo )
@@ -342,13 +396,13 @@ def member_activity
342
396
builder . use Octokit ::Middleware ::FollowRedirects
343
397
builder . use Octokit ::Response ::RaiseError
344
398
builder . use Octokit ::Response ::FeedParser
345
- builder . response :logger
399
+ builder . response :logger if @debug
346
400
builder . adapter Faraday . default_adapter
347
401
end
348
402
349
403
Octokit . configure do |kit |
350
404
kit . auto_paginate = true
351
- kit . middleware = stack if @debug
405
+ kit . middleware = stack
352
406
end
353
407
354
408
options [ :client ] = Octokit ::Client . new
0 commit comments