* stats: refactor: move HTTP handlers to stats/
DNS module passes additional parameters to Stats module. This allows Stats to handle HTTP requests by itself - completely removing all stats-related code from outside.
This commit is contained in:
@@ -4,15 +4,27 @@ package stats
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type unitIDCallback func() uint32
|
||||
|
||||
// DiskConfig - configuration settings that are stored on disk
|
||||
type DiskConfig struct {
|
||||
Interval uint32 `yaml:"statistics_interval"` // time interval for statistics (in days)
|
||||
}
|
||||
|
||||
// Config - module configuration
|
||||
type Config struct {
|
||||
Filename string // database file name
|
||||
LimitDays uint32 // time limit (in days)
|
||||
UnitID unitIDCallback // user function to get the current unit ID. If nil, the current time hour is used.
|
||||
|
||||
// Called when the configuration is changed by HTTP request
|
||||
ConfigModified func()
|
||||
|
||||
// Register an HTTP handler
|
||||
HTTPRegister func(string, string, func(http.ResponseWriter, *http.Request))
|
||||
}
|
||||
|
||||
// New - create object
|
||||
@@ -27,18 +39,11 @@ type Stats interface {
|
||||
// (can't be called in parallel with any other function of this interface).
|
||||
Close()
|
||||
|
||||
// Set new configuration at runtime.
|
||||
// limit: time limit (in days)
|
||||
Configure(limit int)
|
||||
|
||||
// Reset counters and clear database
|
||||
Clear()
|
||||
|
||||
// Update counters
|
||||
Update(e Entry)
|
||||
|
||||
// Get data
|
||||
GetData(timeUnit TimeUnit) map[string]interface{}
|
||||
// WriteDiskConfig - write configuration
|
||||
WriteDiskConfig(dc *DiskConfig)
|
||||
}
|
||||
|
||||
// TimeUnit - time unit
|
||||
|
||||
107
stats/stats_http.go
Normal file
107
stats/stats_http.go
Normal file
@@ -0,0 +1,107 @@
|
||||
// HTTP request handlers for accessing statistics data and configuration settings
|
||||
|
||||
package stats
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
)
|
||||
|
||||
func httpError(r *http.Request, w http.ResponseWriter, code int, format string, args ...interface{}) {
|
||||
text := fmt.Sprintf(format, args...)
|
||||
|
||||
log.Info("Stats: %s %s: %s", r.Method, r.URL, text)
|
||||
|
||||
http.Error(w, text, code)
|
||||
}
|
||||
|
||||
// Return data
|
||||
func (s *statsCtx) handleStats(w http.ResponseWriter, r *http.Request) {
|
||||
units := Hours
|
||||
if s.limit/24 > 7 {
|
||||
units = Days
|
||||
}
|
||||
counter := log.StartTimer()
|
||||
d := s.getData(units)
|
||||
counter.LogElapsed("Stats: prepared data")
|
||||
|
||||
if d == nil {
|
||||
httpError(r, w, http.StatusInternalServerError, "Couldn't get statistics data")
|
||||
return
|
||||
}
|
||||
|
||||
data, err := json.Marshal(d)
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusInternalServerError, "json encode: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(data)
|
||||
}
|
||||
|
||||
type config struct {
|
||||
IntervalDays uint32 `json:"interval"`
|
||||
}
|
||||
|
||||
// Get configuration
|
||||
func (s *statsCtx) handleStatsInfo(w http.ResponseWriter, r *http.Request) {
|
||||
resp := config{}
|
||||
resp.IntervalDays = s.limit / 24
|
||||
|
||||
data, err := json.Marshal(resp)
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusInternalServerError, "json encode: %s", err)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, err = w.Write(data)
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusInternalServerError, "http write: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Set configuration
|
||||
func (s *statsCtx) handleStatsConfig(w http.ResponseWriter, r *http.Request) {
|
||||
reqData := config{}
|
||||
err := json.NewDecoder(r.Body).Decode(&reqData)
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusBadRequest, "json decode: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !checkInterval(reqData.IntervalDays) {
|
||||
httpError(r, w, http.StatusBadRequest, "Unsupported interval")
|
||||
return
|
||||
}
|
||||
|
||||
s.setLimit(int(reqData.IntervalDays))
|
||||
s.conf.ConfigModified()
|
||||
}
|
||||
|
||||
// Reset data
|
||||
func (s *statsCtx) handleStatsReset(w http.ResponseWriter, r *http.Request) {
|
||||
s.clear()
|
||||
}
|
||||
|
||||
// Register web handlers
|
||||
func (s *statsCtx) initWeb() {
|
||||
if s.conf.HTTPRegister == nil {
|
||||
return
|
||||
}
|
||||
|
||||
s.conf.HTTPRegister("GET", "/control/stats", func(w http.ResponseWriter, r *http.Request) {
|
||||
s.handleStats(w, r)
|
||||
})
|
||||
s.conf.HTTPRegister("POST", "/control/stats_reset", func(w http.ResponseWriter, r *http.Request) {
|
||||
s.handleStatsReset(w, r)
|
||||
})
|
||||
s.conf.HTTPRegister("POST", "/control/stats_config", func(w http.ResponseWriter, r *http.Request) {
|
||||
s.handleStatsConfig(w, r)
|
||||
})
|
||||
s.conf.HTTPRegister("GET", "/control/stats_info", func(w http.ResponseWriter, r *http.Request) {
|
||||
s.handleStatsInfo(w, r)
|
||||
})
|
||||
}
|
||||
@@ -30,7 +30,7 @@ func TestStats(t *testing.T) {
|
||||
Filename: "./stats.db",
|
||||
LimitDays: 1,
|
||||
}
|
||||
s, _ := New(conf)
|
||||
s, _ := createObject(conf)
|
||||
|
||||
e := Entry{}
|
||||
|
||||
@@ -46,7 +46,7 @@ func TestStats(t *testing.T) {
|
||||
e.Time = 123456
|
||||
s.Update(e)
|
||||
|
||||
d := s.GetData(Hours)
|
||||
d := s.getData(Hours)
|
||||
a := []uint64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}
|
||||
assert.True(t, UIntArrayEquals(d["dns_queries"].([]uint64), a))
|
||||
|
||||
@@ -75,7 +75,7 @@ func TestStats(t *testing.T) {
|
||||
assert.True(t, d["num_replaced_parental"].(uint64) == 0)
|
||||
assert.True(t, d["avg_processing_time"].(float64) == 0.123456)
|
||||
|
||||
s.Clear()
|
||||
s.clear()
|
||||
s.Close()
|
||||
os.Remove(conf.Filename)
|
||||
}
|
||||
@@ -95,7 +95,7 @@ func TestLargeNumbers(t *testing.T) {
|
||||
UnitID: newID,
|
||||
}
|
||||
os.Remove(conf.Filename)
|
||||
s, _ := New(conf)
|
||||
s, _ := createObject(conf)
|
||||
e := Entry{}
|
||||
|
||||
n := 1000 // number of distinct clients and domains every hour
|
||||
@@ -115,7 +115,7 @@ func TestLargeNumbers(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
d := s.GetData(Hours)
|
||||
d := s.getData(Hours)
|
||||
assert.True(t, d["num_dns_queries"].(uint64) == uint64(int(hour)*n))
|
||||
|
||||
s.Close()
|
||||
|
||||
@@ -64,6 +64,9 @@ type unitDB struct {
|
||||
|
||||
func createObject(conf Config) (*statsCtx, error) {
|
||||
s := statsCtx{}
|
||||
if !checkInterval(conf.LimitDays) {
|
||||
conf.LimitDays = 1
|
||||
}
|
||||
s.limit = conf.LimitDays * 24
|
||||
s.conf = conf
|
||||
if conf.UnitID == nil {
|
||||
@@ -112,12 +115,18 @@ func createObject(conf Config) (*statsCtx, error) {
|
||||
}
|
||||
s.unit = &u
|
||||
|
||||
s.initWeb()
|
||||
|
||||
go s.periodicFlush()
|
||||
|
||||
log.Debug("Stats: initialized")
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
func checkInterval(days uint32) bool {
|
||||
return days == 1 || days == 7 || days == 30 || days == 90
|
||||
}
|
||||
|
||||
func (s *statsCtx) dbOpen() bool {
|
||||
var err error
|
||||
log.Tracef("db.Open...")
|
||||
@@ -362,12 +371,13 @@ func convertTopArray(a []countPair) []map[string]uint64 {
|
||||
return m
|
||||
}
|
||||
|
||||
func (s *statsCtx) Configure(limit int) {
|
||||
if limit < 0 {
|
||||
return
|
||||
}
|
||||
s.limit = uint32(limit) * 24
|
||||
log.Debug("Stats: set limit: %d", limit)
|
||||
func (s *statsCtx) setLimit(limitDays int) {
|
||||
s.limit = uint32(limitDays) * 24
|
||||
log.Debug("Stats: set limit: %d", limitDays)
|
||||
}
|
||||
|
||||
func (s *statsCtx) WriteDiskConfig(dc *DiskConfig) {
|
||||
dc.Interval = s.limit / 24
|
||||
}
|
||||
|
||||
func (s *statsCtx) Close() {
|
||||
@@ -391,7 +401,8 @@ func (s *statsCtx) Close() {
|
||||
log.Debug("Stats: closed")
|
||||
}
|
||||
|
||||
func (s *statsCtx) Clear() {
|
||||
// Reset counters and clear database
|
||||
func (s *statsCtx) clear() {
|
||||
tx := s.beginTxn(true)
|
||||
if tx != nil {
|
||||
db := s.db
|
||||
@@ -472,7 +483,7 @@ func (s *statsCtx) Update(e Entry) {
|
||||
These values are just the sum of data for all units.
|
||||
*/
|
||||
// nolint (gocyclo)
|
||||
func (s *statsCtx) GetData(timeUnit TimeUnit) map[string]interface{} {
|
||||
func (s *statsCtx) getData(timeUnit TimeUnit) map[string]interface{} {
|
||||
d := map[string]interface{}{}
|
||||
|
||||
tx := s.beginTxn(false)
|
||||
|
||||
Reference in New Issue
Block a user