diff --git a/cmd/processRequest.go b/cmd/processRequest.go index da03f34..faba90b 100644 --- a/cmd/processRequest.go +++ b/cmd/processRequest.go @@ -28,6 +28,8 @@ type RequestProcessor struct { peakRequest30 sync.Map peakRequest60 sync.Map lruCache *lru.Cache + stats *Stats + router Router } // NewRequestProcessor returns new RequestProcessor. @@ -37,9 +39,15 @@ func NewRequestProcessor() (*RequestProcessor, error) { return nil, err } - return &RequestProcessor{ + rp := &RequestProcessor{ lruCache: lruCache, - }, nil + stats: NewStats(), + } + + // Initialize routes. + rp.router.AddPath("/:stats", rp.stats) + + return rp, nil } // Start starts async request processor jobs, such as peak handling. @@ -53,11 +61,23 @@ func (rp *RequestProcessor) ProcessRequest(r *http.Request) (*ResponseWithHeader err error ) + rp.stats.Inc("total") + + // Main routing logic. + if rh := rp.router.Route(r); rh != nil { + result := rh.Response(r) + if result != nil { + return result, nil + } + } + if resp, ok := redirectInsecure(r); ok { + rp.stats.Inc("redirects") return resp, nil } if dontCache(r) { + rp.stats.Inc("uncached") return get(r) } @@ -69,6 +89,7 @@ func (rp *RequestProcessor) ProcessRequest(r *http.Request) (*ResponseWithHeader cacheBody, ok := rp.lruCache.Get(cacheDigest) if ok { + rp.stats.Inc("cache1") cacheEntry := cacheBody.(ResponseWithHeader) // if after all attempts we still have no answer, @@ -93,7 +114,17 @@ func (rp *RequestProcessor) ProcessRequest(r *http.Request) (*ResponseWithHeader } if !foundInCache { + // Handling query. + format := r.URL.Query().Get("format") + if len(format) != 0 { + rp.stats.Inc("format") + if format == "j1" { + rp.stats.Inc("format=j1") + } + } + rp.lruCache.Add(cacheDigest, ResponseWithHeader{InProgress: true}) + response, err = get(r) if err != nil { return nil, err diff --git a/cmd/stat.go b/cmd/stat.go index eda4f27..4006247 100644 --- a/cmd/stat.go +++ b/cmd/stat.go @@ -1,40 +1,85 @@ package main -// import ( -// "log" -// "sync" -// "time" -// ) -// -// type safeCounter struct { -// v map[int]int -// mux sync.Mutex -// } -// -// func (c *safeCounter) inc(key int) { -// c.mux.Lock() -// c.v[key]++ -// c.mux.Unlock() -// } -// -// // func (c *safeCounter) val(key int) int { -// // c.mux.Lock() -// // defer c.mux.Unlock() -// // return c.v[key] -// // } -// // -// // func (c *safeCounter) reset(key int) int { -// // c.mux.Lock() -// // defer c.mux.Unlock() -// // result := c.v[key] -// // c.v[key] = 0 -// // return result -// // } -// -// var queriesPerMinute safeCounter -// -// func printStat() { -// _, min, _ := time.Now().Clock() -// queriesPerMinute.inc(min) -// log.Printf("Processed %d requests\n", min) -// } +import ( + "bytes" + "fmt" + "net/http" + "sync" + "time" +) + +// Stats holds processed requests statistics. +type Stats struct { + m sync.Mutex + v map[string]int + startTime time.Time +} + +// NewStats returns new Stats. +func NewStats() *Stats { + return &Stats{ + v: map[string]int{}, + startTime: time.Now(), + } +} + +// Inc key by one. +func (c *Stats) Inc(key string) { + c.m.Lock() + c.v[key]++ + c.m.Unlock() +} + +// Get current key counter value. +func (c *Stats) Get(key string) int { + c.m.Lock() + defer c.m.Unlock() + return c.v[key] +} + +// Reset key counter. +func (c *Stats) Reset(key string) int { + c.m.Lock() + defer c.m.Unlock() + result := c.v[key] + c.v[key] = 0 + return result +} + +// Show returns current statistics formatted as []byte. +func (c *Stats) Show() []byte { + var ( + b bytes.Buffer + ) + + c.m.Lock() + defer c.m.Unlock() + + uptime := time.Since(c.startTime) / time.Second + + fmt.Fprintf(&b, "%-20s: %v\n", "Running since", c.startTime.Format(time.RFC3339)) + fmt.Fprintf(&b, "%-20s: %d\n", "Uptime (min)", uptime/60) + + fmt.Fprintf(&b, "%-20s: %d\n", "Total queries", c.v["total"]) + if uptime != 0 { + fmt.Fprintf(&b, "%-20s: %d\n", "Throughput (QpM)", c.v["total"]*60/int(uptime)) + } + + fmt.Fprintf(&b, "%-20s: %d\n", "Cache L1 queries", c.v["cache1"]) + if c.v["total"] != 0 { + fmt.Fprintf(&b, "%-20s: %d\n", "Cache L1 queries (%)", (100*c.v["cache1"])/c.v["total"]) + } + + fmt.Fprintf(&b, "%-20s: %d\n", "Upstream queries", c.v["total"]-c.v["cache1"]) + fmt.Fprintf(&b, "%-20s: %d\n", "Queries with format", c.v["format"]) + fmt.Fprintf(&b, "%-20s: %d\n", "Queries with format=j1", c.v["format=j1"]) + + return b.Bytes() +} + +func (c *Stats) Response(*http.Request) *ResponseWithHeader { + return &ResponseWithHeader{ + Body: c.Show(), + StatusCode: 200, + } +}