6
6
"strconv"
7
7
"strings"
8
8
9
+ "github.com/prometheus/client_golang/prometheus"
10
+ "github.com/prometheus/client_golang/prometheus/promhttp"
11
+
9
12
"github.com/gorilla/mux"
10
13
"github.com/minotar/minecraft"
11
14
)
@@ -16,12 +19,20 @@ type Router struct {
16
19
17
20
// Middleware function to manipulate our request and response.
18
21
func imgdHandler (router http.Handler ) http.Handler {
19
- return http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
22
+ return metricChain ( http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
20
23
w .Header ().Set ("Access-Control-Allow-Origin" , "*" )
21
24
w .Header ().Set ("Access-Control-Allow-Methods" , "GET" )
22
25
w .Header ().Set ("Access-Control-Allow-Headers" , "Accept, Content-Type, Content-Length, Accept-Encoding" )
23
26
router .ServeHTTP (w , r )
24
- })
27
+ }))
28
+ }
29
+
30
+ func metricChain (router http.Handler ) http.Handler {
31
+ return promhttp .InstrumentHandlerInFlight (inFlightGauge ,
32
+ promhttp .InstrumentHandlerDuration (requestDuration ,
33
+ promhttp .InstrumentHandlerResponseSize (responseSize , router ),
34
+ ),
35
+ )
25
36
}
26
37
27
38
type NotFoundHandler struct {}
@@ -138,7 +149,9 @@ func (router *Router) Serve(resource string) {
138
149
return
139
150
}
140
151
152
+ processingTimer := prometheus .NewTimer (processingDuration .WithLabelValues (resource ))
141
153
err := router .ResolveMethod (skin , resource )(int (width ))
154
+ processingTimer .ObserveDuration ()
142
155
if err != nil {
143
156
w .WriteHeader (http .StatusInternalServerError )
144
157
fmt .Fprintf (w , "500 internal server error" )
@@ -177,6 +190,8 @@ func (router *Router) Bind() {
177
190
log .Infof ("%s %s 200" , r .RemoteAddr , r .RequestURI )
178
191
})
179
192
193
+ router .Mux .Handle ("/metrics" , promhttp .Handler ())
194
+
180
195
router .Mux .HandleFunc ("/stats" , func (w http.ResponseWriter , r * http.Request ) {
181
196
w .Header ().Set ("Content-Type" , "application/json" )
182
197
w .Write (stats .ToJSON ())
@@ -191,32 +206,70 @@ func (router *Router) Bind() {
191
206
192
207
func fetchSkin (username string ) * mcSkin {
193
208
if username == "char" || username == "MHF_Steve" {
194
- skin , _ := minecraft .FetchSkinForChar ()
209
+ skin , _ := minecraft .FetchSkinForSteve ()
195
210
return & mcSkin {Skin : skin }
196
211
}
197
212
213
+ hasTimer := prometheus .NewTimer (cacheDuration .WithLabelValues ("has" ))
198
214
if cache .has (strings .ToLower (username )) {
215
+ hasTimer .ObserveDuration ()
216
+ pullTimer := prometheus .NewTimer (cacheDuration .WithLabelValues ("pull" ))
217
+ defer pullTimer .ObserveDuration ()
199
218
stats .HitCache ()
200
219
return & mcSkin {Processed : nil , Skin : cache .pull (strings .ToLower (username ))}
201
220
}
221
+ hasTimer .ObserveDuration ()
222
+ stats .MissCache ()
202
223
203
- skin , err := minecraft .FetchSkinFromMojang (username )
204
- if err != nil {
205
- log .Debugf ("Failed Skin Mojang: %s (%s)" , username , err .Error ())
206
- // Let's fallback to S3 and try and serve at least an old skin...
207
- skin , err = minecraft .FetchSkinFromS3 (username )
208
- if err != nil {
209
- log .Debugf ("Failed Skin S3: %s (%s)" , username , err .Error ())
210
- // Well, looks like they don't exist after all.
211
- skin , _ = minecraft .FetchSkinForChar ()
212
- stats .Errored ("FallbackSteve" )
224
+ // Everyone loves nested if statements, right?
225
+ var skin minecraft.Skin
226
+ stats .APIRequested ("GetUUID" )
227
+ uuid , err := minecraft .NormalizePlayerForUUID (username )
228
+ if err != nil && err .Error () == "unable to GetAPIProfile: user not found" {
229
+ log .Debugf ("Failed UUID lookup: %s (%s)" , username , err .Error ())
230
+ skin , _ = minecraft .FetchSkinForSteve ()
231
+ stats .Errored ("UnknownUser" )
232
+ // Don't return yet to ensure we cache the failure
233
+ } else {
234
+ var catchErr error
235
+ // Either no error, or there is one (eg. rate limit or network etc.), but they do possibly still exist
236
+ if err != nil && err .Error () == "unable to GetAPIProfile: rate limited" {
237
+ log .Noticef ("Failed UUID lookup: %s (%s)" , username , err .Error ())
238
+ stats .Errored ("LookupUUIDRateLimit" )
239
+ catchErr = err
240
+ } else if err != nil {
241
+ // Other generic issues with looking up UUID, but still worth trying S3
242
+ log .Infof ("Failed UUID lookup: %s (%s)" , username , err .Error ())
243
+ stats .Errored ("LookupUUID" )
244
+ catchErr = err
213
245
} else {
214
- stats .Errored ("FallbackUsernameS3" )
246
+ // We have a UUID, so let's get a skin!
247
+ sPTimer := prometheus .NewTimer (getDuration .WithLabelValues ("SessionProfile" ))
248
+ skin , catchErr = minecraft .FetchSkinUUID (uuid )
249
+ sPTimer .ObserveDuration ()
250
+ if catchErr != nil {
251
+ log .Noticef ("Failed Skin SessionProfile: %s (%s)" , username , catchErr .Error ())
252
+ stats .Errored ("SkinSessionProfile" )
253
+ }
254
+ }
255
+ if catchErr != nil {
256
+ // Let's fallback to S3 and try and serve at least an old skin...
257
+ s3Timer := prometheus .NewTimer (getDuration .WithLabelValues ("S3" ))
258
+ skin , err = minecraft .FetchSkinUsernameS3 (username )
259
+ s3Timer .ObserveDuration ()
260
+ if err != nil {
261
+ log .Debugf ("Failed Skin S3: %s (%s)" , username , err .Error ())
262
+ // Well, looks like they don't exist after all.
263
+ skin , _ = minecraft .FetchSkinForSteve ()
264
+ stats .Errored ("FallbackSteve" )
265
+ } else {
266
+ stats .Errored ("FallbackUsernameS3" )
267
+ }
215
268
}
216
269
}
217
270
218
- stats . MissCache ( )
271
+ addTimer := prometheus . NewTimer ( cacheDuration . WithLabelValues ( "add" ) )
219
272
cache .add (strings .ToLower (username ), skin )
220
-
273
+ addTimer . ObserveDuration ()
221
274
return & mcSkin {Processed : nil , Skin : skin }
222
275
}
0 commit comments