diff --git a/CHANGELOG.md b/CHANGELOG.md index 66f3b1f7ffb..b2fb8a02a9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,9 +15,12 @@ and this project adheres to ### Added +- Completely disabling statistics by setting the statistics interval to zero + ([#2141]). - The ability to completely purge DHCP leases ([#1691]). -- The ability to set the timeout for querying the upstream servers ([#2280]). -- The ability to change group and user ID on startup on Unix ([#2763]). +- Settable timeouts for querying the upstream servers ([#2280]). +- Configuration file parameters to change group and user ID on startup on Unix + ([#2763]). - Experimental OpenBSD support for AMD64 and 64-bit ARM CPUs ([#2439]). - Support for custom port in DNS-over-HTTPS profiles for Apple's devices ([#3172]). @@ -67,6 +70,7 @@ released by then. [#1381]: https://github.com/AdguardTeam/AdGuardHome/issues/1381 [#1691]: https://github.com/AdguardTeam/AdGuardHome/issues/1691 +[#2141]: https://github.com/AdguardTeam/AdGuardHome/issues/2141 [#2280]: https://github.com/AdguardTeam/AdGuardHome/issues/2280 [#2439]: https://github.com/AdguardTeam/AdGuardHome/issues/2439 [#2441]: https://github.com/AdguardTeam/AdGuardHome/issues/2441 diff --git a/client/src/components/Settings/StatsConfig/Form.js b/client/src/components/Settings/StatsConfig/Form.js index c16b39b24b6..60a5ae860d6 100644 --- a/client/src/components/Settings/StatsConfig/Form.js +++ b/client/src/components/Settings/StatsConfig/Form.js @@ -8,22 +8,27 @@ import { renderRadioField, toNumber } from '../../../helpers/form'; import { FORM_NAME, STATS_INTERVALS_DAYS } from '../../../helpers/constants'; import '../FormButton.css'; -const getIntervalFields = (processing, t, toNumber) => STATS_INTERVALS_DAYS.map((interval) => { - const title = interval === 1 ? t('interval_24_hour') : t('interval_days', { count: interval }); +const getIntervalTitle = (interval, t) => { + switch (interval) { + case 0: + return t('disabled'); + case 1: + return t('interval_24_hour'); + default: + return t('interval_days', { count: interval }); + } +}; - return ( - - ); -}); +const getIntervalFields = (processing, t, toNumber) => STATS_INTERVALS_DAYS.map((interval) => ); const Form = (props) => { const { diff --git a/client/src/helpers/constants.js b/client/src/helpers/constants.js index 4a81d5801e3..5b2633705d8 100644 --- a/client/src/helpers/constants.js +++ b/client/src/helpers/constants.js @@ -355,7 +355,7 @@ export const ENCRYPTION_SOURCE = { export const FILTERED = 'Filtered'; export const NOT_FILTERED = 'NotFiltered'; -export const STATS_INTERVALS_DAYS = [1, 7, 30, 90]; +export const STATS_INTERVALS_DAYS = [0, 1, 7, 30, 90]; export const QUERY_LOG_INTERVALS_DAYS = [1, 7, 30, 90]; diff --git a/internal/stats/http.go b/internal/stats/http.go index 1580174a388..e828a2b57c2 100644 --- a/internal/stats/http.go +++ b/internal/stats/http.go @@ -19,6 +19,10 @@ func httpError(r *http.Request, w http.ResponseWriter, code int, format string, http.Error(w, text, code) } +// topAddrs is an alias for the types of the TopFoo fields of statsResponse. +// The key is either a client's address or a requested address. +type topAddrs = map[string]uint64 + // statsResponse is a response for getting statistics. type statsResponse struct { TimeUnits string `json:"time_units"` @@ -31,9 +35,9 @@ type statsResponse struct { AvgProcessingTime float64 `json:"avg_processing_time"` - TopQueried []map[string]uint64 `json:"top_queried_domains"` - TopClients []map[string]uint64 `json:"top_clients"` - TopBlocked []map[string]uint64 `json:"top_blocked_domains"` + TopQueried []topAddrs `json:"top_queried_domains"` + TopClients []topAddrs `json:"top_clients"` + TopBlocked []topAddrs `json:"top_blocked_domains"` DNSQueries []uint64 `json:"dns_queries"` @@ -45,17 +49,37 @@ type statsResponse struct { // handleStats is a handler for getting statistics. func (s *statsCtx) handleStats(w http.ResponseWriter, r *http.Request) { start := time.Now() - response, ok := s.getData() - log.Debug("Stats: prepared data in %v", time.Since(start)) - if !ok { - httpError(r, w, http.StatusInternalServerError, "Couldn't get statistics data") + var resp statsResponse + if s.conf.limit == 0 { + resp = statsResponse{ + TimeUnits: "days", - return + TopBlocked: []topAddrs{}, + TopClients: []topAddrs{}, + TopQueried: []topAddrs{}, + + BlockedFiltering: []uint64{}, + DNSQueries: []uint64{}, + ReplacedParental: []uint64{}, + ReplacedSafebrowsing: []uint64{}, + } + } else { + var ok bool + resp, ok = s.getData() + + log.Debug("stats: prepared data in %v", time.Since(start)) + + if !ok { + httpError(r, w, http.StatusInternalServerError, "Couldn't get statistics data") + + return + } } w.Header().Set("Content-Type", "application/json") - err := json.NewEncoder(w).Encode(response) + + err := json.NewEncoder(w).Encode(resp) if err != nil { httpError(r, w, http.StatusInternalServerError, "json encode: %s", err) diff --git a/internal/stats/unit.go b/internal/stats/unit.go index 968a2f66b21..8997da28868 100644 --- a/internal/stats/unit.go +++ b/internal/stats/unit.go @@ -147,7 +147,7 @@ func (s *statsCtx) Start() { } func checkInterval(days uint32) bool { - return days == 1 || days == 7 || days == 30 || days == 90 + return days == 0 || days == 1 || days == 7 || days == 30 || days == 90 } func (s *statsCtx) dbOpen() bool { @@ -251,7 +251,7 @@ func (s *statsCtx) periodicFlush() { } id := s.conf.UnitID() - if ptr.id == id { + if ptr.id == id || s.conf.limit == 0 { time.Sleep(time.Second) continue @@ -412,9 +412,11 @@ func convertTopSlice(a []countPair) []map[string]uint64 { } func (s *statsCtx) setLimit(limitDays int) { - conf := *s.conf - conf.limit = uint32(limitDays) * 24 - s.conf = &conf + s.conf.limit = uint32(limitDays) * 24 + if limitDays == 0 { + s.clear() + } + log.Debug("stats: set limit: %d", limitDays) } @@ -488,6 +490,10 @@ func (s *statsCtx) getClientIP(ip net.IP) (clientIP net.IP) { } func (s *statsCtx) Update(e Entry) { + if s.conf.limit == 0 { + return + } + if e.Result == 0 || e.Result >= rLast || e.Domain == "" || @@ -695,6 +701,10 @@ func (s *statsCtx) getData() (statsResponse, bool) { } func (s *statsCtx) GetTopClientsIP(maxCount uint) []net.IP { + if s.conf.limit == 0 { + return nil + } + units, _ := s.loadUnits(s.conf.limit) if units == nil { return nil diff --git a/openapi/CHANGELOG.md b/openapi/CHANGELOG.md index 5b0d28c042e..7660cc27725 100644 --- a/openapi/CHANGELOG.md +++ b/openapi/CHANGELOG.md @@ -4,6 +4,11 @@ ## v0.107: API changes +### Disabling Statistics + +* The API `POST /control/stats_config` HTTP API allows disabling statistics by + setting `"interval"` to `0`. + ### `POST /control/dhcp/reset_leases` * The new `POST /control/dhcp/reset_leases` HTTP API allows removing all leases diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 313c739f723..7b145ed0f44 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -1630,8 +1630,16 @@ 'description': 'Statistics configuration' 'properties': 'interval': + 'description': > + Time period to keep the data. `0` means that the statistics is + disabled. + 'enum': + - 0 + - 1 + - 7 + - 30 + - 90 'type': 'integer' - 'description': 'Time period to keep data (1 | 7 | 30 | 90)' 'DhcpConfig': 'type': 'object' 'properties':