diff --git a/tsweb/tsweb.go b/tsweb/tsweb.go index 191a1e9e3..7c117ff90 100644 --- a/tsweb/tsweb.go +++ b/tsweb/tsweb.go @@ -23,6 +23,7 @@ "runtime" "strconv" "strings" + "sync" "time" "go4.org/mem" @@ -302,15 +303,38 @@ func (h retHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } if h.opts.StatusCodeCounters != nil { - key := fmt.Sprintf("%dxx", msg.Code/100) - h.opts.StatusCodeCounters.Add(key, 1) + h.opts.StatusCodeCounters.Add(responseCodeString(msg.Code/100), 1) } if h.opts.StatusCodeCountersFull != nil { - h.opts.StatusCodeCountersFull.Add(strconv.Itoa(msg.Code), 1) + h.opts.StatusCodeCountersFull.Add(responseCodeString(msg.Code), 1) } } +func responseCodeString(code int) string { + if v, ok := responseCodeCache.Load(code); ok { + return v.(string) + } + + var ret string + if code < 10 { + ret = fmt.Sprintf("%dxx", code) + } else { + ret = strconv.Itoa(code) + } + responseCodeCache.Store(code, ret) + return ret +} + +// responseCodeCache memoizes the string form of HTTP response codes, +// so that the hot request-handling codepath doesn't have to allocate +// in strconv/fmt for every request. +// +// Keys are either full HTTP response code ints (200, 404) or "family" +// ints representing entire families (e.g. 2 for 2xx codes). Values +// are the string form of that code/family. +var responseCodeCache sync.Map + // loggingResponseWriter wraps a ResponseWriter and record the HTTP // response code that gets sent, if any. type loggingResponseWriter struct {