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