Merge: * stats: refactor: move HTTP handlers to stats/
* commit 'bbb5413331d43bbc182834ea8b3c2318280c7748': * stats: refactor: move HTTP handlers to stats/
This commit is contained in:
commit
250f829b29
|
@ -88,7 +88,7 @@ type FilteringConfig struct {
|
||||||
// Requests won't be filtered until dnsfilter is created.
|
// Requests won't be filtered until dnsfilter is created.
|
||||||
// If "restart" command is received while we're creating an old dnsfilter object,
|
// If "restart" command is received while we're creating an old dnsfilter object,
|
||||||
// we delay creation of the new object until the old one is created.
|
// we delay creation of the new object until the old one is created.
|
||||||
AsyncStartup bool
|
AsyncStartup bool `yaml:"-"`
|
||||||
|
|
||||||
ProtectionEnabled bool `yaml:"protection_enabled"` // whether or not use any of dnsfilter features
|
ProtectionEnabled bool `yaml:"protection_enabled"` // whether or not use any of dnsfilter features
|
||||||
FilteringEnabled bool `yaml:"filtering_enabled"` // whether or not use filter lists
|
FilteringEnabled bool `yaml:"filtering_enabled"` // whether or not use filter lists
|
||||||
|
|
|
@ -269,9 +269,6 @@ func parseConfig() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !checkStatsInterval(config.DNS.StatsInterval) {
|
|
||||||
config.DNS.StatsInterval = 1
|
|
||||||
}
|
|
||||||
if !checkFiltersUpdateIntervalHours(config.DNS.FiltersUpdateIntervalHours) {
|
if !checkFiltersUpdateIntervalHours(config.DNS.FiltersUpdateIntervalHours) {
|
||||||
config.DNS.FiltersUpdateIntervalHours = 24
|
config.DNS.FiltersUpdateIntervalHours = 24
|
||||||
}
|
}
|
||||||
|
@ -356,6 +353,12 @@ func (c *configuration) write() error {
|
||||||
config.Users = config.auth.GetUsers()
|
config.Users = config.auth.GetUsers()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.stats != nil {
|
||||||
|
sdc := stats.DiskConfig{}
|
||||||
|
config.stats.WriteDiskConfig(&sdc)
|
||||||
|
config.DNS.StatsInterval = sdc.Interval
|
||||||
|
}
|
||||||
|
|
||||||
configFile := config.getConfigFilename()
|
configFile := config.getConfigFilename()
|
||||||
log.Debug("Writing YAML file: %s", configFile)
|
log.Debug("Writing YAML file: %s", configFile)
|
||||||
yamlText, err := yaml.Marshal(&config)
|
yamlText, err := yaml.Marshal(&config)
|
||||||
|
|
|
@ -569,14 +569,11 @@ func registerControlHandlers() {
|
||||||
registerRewritesHandlers()
|
registerRewritesHandlers()
|
||||||
RegisterBlockedServicesHandlers()
|
RegisterBlockedServicesHandlers()
|
||||||
RegisterQueryLogHandlers()
|
RegisterQueryLogHandlers()
|
||||||
RegisterStatsHandlers()
|
|
||||||
RegisterAuthHandlers()
|
RegisterAuthHandlers()
|
||||||
|
|
||||||
http.HandleFunc("/dns-query", postInstall(handleDOH))
|
http.HandleFunc("/dns-query", postInstall(handleDOH))
|
||||||
}
|
}
|
||||||
|
|
||||||
type httpHandlerType func(http.ResponseWriter, *http.Request)
|
func httpRegister(method string, url string, handler func(http.ResponseWriter, *http.Request)) {
|
||||||
|
|
||||||
func httpRegister(method string, url string, handler httpHandlerType) {
|
|
||||||
http.Handle(url, postInstallHandler(optionalAuthHandler(gziphandler.GzipHandler(ensureHandler(method, handler)))))
|
http.Handle(url, postInstallHandler(optionalAuthHandler(gziphandler.GzipHandler(ensureHandler(method, handler)))))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,94 +0,0 @@
|
||||||
package home
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/stats"
|
|
||||||
"github.com/AdguardTeam/golibs/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
type statsConfig struct {
|
|
||||||
Interval uint32 `json:"interval"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get stats configuration
|
|
||||||
func handleStatsInfo(w http.ResponseWriter, r *http.Request) {
|
|
||||||
resp := statsConfig{}
|
|
||||||
resp.Interval = config.DNS.StatsInterval
|
|
||||||
|
|
||||||
jsonVal, err := json.Marshal(resp)
|
|
||||||
if err != nil {
|
|
||||||
httpError(w, http.StatusInternalServerError, "json encode: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
_, err = w.Write(jsonVal)
|
|
||||||
if err != nil {
|
|
||||||
httpError(w, http.StatusInternalServerError, "http write: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set stats configuration
|
|
||||||
func handleStatsConfig(w http.ResponseWriter, r *http.Request) {
|
|
||||||
reqData := statsConfig{}
|
|
||||||
err := json.NewDecoder(r.Body).Decode(&reqData)
|
|
||||||
if err != nil {
|
|
||||||
httpError(w, http.StatusBadRequest, "json decode: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !checkStatsInterval(reqData.Interval) {
|
|
||||||
httpError(w, http.StatusBadRequest, "Unsupported interval")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
config.DNS.StatsInterval = reqData.Interval
|
|
||||||
_ = config.write()
|
|
||||||
|
|
||||||
config.stats.Configure(int(config.DNS.StatsInterval))
|
|
||||||
|
|
||||||
returnOK(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleStats returns aggregated stats data
|
|
||||||
func handleStats(w http.ResponseWriter, r *http.Request) {
|
|
||||||
units := stats.Hours
|
|
||||||
if config.DNS.StatsInterval > 7 {
|
|
||||||
units = stats.Days
|
|
||||||
}
|
|
||||||
counter := log.StartTimer()
|
|
||||||
d := config.stats.GetData(units)
|
|
||||||
counter.LogElapsed("Stats: prepared data")
|
|
||||||
|
|
||||||
if d == nil {
|
|
||||||
httpError(w, http.StatusInternalServerError, "Couldn't get statistics data")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := json.Marshal(d)
|
|
||||||
if err != nil {
|
|
||||||
httpError(w, http.StatusInternalServerError, "json encode: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Write(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleStatsReset resets the stats
|
|
||||||
func handleStatsReset(w http.ResponseWriter, r *http.Request) {
|
|
||||||
config.stats.Clear()
|
|
||||||
returnOK(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterStatsHandlers - register handlers
|
|
||||||
func RegisterStatsHandlers() {
|
|
||||||
httpRegister(http.MethodGet, "/control/stats", handleStats)
|
|
||||||
httpRegister(http.MethodPost, "/control/stats_reset", handleStatsReset)
|
|
||||||
httpRegister(http.MethodPost, "/control/stats_config", handleStatsConfig)
|
|
||||||
httpRegister(http.MethodGet, "/control/stats_info", handleStatsInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkStatsInterval(i uint32) bool {
|
|
||||||
return i == 1 || i == 7 || i == 30 || i == 90
|
|
||||||
}
|
|
|
@ -21,6 +21,11 @@ type dnsContext struct {
|
||||||
whois *Whois
|
whois *Whois
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Called by other modules when configuration is changed
|
||||||
|
func onConfigModified() {
|
||||||
|
_ = config.write()
|
||||||
|
}
|
||||||
|
|
||||||
// initDNSServer creates an instance of the dnsforward.Server
|
// initDNSServer creates an instance of the dnsforward.Server
|
||||||
// Please note that we must do it even if we don't start it
|
// Please note that we must do it even if we don't start it
|
||||||
// so that we had access to the query log and the stats
|
// so that we had access to the query log and the stats
|
||||||
|
@ -33,6 +38,8 @@ func initDNSServer(baseDir string) {
|
||||||
statsConf := stats.Config{
|
statsConf := stats.Config{
|
||||||
Filename: filepath.Join(baseDir, "stats.db"),
|
Filename: filepath.Join(baseDir, "stats.db"),
|
||||||
LimitDays: config.DNS.StatsInterval,
|
LimitDays: config.DNS.StatsInterval,
|
||||||
|
ConfigModified: onConfigModified,
|
||||||
|
HTTPRegister: httpRegister,
|
||||||
}
|
}
|
||||||
config.stats, err = stats.New(statsConf)
|
config.stats, err = stats.New(statsConf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -4,15 +4,27 @@ package stats
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
type unitIDCallback func() uint32
|
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
|
// Config - module configuration
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Filename string // database file name
|
Filename string // database file name
|
||||||
LimitDays uint32 // time limit (in days)
|
LimitDays uint32 // time limit (in days)
|
||||||
UnitID unitIDCallback // user function to get the current unit ID. If nil, the current time hour is used.
|
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
|
// New - create object
|
||||||
|
@ -27,18 +39,11 @@ type Stats interface {
|
||||||
// (can't be called in parallel with any other function of this interface).
|
// (can't be called in parallel with any other function of this interface).
|
||||||
Close()
|
Close()
|
||||||
|
|
||||||
// Set new configuration at runtime.
|
|
||||||
// limit: time limit (in days)
|
|
||||||
Configure(limit int)
|
|
||||||
|
|
||||||
// Reset counters and clear database
|
|
||||||
Clear()
|
|
||||||
|
|
||||||
// Update counters
|
// Update counters
|
||||||
Update(e Entry)
|
Update(e Entry)
|
||||||
|
|
||||||
// Get data
|
// WriteDiskConfig - write configuration
|
||||||
GetData(timeUnit TimeUnit) map[string]interface{}
|
WriteDiskConfig(dc *DiskConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TimeUnit - time unit
|
// TimeUnit - time unit
|
||||||
|
|
|
@ -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",
|
Filename: "./stats.db",
|
||||||
LimitDays: 1,
|
LimitDays: 1,
|
||||||
}
|
}
|
||||||
s, _ := New(conf)
|
s, _ := createObject(conf)
|
||||||
|
|
||||||
e := Entry{}
|
e := Entry{}
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ func TestStats(t *testing.T) {
|
||||||
e.Time = 123456
|
e.Time = 123456
|
||||||
s.Update(e)
|
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}
|
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))
|
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["num_replaced_parental"].(uint64) == 0)
|
||||||
assert.True(t, d["avg_processing_time"].(float64) == 0.123456)
|
assert.True(t, d["avg_processing_time"].(float64) == 0.123456)
|
||||||
|
|
||||||
s.Clear()
|
s.clear()
|
||||||
s.Close()
|
s.Close()
|
||||||
os.Remove(conf.Filename)
|
os.Remove(conf.Filename)
|
||||||
}
|
}
|
||||||
|
@ -95,7 +95,7 @@ func TestLargeNumbers(t *testing.T) {
|
||||||
UnitID: newID,
|
UnitID: newID,
|
||||||
}
|
}
|
||||||
os.Remove(conf.Filename)
|
os.Remove(conf.Filename)
|
||||||
s, _ := New(conf)
|
s, _ := createObject(conf)
|
||||||
e := Entry{}
|
e := Entry{}
|
||||||
|
|
||||||
n := 1000 // number of distinct clients and domains every hour
|
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))
|
assert.True(t, d["num_dns_queries"].(uint64) == uint64(int(hour)*n))
|
||||||
|
|
||||||
s.Close()
|
s.Close()
|
||||||
|
|
|
@ -64,6 +64,9 @@ type unitDB struct {
|
||||||
|
|
||||||
func createObject(conf Config) (*statsCtx, error) {
|
func createObject(conf Config) (*statsCtx, error) {
|
||||||
s := statsCtx{}
|
s := statsCtx{}
|
||||||
|
if !checkInterval(conf.LimitDays) {
|
||||||
|
conf.LimitDays = 1
|
||||||
|
}
|
||||||
s.limit = conf.LimitDays * 24
|
s.limit = conf.LimitDays * 24
|
||||||
s.conf = conf
|
s.conf = conf
|
||||||
if conf.UnitID == nil {
|
if conf.UnitID == nil {
|
||||||
|
@ -112,12 +115,18 @@ func createObject(conf Config) (*statsCtx, error) {
|
||||||
}
|
}
|
||||||
s.unit = &u
|
s.unit = &u
|
||||||
|
|
||||||
|
s.initWeb()
|
||||||
|
|
||||||
go s.periodicFlush()
|
go s.periodicFlush()
|
||||||
|
|
||||||
log.Debug("Stats: initialized")
|
log.Debug("Stats: initialized")
|
||||||
return &s, nil
|
return &s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkInterval(days uint32) bool {
|
||||||
|
return days == 1 || days == 7 || days == 30 || days == 90
|
||||||
|
}
|
||||||
|
|
||||||
func (s *statsCtx) dbOpen() bool {
|
func (s *statsCtx) dbOpen() bool {
|
||||||
var err error
|
var err error
|
||||||
log.Tracef("db.Open...")
|
log.Tracef("db.Open...")
|
||||||
|
@ -362,12 +371,13 @@ func convertTopArray(a []countPair) []map[string]uint64 {
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *statsCtx) Configure(limit int) {
|
func (s *statsCtx) setLimit(limitDays int) {
|
||||||
if limit < 0 {
|
s.limit = uint32(limitDays) * 24
|
||||||
return
|
log.Debug("Stats: set limit: %d", limitDays)
|
||||||
}
|
}
|
||||||
s.limit = uint32(limit) * 24
|
|
||||||
log.Debug("Stats: set limit: %d", limit)
|
func (s *statsCtx) WriteDiskConfig(dc *DiskConfig) {
|
||||||
|
dc.Interval = s.limit / 24
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *statsCtx) Close() {
|
func (s *statsCtx) Close() {
|
||||||
|
@ -391,7 +401,8 @@ func (s *statsCtx) Close() {
|
||||||
log.Debug("Stats: closed")
|
log.Debug("Stats: closed")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *statsCtx) Clear() {
|
// Reset counters and clear database
|
||||||
|
func (s *statsCtx) clear() {
|
||||||
tx := s.beginTxn(true)
|
tx := s.beginTxn(true)
|
||||||
if tx != nil {
|
if tx != nil {
|
||||||
db := s.db
|
db := s.db
|
||||||
|
@ -472,7 +483,7 @@ func (s *statsCtx) Update(e Entry) {
|
||||||
These values are just the sum of data for all units.
|
These values are just the sum of data for all units.
|
||||||
*/
|
*/
|
||||||
// nolint (gocyclo)
|
// nolint (gocyclo)
|
||||||
func (s *statsCtx) GetData(timeUnit TimeUnit) map[string]interface{} {
|
func (s *statsCtx) getData(timeUnit TimeUnit) map[string]interface{} {
|
||||||
d := map[string]interface{}{}
|
d := map[string]interface{}{}
|
||||||
|
|
||||||
tx := s.beginTxn(false)
|
tx := s.beginTxn(false)
|
||||||
|
|
Loading…
Reference in New Issue