2019-08-22 16:34:58 +03:00
package stats
import (
"bytes"
"encoding/binary"
"encoding/gob"
"fmt"
2020-03-03 20:21:53 +03:00
"net"
2019-08-22 16:34:58 +03:00
"os"
"sort"
"sync"
"time"
"github.com/AdguardTeam/golibs/log"
2020-04-05 18:21:26 +03:00
bolt "go.etcd.io/bbolt"
2019-08-22 16:34:58 +03:00
)
const (
maxDomains = 100 // max number of top domains to store in file or return via Get()
maxClients = 100 // max number of top clients to store in file or return via Get()
)
// statsCtx - global context
type statsCtx struct {
2019-12-11 12:38:58 +03:00
db * bolt . DB
conf * Config
2019-08-22 16:34:58 +03:00
unit * unit // the current unit
unitLock sync . Mutex // protect 'unit'
}
// data for 1 time unit
type unit struct {
2019-09-10 17:59:10 +03:00
id uint32 // unit ID. Default: absolute hour since Jan 1, 1970
2019-08-22 16:34:58 +03:00
2019-09-10 17:59:10 +03:00
nTotal uint64 // total requests
nResult [ ] uint64 // number of requests per one result
timeSum uint64 // sum of processing time of all requests (usec)
2019-08-22 16:34:58 +03:00
// top:
2019-09-10 17:59:10 +03:00
domains map [ string ] uint64 // number of requests per domain
blockedDomains map [ string ] uint64 // number of blocked requests per domain
clients map [ string ] uint64 // number of requests per client
2019-08-22 16:34:58 +03:00
}
// name-count pair
type countPair struct {
Name string
2019-09-10 17:59:10 +03:00
Count uint64
2019-08-22 16:34:58 +03:00
}
// structure for storing data in file
type unitDB struct {
2019-09-10 17:59:10 +03:00
NTotal uint64
NResult [ ] uint64
2019-08-22 16:34:58 +03:00
Domains [ ] countPair
BlockedDomains [ ] countPair
Clients [ ] countPair
2019-09-10 17:59:10 +03:00
TimeAvg uint32 // usec
2019-08-22 16:34:58 +03:00
}
2019-09-16 16:14:52 +03:00
func createObject ( conf Config ) ( * statsCtx , error ) {
2019-08-22 16:34:58 +03:00
s := statsCtx { }
2019-09-25 15:36:09 +03:00
if ! checkInterval ( conf . LimitDays ) {
conf . LimitDays = 1
}
2019-12-11 12:38:58 +03:00
s . conf = & Config { }
* s . conf = conf
s . conf . limit = conf . LimitDays * 24
2019-09-16 16:14:52 +03:00
if conf . UnitID == nil {
s . conf . UnitID = newUnitID
2019-08-22 16:34:58 +03:00
}
if ! s . dbOpen ( ) {
2019-09-06 15:42:21 +03:00
return nil , fmt . Errorf ( "open database" )
2019-08-22 16:34:58 +03:00
}
2019-09-16 16:14:52 +03:00
id := s . conf . UnitID ( )
2019-08-22 16:34:58 +03:00
tx := s . beginTxn ( true )
var udb * unitDB
if tx != nil {
log . Tracef ( "Deleting old units..." )
2019-12-11 12:38:58 +03:00
firstID := id - s . conf . limit - 1
2019-08-22 16:34:58 +03:00
unitDel := 0
forEachBkt := func ( name [ ] byte , b * bolt . Bucket ) error {
2019-09-10 17:59:10 +03:00
id := uint32 ( btoi ( name ) )
2019-08-22 16:34:58 +03:00
if id < firstID {
err := tx . DeleteBucket ( name )
if err != nil {
log . Debug ( "tx.DeleteBucket: %s" , err )
}
log . Debug ( "Stats: deleted unit %d" , id )
unitDel ++
return nil
}
return fmt . Errorf ( "" )
}
_ = tx . ForEach ( forEachBkt )
udb = s . loadUnitFromDB ( tx , id )
if unitDel != 0 {
s . commitTxn ( tx )
} else {
_ = tx . Rollback ( )
}
}
u := unit { }
s . initUnit ( & u , id )
if udb != nil {
deserialize ( & u , udb )
}
s . unit = & u
log . Debug ( "Stats: initialized" )
2019-09-06 15:42:21 +03:00
return & s , nil
2019-08-22 16:34:58 +03:00
}
2020-01-16 14:25:40 +03:00
func ( s * statsCtx ) Start ( ) {
s . initWeb ( )
go s . periodicFlush ( )
}
2019-09-25 15:36:09 +03:00
func checkInterval ( days uint32 ) bool {
return days == 1 || days == 7 || days == 30 || days == 90
}
2019-08-22 16:34:58 +03:00
func ( s * statsCtx ) dbOpen ( ) bool {
var err error
log . Tracef ( "db.Open..." )
2019-09-16 16:14:52 +03:00
s . db , err = bolt . Open ( s . conf . Filename , 0644 , nil )
2019-08-22 16:34:58 +03:00
if err != nil {
2019-09-16 16:14:52 +03:00
log . Error ( "Stats: open DB: %s: %s" , s . conf . Filename , err )
2019-12-09 14:13:39 +03:00
if err . Error ( ) == "invalid argument" {
2020-10-30 13:32:02 +03:00
log . Error ( "AdGuard Home cannot be initialized due to an incompatible file system.\nPlease read the explanation here: https://github.com/AdguardTeam/AdGuardHome/internal/wiki/Getting-Started#limitations" )
2019-12-09 14:13:39 +03:00
}
2019-08-22 16:34:58 +03:00
return false
}
log . Tracef ( "db.Open" )
return true
}
// Atomically swap the currently active unit with a new value
// Return old value
func ( s * statsCtx ) swapUnit ( new * unit ) * unit {
s . unitLock . Lock ( )
u := s . unit
s . unit = new
s . unitLock . Unlock ( )
return u
}
// Get unit ID for the current hour
2019-09-10 17:59:10 +03:00
func newUnitID ( ) uint32 {
return uint32 ( time . Now ( ) . Unix ( ) / ( 60 * 60 ) )
2019-08-22 16:34:58 +03:00
}
// Initialize a unit
2019-09-10 17:59:10 +03:00
func ( s * statsCtx ) initUnit ( u * unit , id uint32 ) {
2019-08-22 16:34:58 +03:00
u . id = id
2019-09-10 17:59:10 +03:00
u . nResult = make ( [ ] uint64 , rLast )
u . domains = make ( map [ string ] uint64 )
u . blockedDomains = make ( map [ string ] uint64 )
u . clients = make ( map [ string ] uint64 )
2019-08-22 16:34:58 +03:00
}
// Open a DB transaction
func ( s * statsCtx ) beginTxn ( wr bool ) * bolt . Tx {
db := s . db
if db == nil {
return nil
}
log . Tracef ( "db.Begin..." )
tx , err := db . Begin ( wr )
if err != nil {
log . Error ( "db.Begin: %s" , err )
return nil
}
log . Tracef ( "db.Begin" )
return tx
}
func ( s * statsCtx ) commitTxn ( tx * bolt . Tx ) {
err := tx . Commit ( )
if err != nil {
log . Debug ( "tx.Commit: %s" , err )
return
}
log . Tracef ( "tx.Commit" )
}
// Get unit name
2019-09-10 17:59:10 +03:00
func unitName ( id uint32 ) [ ] byte {
return itob ( uint64 ( id ) )
2019-08-22 16:34:58 +03:00
}
// Convert integer to 8-byte array (big endian)
2019-09-10 17:59:10 +03:00
func itob ( v uint64 ) [ ] byte {
2019-08-22 16:34:58 +03:00
b := make ( [ ] byte , 8 )
2019-09-10 17:59:10 +03:00
binary . BigEndian . PutUint64 ( b , v )
2019-08-22 16:34:58 +03:00
return b
}
// Convert 8-byte array (big endian) to integer
2019-09-10 17:59:10 +03:00
func btoi ( b [ ] byte ) uint64 {
return binary . BigEndian . Uint64 ( b )
2019-08-22 16:34:58 +03:00
}
// Flush the current unit to DB and delete an old unit when a new hour is started
2019-11-11 16:18:20 +03:00
// If a unit must be flushed:
// . lock DB
// . atomically set a new empty unit as the current one and get the old unit
// This is important to do it inside DB lock, so the reader won't get inconsistent results.
// . write the unit to DB
// . remove the stale unit from DB
// . unlock DB
2019-08-22 16:34:58 +03:00
func ( s * statsCtx ) periodicFlush ( ) {
for {
s . unitLock . Lock ( )
ptr := s . unit
s . unitLock . Unlock ( )
if ptr == nil {
break
}
2019-09-16 16:14:52 +03:00
id := s . conf . UnitID ( )
2019-08-22 16:34:58 +03:00
if ptr . id == id {
time . Sleep ( time . Second )
continue
}
2019-11-11 16:18:20 +03:00
tx := s . beginTxn ( true )
2019-08-22 16:34:58 +03:00
nu := unit { }
s . initUnit ( & nu , id )
u := s . swapUnit ( & nu )
udb := serialize ( u )
if tx == nil {
continue
}
ok1 := s . flushUnitToDB ( tx , u . id , udb )
2019-12-11 12:38:58 +03:00
ok2 := s . deleteUnit ( tx , id - s . conf . limit )
2019-08-22 16:34:58 +03:00
if ok1 || ok2 {
s . commitTxn ( tx )
} else {
_ = tx . Rollback ( )
}
}
log . Tracef ( "periodicFlush() exited" )
}
// Delete unit's data from file
2019-09-10 17:59:10 +03:00
func ( s * statsCtx ) deleteUnit ( tx * bolt . Tx , id uint32 ) bool {
2019-08-22 16:34:58 +03:00
err := tx . DeleteBucket ( unitName ( id ) )
if err != nil {
log . Tracef ( "bolt DeleteBucket: %s" , err )
return false
}
log . Debug ( "Stats: deleted unit %d" , id )
return true
}
2019-09-10 17:59:10 +03:00
func convertMapToArray ( m map [ string ] uint64 , max int ) [ ] countPair {
2019-08-22 16:34:58 +03:00
a := [ ] countPair { }
for k , v := range m {
pair := countPair { }
pair . Name = k
2019-09-10 17:59:10 +03:00
pair . Count = v
2019-08-22 16:34:58 +03:00
a = append ( a , pair )
}
less := func ( i , j int ) bool {
if a [ i ] . Count >= a [ j ] . Count {
return true
}
return false
}
sort . Slice ( a , less )
if max > len ( a ) {
max = len ( a )
}
return a [ : max ]
}
2019-09-10 17:59:10 +03:00
func convertArrayToMap ( a [ ] countPair ) map [ string ] uint64 {
m := map [ string ] uint64 { }
2019-08-22 16:34:58 +03:00
for _ , it := range a {
2019-09-10 17:59:10 +03:00
m [ it . Name ] = it . Count
2019-08-22 16:34:58 +03:00
}
return m
}
func serialize ( u * unit ) * unitDB {
udb := unitDB { }
2019-09-10 17:59:10 +03:00
udb . NTotal = u . nTotal
2019-08-22 16:34:58 +03:00
for _ , it := range u . nResult {
2019-09-10 17:59:10 +03:00
udb . NResult = append ( udb . NResult , it )
2019-08-22 16:34:58 +03:00
}
if u . nTotal != 0 {
2019-09-10 17:59:10 +03:00
udb . TimeAvg = uint32 ( u . timeSum / u . nTotal )
2019-08-22 16:34:58 +03:00
}
udb . Domains = convertMapToArray ( u . domains , maxDomains )
udb . BlockedDomains = convertMapToArray ( u . blockedDomains , maxDomains )
udb . Clients = convertMapToArray ( u . clients , maxClients )
return & udb
}
func deserialize ( u * unit , udb * unitDB ) {
2019-09-10 17:59:10 +03:00
u . nTotal = udb . NTotal
2019-09-10 18:04:43 +03:00
n := len ( udb . NResult )
if n < len ( u . nResult ) {
n = len ( u . nResult ) // n = min(len(udb.NResult), len(u.nResult))
}
2019-09-13 12:55:01 +03:00
for i := 1 ; i < n ; i ++ {
2019-09-10 18:04:43 +03:00
u . nResult [ i ] = udb . NResult [ i ]
2019-08-22 16:34:58 +03:00
}
2019-09-10 18:04:43 +03:00
2019-08-22 16:34:58 +03:00
u . domains = convertArrayToMap ( udb . Domains )
u . blockedDomains = convertArrayToMap ( udb . BlockedDomains )
u . clients = convertArrayToMap ( udb . Clients )
2019-09-10 17:59:10 +03:00
u . timeSum = uint64 ( udb . TimeAvg ) * u . nTotal
2019-08-22 16:34:58 +03:00
}
2019-09-10 17:59:10 +03:00
func ( s * statsCtx ) flushUnitToDB ( tx * bolt . Tx , id uint32 , udb * unitDB ) bool {
2019-08-22 16:34:58 +03:00
log . Tracef ( "Flushing unit %d" , id )
bkt , err := tx . CreateBucketIfNotExists ( unitName ( id ) )
if err != nil {
log . Error ( "tx.CreateBucketIfNotExists: %s" , err )
return false
}
var buf bytes . Buffer
enc := gob . NewEncoder ( & buf )
err = enc . Encode ( udb )
if err != nil {
log . Error ( "gob.Encode: %s" , err )
return false
}
err = bkt . Put ( [ ] byte { 0 } , buf . Bytes ( ) )
if err != nil {
log . Error ( "bkt.Put: %s" , err )
return false
}
return true
}
2019-09-10 17:59:10 +03:00
func ( s * statsCtx ) loadUnitFromDB ( tx * bolt . Tx , id uint32 ) * unitDB {
2019-08-22 16:34:58 +03:00
bkt := tx . Bucket ( unitName ( id ) )
if bkt == nil {
return nil
}
2019-10-23 16:48:00 +03:00
// log.Tracef("Loading unit %d", id)
2019-08-22 16:34:58 +03:00
var buf bytes . Buffer
buf . Write ( bkt . Get ( [ ] byte { 0 } ) )
dec := gob . NewDecoder ( & buf )
udb := unitDB { }
err := dec . Decode ( & udb )
if err != nil {
log . Error ( "gob Decode: %s" , err )
return nil
}
return & udb
}
2019-09-10 17:59:10 +03:00
func convertTopArray ( a [ ] countPair ) [ ] map [ string ] uint64 {
m := [ ] map [ string ] uint64 { }
2019-08-22 16:34:58 +03:00
for _ , it := range a {
2019-09-10 17:59:10 +03:00
ent := map [ string ] uint64 { }
2019-08-22 16:34:58 +03:00
ent [ it . Name ] = it . Count
m = append ( m , ent )
}
return m
}
2019-09-25 15:36:09 +03:00
func ( s * statsCtx ) setLimit ( limitDays int ) {
2019-12-11 12:38:58 +03:00
conf := * s . conf
conf . limit = uint32 ( limitDays ) * 24
s . conf = & conf
2019-09-25 15:36:09 +03:00
log . Debug ( "Stats: set limit: %d" , limitDays )
}
func ( s * statsCtx ) WriteDiskConfig ( dc * DiskConfig ) {
2019-12-11 12:38:58 +03:00
dc . Interval = s . conf . limit / 24
2019-08-22 16:34:58 +03:00
}
func ( s * statsCtx ) Close ( ) {
u := s . swapUnit ( nil )
udb := serialize ( u )
tx := s . beginTxn ( true )
if tx != nil {
if s . flushUnitToDB ( tx , u . id , udb ) {
s . commitTxn ( tx )
} else {
_ = tx . Rollback ( )
}
}
if s . db != nil {
log . Tracef ( "db.Close..." )
_ = s . db . Close ( )
log . Tracef ( "db.Close" )
}
log . Debug ( "Stats: closed" )
}
2019-09-25 15:36:09 +03:00
// Reset counters and clear database
func ( s * statsCtx ) clear ( ) {
2019-08-22 16:34:58 +03:00
tx := s . beginTxn ( true )
if tx != nil {
db := s . db
s . db = nil
_ = tx . Rollback ( )
// the active transactions can continue using database,
// but no new transactions will be opened
_ = db . Close ( )
log . Tracef ( "db.Close" )
// all active transactions are now closed
}
u := unit { }
2019-09-16 16:14:52 +03:00
s . initUnit ( & u , s . conf . UnitID ( ) )
2019-08-22 16:34:58 +03:00
_ = s . swapUnit ( & u )
2019-09-16 16:14:52 +03:00
err := os . Remove ( s . conf . Filename )
2019-08-22 16:34:58 +03:00
if err != nil {
log . Error ( "os.Remove: %s" , err )
}
_ = s . dbOpen ( )
log . Debug ( "Stats: cleared" )
}
2020-03-03 20:21:53 +03:00
// Get Client IP address
func ( s * statsCtx ) getClientIP ( clientIP string ) string {
if s . conf . AnonymizeClientIP {
ip := net . ParseIP ( clientIP )
if ip != nil {
ip4 := ip . To4 ( )
2020-06-03 19:08:11 +03:00
const AnonymizeClientIP4Mask = 16
2020-03-03 20:21:53 +03:00
const AnonymizeClientIP6Mask = 112
if ip4 != nil {
clientIP = ip4 . Mask ( net . CIDRMask ( AnonymizeClientIP4Mask , 32 ) ) . String ( )
} else {
clientIP = ip . Mask ( net . CIDRMask ( AnonymizeClientIP6Mask , 128 ) ) . String ( )
}
}
}
return clientIP
}
2019-08-22 16:34:58 +03:00
func ( s * statsCtx ) Update ( e Entry ) {
if e . Result == 0 ||
2019-09-16 15:54:41 +03:00
e . Result >= rLast ||
2019-08-22 16:34:58 +03:00
len ( e . Domain ) == 0 ||
! ( len ( e . Client ) == 4 || len ( e . Client ) == 16 ) {
return
}
2020-03-03 20:21:53 +03:00
client := s . getClientIP ( e . Client . String ( ) )
2019-08-22 16:34:58 +03:00
s . unitLock . Lock ( )
u := s . unit
u . nResult [ e . Result ] ++
if e . Result == RNotFiltered {
u . domains [ e . Domain ] ++
} else {
u . blockedDomains [ e . Domain ] ++
}
u . clients [ client ] ++
2019-09-10 17:59:10 +03:00
u . timeSum += uint64 ( e . Time )
2019-08-22 16:34:58 +03:00
u . nTotal ++
s . unitLock . Unlock ( )
}
2019-12-11 12:38:58 +03:00
func ( s * statsCtx ) loadUnits ( limit uint32 ) ( [ ] * unitDB , uint32 ) {
2019-10-07 15:55:09 +03:00
tx := s . beginTxn ( false )
if tx == nil {
2019-11-11 16:18:20 +03:00
return nil , 0
2019-10-07 15:55:09 +03:00
}
2019-11-11 16:18:20 +03:00
s . unitLock . Lock ( )
curUnit := serialize ( s . unit )
curID := s . unit . id
s . unitLock . Unlock ( )
2019-10-07 15:55:09 +03:00
units := [ ] * unitDB { } //per-hour units
2019-12-11 12:38:58 +03:00
firstID := curID - limit + 1
2019-11-11 16:18:20 +03:00
for i := firstID ; i != curID ; i ++ {
2019-10-07 15:55:09 +03:00
u := s . loadUnitFromDB ( tx , i )
if u == nil {
u = & unitDB { }
u . NResult = make ( [ ] uint64 , rLast )
}
units = append ( units , u )
}
_ = tx . Rollback ( )
2019-11-11 16:18:20 +03:00
units = append ( units , curUnit )
2019-10-07 15:55:09 +03:00
2019-12-11 12:38:58 +03:00
if len ( units ) != int ( limit ) {
log . Fatalf ( "len(units) != limit: %d %d" , len ( units ) , limit )
2019-10-07 15:55:09 +03:00
}
2019-11-11 16:18:20 +03:00
return units , firstID
2019-10-07 15:55:09 +03:00
}
2019-08-22 16:34:58 +03:00
/ * Algorithm :
. Prepare array of N units , where N is the value of "limit" configuration setting
. Load data for the most recent units from file
If a unit with required ID doesn ' t exist , just add an empty unit
. Get data for the current unit
. Process data from the units and prepare an output map object :
* per time unit counters :
* DNS - queries / time - unit
* blocked / time - unit
* safebrowsing - blocked / time - unit
* parental - blocked / time - unit
If time - unit is an hour , just add values from each unit to an array .
If time - unit is a day , aggregate per - hour data into days .
* top counters :
* queries / domain
* queries / blocked - domain
* queries / client
To get these values we first sum up data for all units into a single map .
Then we get the pairs with the highest numbers ( the values are sorted in descending order )
* total counters :
* DNS - queries
* blocked
* safebrowsing - blocked
* safesearch - blocked
* parental - blocked
These values are just the sum of data for all units .
* /
// nolint (gocyclo)
2019-12-11 12:38:58 +03:00
func ( s * statsCtx ) getData ( ) map [ string ] interface { } {
limit := s . conf . limit
2019-08-22 16:34:58 +03:00
d := map [ string ] interface { } { }
2019-12-11 12:38:58 +03:00
timeUnit := Hours
if limit / 24 > 7 {
timeUnit = Days
}
2019-08-22 16:34:58 +03:00
2019-12-11 12:38:58 +03:00
units , firstID := s . loadUnits ( limit )
2019-10-07 15:55:09 +03:00
if units == nil {
return nil
2019-08-22 16:34:58 +03:00
}
// per time unit counters:
// 720 hours may span 31 days, so we skip data for the first day in this case
firstDayID := ( firstID + 24 - 1 ) / 24 * 24 // align_ceil(24)
2019-09-10 17:59:10 +03:00
a := [ ] uint64 { }
2019-08-22 16:34:58 +03:00
if timeUnit == Hours {
for _ , u := range units {
a = append ( a , u . NTotal )
}
} else {
2019-09-10 17:59:10 +03:00
var sum uint64
2019-08-22 16:34:58 +03:00
id := firstDayID
nextDayID := firstDayID + 24
2019-09-10 17:59:10 +03:00
for i := firstDayID - firstID ; int ( i ) != len ( units ) ; i ++ {
2019-08-22 16:34:58 +03:00
sum += units [ i ] . NTotal
if id == nextDayID {
a = append ( a , sum )
sum = 0
nextDayID += 24
}
id ++
}
2019-10-09 17:17:57 +03:00
if id <= nextDayID {
2019-08-22 16:34:58 +03:00
a = append ( a , sum )
}
2019-12-11 12:38:58 +03:00
if len ( a ) != int ( limit / 24 ) {
log . Fatalf ( "len(a) != limit: %d %d" , len ( a ) , limit )
2019-08-22 16:34:58 +03:00
}
}
d [ "dns_queries" ] = a
2019-09-10 17:59:10 +03:00
a = [ ] uint64 { }
2019-08-22 16:34:58 +03:00
if timeUnit == Hours {
for _ , u := range units {
a = append ( a , u . NResult [ RFiltered ] )
}
} else {
2019-09-10 17:59:10 +03:00
var sum uint64
2019-08-22 16:34:58 +03:00
id := firstDayID
nextDayID := firstDayID + 24
2019-09-10 17:59:10 +03:00
for i := firstDayID - firstID ; int ( i ) != len ( units ) ; i ++ {
2019-08-22 16:34:58 +03:00
sum += units [ i ] . NResult [ RFiltered ]
if id == nextDayID {
a = append ( a , sum )
sum = 0
nextDayID += 24
}
id ++
}
2019-10-09 17:17:57 +03:00
if id <= nextDayID {
2019-08-22 16:34:58 +03:00
a = append ( a , sum )
}
}
d [ "blocked_filtering" ] = a
2019-09-10 17:59:10 +03:00
a = [ ] uint64 { }
2019-08-22 16:34:58 +03:00
if timeUnit == Hours {
for _ , u := range units {
a = append ( a , u . NResult [ RSafeBrowsing ] )
}
} else {
2019-09-10 17:59:10 +03:00
var sum uint64
2019-08-22 16:34:58 +03:00
id := firstDayID
nextDayID := firstDayID + 24
2019-09-10 17:59:10 +03:00
for i := firstDayID - firstID ; int ( i ) != len ( units ) ; i ++ {
2019-08-22 16:34:58 +03:00
sum += units [ i ] . NResult [ RSafeBrowsing ]
if id == nextDayID {
a = append ( a , sum )
sum = 0
nextDayID += 24
}
id ++
}
2019-10-09 17:17:57 +03:00
if id <= nextDayID {
2019-08-22 16:34:58 +03:00
a = append ( a , sum )
}
}
d [ "replaced_safebrowsing" ] = a
2019-09-10 17:59:10 +03:00
a = [ ] uint64 { }
2019-08-22 16:34:58 +03:00
if timeUnit == Hours {
for _ , u := range units {
a = append ( a , u . NResult [ RParental ] )
}
} else {
2019-09-10 17:59:10 +03:00
var sum uint64
2019-08-22 16:34:58 +03:00
id := firstDayID
nextDayID := firstDayID + 24
2019-09-10 17:59:10 +03:00
for i := firstDayID - firstID ; int ( i ) != len ( units ) ; i ++ {
2019-08-22 16:34:58 +03:00
sum += units [ i ] . NResult [ RParental ]
if id == nextDayID {
a = append ( a , sum )
sum = 0
nextDayID += 24
}
id ++
}
2019-10-09 17:17:57 +03:00
if id <= nextDayID {
2019-08-22 16:34:58 +03:00
a = append ( a , sum )
}
}
d [ "replaced_parental" ] = a
// top counters:
2019-09-10 17:59:10 +03:00
m := map [ string ] uint64 { }
2019-08-22 16:34:58 +03:00
for _ , u := range units {
for _ , it := range u . Domains {
2019-09-10 17:59:10 +03:00
m [ it . Name ] += it . Count
2019-08-22 16:34:58 +03:00
}
}
a2 := convertMapToArray ( m , maxDomains )
d [ "top_queried_domains" ] = convertTopArray ( a2 )
2019-09-10 17:59:10 +03:00
m = map [ string ] uint64 { }
2019-08-22 16:34:58 +03:00
for _ , u := range units {
for _ , it := range u . BlockedDomains {
2019-09-10 17:59:10 +03:00
m [ it . Name ] += it . Count
2019-08-22 16:34:58 +03:00
}
}
a2 = convertMapToArray ( m , maxDomains )
d [ "top_blocked_domains" ] = convertTopArray ( a2 )
2019-09-10 17:59:10 +03:00
m = map [ string ] uint64 { }
2019-08-22 16:34:58 +03:00
for _ , u := range units {
for _ , it := range u . Clients {
2019-09-10 17:59:10 +03:00
m [ it . Name ] += it . Count
2019-08-22 16:34:58 +03:00
}
}
a2 = convertMapToArray ( m , maxClients )
d [ "top_clients" ] = convertTopArray ( a2 )
// total counters:
sum := unitDB { }
2019-09-10 17:59:10 +03:00
sum . NResult = make ( [ ] uint64 , rLast )
2019-08-22 16:34:58 +03:00
timeN := 0
for _ , u := range units {
sum . NTotal += u . NTotal
sum . TimeAvg += u . TimeAvg
if u . TimeAvg != 0 {
timeN ++
}
sum . NResult [ RFiltered ] += u . NResult [ RFiltered ]
sum . NResult [ RSafeBrowsing ] += u . NResult [ RSafeBrowsing ]
sum . NResult [ RSafeSearch ] += u . NResult [ RSafeSearch ]
sum . NResult [ RParental ] += u . NResult [ RParental ]
}
d [ "num_dns_queries" ] = sum . NTotal
d [ "num_blocked_filtering" ] = sum . NResult [ RFiltered ]
d [ "num_replaced_safebrowsing" ] = sum . NResult [ RSafeBrowsing ]
d [ "num_replaced_safesearch" ] = sum . NResult [ RSafeSearch ]
d [ "num_replaced_parental" ] = sum . NResult [ RParental ]
avgTime := float64 ( 0 )
if timeN != 0 {
2019-09-10 17:59:10 +03:00
avgTime = float64 ( sum . TimeAvg / uint32 ( timeN ) ) / 1000000
2019-08-22 16:34:58 +03:00
}
d [ "avg_processing_time" ] = avgTime
d [ "time_units" ] = "hours"
if timeUnit == Days {
d [ "time_units" ] = "days"
}
return d
}
2019-10-07 15:56:33 +03:00
2019-12-11 12:38:58 +03:00
func ( s * statsCtx ) GetTopClientsIP ( maxCount uint ) [ ] string {
units , _ := s . loadUnits ( s . conf . limit )
2019-10-07 15:56:33 +03:00
if units == nil {
return nil
}
// top clients
m := map [ string ] uint64 { }
for _ , u := range units {
for _ , it := range u . Clients {
m [ it . Name ] += it . Count
}
}
2019-12-11 12:38:58 +03:00
a := convertMapToArray ( m , int ( maxCount ) )
2019-10-07 15:56:33 +03:00
d := [ ] string { }
for _ , it := range a {
d = append ( d , it . Name )
}
return d
}