package stats import ( "fmt" "net/http" "os" "sync" "time" ) // Stats data structure type Stats struct { mu sync.RWMutex Uptime time.Time Pid int ResponseCounts map[string]int TotalResponseCounts map[string]int TotalResponseTime time.Time } // New constructs a new Stats structure func New() *Stats { stats := &Stats{ Uptime: time.Now(), Pid: os.Getpid(), ResponseCounts: map[string]int{}, TotalResponseCounts: map[string]int{}, TotalResponseTime: time.Time{}, } go func() { for { stats.ResetResponseCounts() time.Sleep(time.Second * 1) } }() return stats } // ResetResponseCounts reset the response counts func (mw *Stats) ResetResponseCounts() { mw.mu.Lock() defer mw.mu.Unlock() mw.ResponseCounts = map[string]int{} } // Handler is a MiddlewareFunc makes Stats implement the Middleware interface. func (mw *Stats) Handler(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { beginning, recorder := mw.Begin(w) h.ServeHTTP(recorder, r) mw.End(beginning, recorder) }) } // Negroni compatible interface func (mw *Stats) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { beginning, recorder := mw.Begin(w) next(recorder, r) mw.End(beginning, recorder) } // Begin starts a recorder func (mw *Stats) Begin(w http.ResponseWriter) (time.Time, ResponseWriter) { start := time.Now() writer := NewRecorderResponseWriter(w, 200) return start, writer } // EndWithStatus closes the recorder with a specific status func (mw *Stats) EndWithStatus(start time.Time, status int) { end := time.Now() responseTime := end.Sub(start) mw.mu.Lock() defer mw.mu.Unlock() statusCode := fmt.Sprintf("%d", status) mw.ResponseCounts[statusCode]++ mw.TotalResponseCounts[statusCode]++ mw.TotalResponseTime = mw.TotalResponseTime.Add(responseTime) } // End closes the recorder with the recorder status func (mw *Stats) End(start time.Time, recorder ResponseWriter) { mw.EndWithStatus(start, recorder.Status()) } // Data serializable structure type Data struct { Pid int `json:"pid"` UpTime string `json:"uptime"` UpTimeSec float64 `json:"uptime_sec"` Time string `json:"time"` TimeUnix int64 `json:"unixtime"` StatusCodeCount map[string]int `json:"status_code_count"` TotalStatusCodeCount map[string]int `json:"total_status_code_count"` Count int `json:"count"` TotalCount int `json:"total_count"` TotalResponseTime string `json:"total_response_time"` TotalResponseTimeSec float64 `json:"total_response_time_sec"` AverageResponseTime string `json:"average_response_time"` AverageResponseTimeSec float64 `json:"average_response_time_sec"` } // Data returns the data serializable structure func (mw *Stats) Data() *Data { mw.mu.RLock() responseCounts := make(map[string]int, len(mw.ResponseCounts)) totalResponseCounts := make(map[string]int, len(mw.TotalResponseCounts)) now := time.Now() uptime := now.Sub(mw.Uptime) count := 0 for code, current := range mw.ResponseCounts { responseCounts[code] = current count += current } totalCount := 0 for code, count := range mw.TotalResponseCounts { totalResponseCounts[code] = count totalCount += count } totalResponseTime := mw.TotalResponseTime.Sub(time.Time{}) averageResponseTime := time.Duration(0) if totalCount > 0 { avgNs := int64(totalResponseTime) / int64(totalCount) averageResponseTime = time.Duration(avgNs) } mw.mu.RUnlock() r := &Data{ Pid: mw.Pid, UpTime: uptime.String(), UpTimeSec: uptime.Seconds(), Time: now.String(), TimeUnix: now.Unix(), StatusCodeCount: responseCounts, TotalStatusCodeCount: totalResponseCounts, Count: count, TotalCount: totalCount, TotalResponseTime: totalResponseTime.String(), TotalResponseTimeSec: totalResponseTime.Seconds(), AverageResponseTime: averageResponseTime.String(), AverageResponseTimeSec: averageResponseTime.Seconds(), } return r }