Skip to content

Commit

Permalink
stats: delete units with bad names
Browse files Browse the repository at this point in the history
  • Loading branch information
ainar-g committed Aug 27, 2021
1 parent 8454e65 commit 8e5a243
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 44 deletions.
7 changes: 5 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ and this project adheres to
## [Unreleased]

<!--
## [v0.107.0] - 2021-08-03 (APPROX.)
## [v0.107.0] - 2021-09-14 (APPROX.)
-->

### Added
Expand Down Expand Up @@ -44,7 +44,8 @@ and this project adheres to

### Changed

- Don't show the private key in API responses if it was saved as a string ([#1898]).
- Don't show the private key in API responses if it was saved as a string
([#1898]).
- Better OpenWrt detection ([#3435]).
- DNS-over-HTTPS queries that come from HTTP proxies in the `trusted_proxies`
list now use the real IP address of the client instead of the address of the
Expand All @@ -67,6 +68,7 @@ and this project adheres to

### Fixed

- Occasional panics when reading old statistics databases ([#3506]).
- `reload` service action on macOS and FreeBSD ([#3457]).
- Inaccurate using of service actions in the installation script ([#3450]).
- Client ID checking ([#3437]).
Expand Down Expand Up @@ -134,6 +136,7 @@ and this project adheres to
[#3437]: https://github.com/AdguardTeam/AdGuardHome/issues/3437
[#3450]: https://github.com/AdguardTeam/AdGuardHome/issues/3450
[#3457]: https://github.com/AdguardTeam/AdGuardHome/issues/3457
[#3506]: https://github.com/AdguardTeam/AdGuardHome/issues/3506



Expand Down
94 changes: 56 additions & 38 deletions internal/stats/unit.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,29 +90,7 @@ func createObject(conf Config) (s *statsCtx, err error) {
firstID := id - s.conf.limit - 1
unitDel := 0

// TODO(a.garipov): See if this is actually necessary. Looks
// like a rather bizarre solution.
errStop := errors.Error("stop iteration")
forEachBkt := func(name []byte, _ *bolt.Bucket) (cberr error) {
nameID := uint32(btoi(name))
if nameID < firstID {
cberr = tx.DeleteBucket(name)
if cberr != nil {
log.Debug("stats: tx.DeleteBucket: %s", cberr)

return nil
}

log.Debug("stats: deleted unit %d", nameID)
unitDel++

return nil
}

return errStop
}

err = tx.ForEach(forEachBkt)
err = tx.ForEach(newBucketWalker(tx, &unitDel, firstID))
if err != nil && !errors.Is(err, errStop) {
log.Debug("stats: deleting units: %s", err)
}
Expand Down Expand Up @@ -141,6 +119,39 @@ func createObject(conf Config) (s *statsCtx, err error) {
return s, nil
}

// TODO(a.garipov): See if this is actually necessary. Looks like a rather
// bizarre solution.
const errStop errors.Error = "stop iteration"

// newBucketWalker returns a new bucket walker that deletes old units. The
// integer that unitDelPtr points to is incremented for every successful
// deletion. If the bucket isn't deleted, f returns errStop.
func newBucketWalker(
tx *bolt.Tx,
unitDelPtr *int,
firstID uint32,
) (f func(name []byte, b *bolt.Bucket) (err error)) {
return func(name []byte, _ *bolt.Bucket) (err error) {
nameID, ok := unitNameToID(name)
if !ok || nameID < firstID {
err = tx.DeleteBucket(name)
if err != nil {
log.Debug("stats: tx.DeleteBucket: %s", err)

return nil
}

log.Debug("stats: deleted unit %d (name %x)", nameID, name)

*unitDelPtr++

return nil
}

return errStop
}
}

func (s *statsCtx) Start() {
s.initWeb()
go s.periodicFlush()
Expand Down Expand Up @@ -215,21 +226,28 @@ func (s *statsCtx) commitTxn(tx *bolt.Tx) {
log.Tracef("tx.Commit")
}

// Get unit name
func unitName(id uint32) []byte {
return itob(uint64(id))
}
// bucketNameLen is the length of a bucket, a 64-bit unsigned integer.
//
// TODO(a.garipov): Find out why a 64-bit integer is used when IDs seem to
// always be 32 bits.
const bucketNameLen = 8

// idToUnitName converts a numerical ID into a database unit name.
func idToUnitName(id uint32) (name []byte) {
name = make([]byte, bucketNameLen)
binary.BigEndian.PutUint64(name, uint64(id))

// Convert integer to 8-byte array (big endian)
func itob(v uint64) []byte {
b := make([]byte, 8)
binary.BigEndian.PutUint64(b, v)
return b
return name
}

// Convert 8-byte array (big endian) to integer
func btoi(b []byte) uint64 {
return binary.BigEndian.Uint64(b)
// unitNameToID converts a database unit name into a numerical ID. ok is false
// if name is not a valid database unit name.
func unitNameToID(name []byte) (id uint32, ok bool) {
if len(name) < bucketNameLen {
return 0, false
}

return uint32(binary.BigEndian.Uint64(name)), true
}

// Flush the current unit to DB and delete an old unit when a new hour is started
Expand Down Expand Up @@ -282,7 +300,7 @@ func (s *statsCtx) periodicFlush() {

// Delete unit's data from file
func (s *statsCtx) deleteUnit(tx *bolt.Tx, id uint32) bool {
err := tx.DeleteBucket(unitName(id))
err := tx.DeleteBucket(idToUnitName(id))
if err != nil {
log.Tracef("stats: bolt DeleteBucket: %s", err)

Expand Down Expand Up @@ -357,7 +375,7 @@ func deserialize(u *unit, udb *unitDB) {
func (s *statsCtx) flushUnitToDB(tx *bolt.Tx, id uint32, udb *unitDB) bool {
log.Tracef("Flushing unit %d", id)

bkt, err := tx.CreateBucketIfNotExists(unitName(id))
bkt, err := tx.CreateBucketIfNotExists(idToUnitName(id))
if err != nil {
log.Error("tx.CreateBucketIfNotExists: %s", err)
return false
Expand All @@ -381,7 +399,7 @@ func (s *statsCtx) flushUnitToDB(tx *bolt.Tx, id uint32, udb *unitDB) bool {
}

func (s *statsCtx) loadUnitFromDB(tx *bolt.Tx, id uint32) *unitDB {
bkt := tx.Bucket(unitName(id))
bkt := tx.Bucket(idToUnitName(id))
if bkt == nil {
return nil
}
Expand Down
8 changes: 4 additions & 4 deletions scripts/make/go-lint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -201,12 +201,12 @@ golint --set_exit_status ./...

# Apply more lax standards to the code we haven't properly refactored yet.
gocyclo --over 17 ./internal/dhcpd/ ./internal/dnsforward/\
./internal/filtering/ ./internal/home/ ./internal/querylog/\
./internal/stats/ ./internal/updater/
./internal/filtering/ ./internal/home/ ./internal/querylog/

# Apply stricter standards to new or vetted code
# Apply stricter standards to new or somewhat refactored code.
gocyclo --over 10 ./internal/aghio/ ./internal/aghnet/ ./internal/aghos/\
./internal/aghtest/ ./internal/tools/ ./internal/version/ ./main.go
./internal/aghtest/ ./internal/stats/ ./internal/tools/\
./internal/updater/ ./internal/version/ ./main.go

gosec --quiet $go_files

Expand Down

0 comments on commit 8e5a243

Please sign in to comment.