4
4
"context"
5
5
"fmt"
6
6
"net/http"
7
+ "time"
7
8
8
9
"github.com/prometheus/client_golang/prometheus"
9
10
"github.com/prometheus/client_golang/prometheus/promauto"
@@ -51,6 +52,16 @@ type Factory struct {
51
52
// metrics is the reusable metrics for all oauth2 providers.
52
53
type metrics struct {
53
54
externalRequestCount * prometheus.CounterVec
55
+
56
+ // if the oauth supports it, rate limit metrics.
57
+ // rateLimit is the defined limit per interval
58
+ rateLimit * prometheus.GaugeVec
59
+ rateLimitRemaining * prometheus.GaugeVec
60
+ rateLimitUsed * prometheus.GaugeVec
61
+ // rateLimitReset is the time in seconds the rate limit resets.
62
+ rateLimitReset * prometheus.GaugeVec
63
+ // rateLimitResetIn is the time in seconds until the rate limit resets.
64
+ rateLimitResetIn * prometheus.GaugeVec
54
65
}
55
66
56
67
func NewFactory (registry prometheus.Registerer ) * Factory {
@@ -68,25 +79,107 @@ func NewFactory(registry prometheus.Registerer) *Factory {
68
79
"source" ,
69
80
"status_code" ,
70
81
}),
82
+ rateLimit : factory .NewGaugeVec (prometheus.GaugeOpts {
83
+ Namespace : "coderd" ,
84
+ Subsystem : "oauth2" ,
85
+ Name : "external_requests_rate_limit_total" ,
86
+ Help : "The total number of allowed requests per interval." ,
87
+ }, []string {
88
+ "name" ,
89
+ // Resource allows different rate limits for the same oauth2 provider.
90
+ // Some IDPs have different buckets for different rate limits.
91
+ "resource" ,
92
+ }),
93
+ rateLimitRemaining : factory .NewGaugeVec (prometheus.GaugeOpts {
94
+ Namespace : "coderd" ,
95
+ Subsystem : "oauth2" ,
96
+ Name : "external_requests_rate_limit_remaining" ,
97
+ Help : "The remaining number of allowed requests in this interval." ,
98
+ }, []string {
99
+ "name" ,
100
+ "resource" ,
101
+ }),
102
+ rateLimitUsed : factory .NewGaugeVec (prometheus.GaugeOpts {
103
+ Namespace : "coderd" ,
104
+ Subsystem : "oauth2" ,
105
+ Name : "external_requests_rate_limit_used" ,
106
+ Help : "The number of requests made in this interval." ,
107
+ }, []string {
108
+ "name" ,
109
+ "resource" ,
110
+ }),
111
+ rateLimitReset : factory .NewGaugeVec (prometheus.GaugeOpts {
112
+ Namespace : "coderd" ,
113
+ Subsystem : "oauth2" ,
114
+ Name : "external_requests_rate_limit_next_reset_unix" ,
115
+ Help : "Unix timestamp of the next interval" ,
116
+ }, []string {
117
+ "name" ,
118
+ "resource" ,
119
+ }),
120
+ rateLimitResetIn : factory .NewGaugeVec (prometheus.GaugeOpts {
121
+ Namespace : "coderd" ,
122
+ Subsystem : "oauth2" ,
123
+ Name : "external_requests_rate_limit_reset_in_seconds" ,
124
+ Help : "Seconds until the next interval" ,
125
+ }, []string {
126
+ "name" ,
127
+ "resource" ,
128
+ }),
71
129
},
72
130
}
73
131
}
74
132
75
- func (f * Factory ) New (name string , under OAuth2Config ) * Config {
133
+ func (f * Factory ) New (name string , under OAuth2Config , opts ... func ( cfg * Config ) ) * Config {
76
134
return & Config {
77
135
name : name ,
78
136
underlying : under ,
79
137
metrics : f .metrics ,
80
138
}
81
139
}
82
140
141
+ // NewGithub returns a new instrumented oauth2 config for github. It tracks
142
+ // rate limits as well as just the external request counts.
143
+ func (f * Factory ) NewGithub (name string , under OAuth2Config ) * Config {
144
+ cfg := f .New (name , under )
145
+ cfg .interceptors = append (cfg .interceptors , func (resp * http.Response , err error ) {
146
+ limits , ok := githubRateLimits (resp , err )
147
+ if ! ok {
148
+ return
149
+ }
150
+ labels := prometheus.Labels {
151
+ "name" : cfg .name ,
152
+ "resource" : limits .Resource ,
153
+ }
154
+ // Default to -1 for "do not know"
155
+ resetIn := float64 (- 1 )
156
+ if ! limits .Reset .IsZero () {
157
+ now := time .Now ()
158
+ resetIn = float64 (limits .Reset .Sub (now ).Seconds ())
159
+ if resetIn < 0 {
160
+ // If it just reset, just make it 0.
161
+ resetIn = 0
162
+ }
163
+ }
164
+
165
+ f .metrics .rateLimit .With (labels ).Set (float64 (limits .Limit ))
166
+ f .metrics .rateLimitRemaining .With (labels ).Set (float64 (limits .Remaining ))
167
+ f .metrics .rateLimitUsed .With (labels ).Set (float64 (limits .Used ))
168
+ f .metrics .rateLimitReset .With (labels ).Set (float64 (limits .Reset .Unix ()))
169
+ f .metrics .rateLimitResetIn .With (labels ).Set (resetIn )
170
+ })
171
+ return cfg
172
+ }
173
+
83
174
type Config struct {
84
175
// Name is a human friendly name to identify the oauth2 provider. This should be
85
176
// deterministic from restart to restart, as it is going to be used as a label in
86
177
// prometheus metrics.
87
178
name string
88
179
underlying OAuth2Config
89
180
metrics * metrics
181
+ // interceptors are called after every request made by the oauth2 client.
182
+ interceptors []func (resp * http.Response , err error )
90
183
}
91
184
92
185
func (c * Config ) Do (ctx context.Context , source Oauth2Source , req * http.Request ) (* http.Response , error ) {
@@ -169,5 +262,10 @@ func (i *instrumentedTripper) RoundTrip(r *http.Request) (*http.Response, error)
169
262
"source" : string (i .source ),
170
263
"status_code" : fmt .Sprintf ("%d" , statusCode ),
171
264
}).Inc ()
265
+
266
+ // Handle any extra interceptors.
267
+ for _ , interceptor := range i .c .interceptors {
268
+ interceptor (resp , err )
269
+ }
172
270
return resp , err
173
271
}
0 commit comments