[counter] Record first seen and last updated on counters

Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
Knut Ahlers 2024-04-04 18:25:03 +02:00
parent b131a7be5f
commit 94b040ed81
Signed by: luzifer
SSH key fingerprint: SHA256:/xtE5lCgiRDQr8SLxHMS92ZBlACmATUmF1crK16Ks4E
3 changed files with 30 additions and 15 deletions

View file

@ -7,6 +7,7 @@ import (
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -193,7 +194,7 @@ func Register(args plugins.RegistrationArguments) (err error) {
mod = val[0] mod = val[0]
} }
if err := updateCounter(db, name, mod, false); err != nil { if err := updateCounter(db, name, mod, false, time.Now()); err != nil {
return 0, errors.Wrap(err, "updating counter") return 0, errors.Wrap(err, "updating counter")
} }
@ -230,7 +231,7 @@ func (actorCounter) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, even
} }
return false, errors.Wrap( return false, errors.Wrap(
updateCounter(db, counterName, counterValue, true), updateCounter(db, counterName, counterValue, true, time.Now()),
"set counter", "set counter",
) )
} }
@ -249,7 +250,7 @@ func (actorCounter) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, even
} }
return false, errors.Wrap( return false, errors.Wrap(
updateCounter(db, counterName, counterStep, false), updateCounter(db, counterName, counterStep, false, time.Now()),
"update counter", "update counter",
) )
} }
@ -298,7 +299,7 @@ func routeActorCounterSetValue(w http.ResponseWriter, r *http.Request) {
return return
} }
if err = updateCounter(db, mux.Vars(r)["name"], value, absolute); err != nil { if err = updateCounter(db, mux.Vars(r)["name"], value, absolute, time.Now()); err != nil {
http.Error(w, errors.Wrap(err, "updating value").Error(), http.StatusInternalServerError) http.Error(w, errors.Wrap(err, "updating value").Error(), http.StatusInternalServerError)
return return
} }

View file

@ -1,6 +1,8 @@
package counter package counter
import ( import (
"time"
"github.com/pkg/errors" "github.com/pkg/errors"
"gorm.io/gorm" "gorm.io/gorm"
"gorm.io/gorm/clause" "gorm.io/gorm/clause"
@ -11,8 +13,10 @@ import (
type ( type (
counter struct { counter struct {
Name string `gorm:"primaryKey"` Name string `gorm:"primaryKey"`
Value int64 Value int64
FirstSeen time.Time
LastModified time.Time
} }
) )
@ -32,7 +36,7 @@ func getCounterValue(db database.Connector, counterName string) (int64, error) {
} }
//revive:disable-next-line:flag-parameter //revive:disable-next-line:flag-parameter
func updateCounter(db database.Connector, counterName string, value int64, absolute bool) error { func updateCounter(db database.Connector, counterName string, value int64, absolute bool, atTime time.Time) error {
if !absolute { if !absolute {
cv, err := getCounterValue(db, counterName) cv, err := getCounterValue(db, counterName)
if err != nil { if err != nil {
@ -46,8 +50,8 @@ func updateCounter(db database.Connector, counterName string, value int64, absol
helpers.RetryTransaction(db.DB(), func(tx *gorm.DB) error { helpers.RetryTransaction(db.DB(), func(tx *gorm.DB) error {
return tx.Clauses(clause.OnConflict{ return tx.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "name"}}, Columns: []clause.Column{{Name: "name"}},
DoUpdates: clause.AssignmentColumns([]string{"value"}), DoUpdates: clause.AssignmentColumns([]string{"last_modified", "value"}),
}).Create(counter{Name: counterName, Value: value}).Error }).Create(counter{Name: counterName, Value: value, FirstSeen: atTime.UTC(), LastModified: atTime.UTC()}).Error
}), }),
"storing counter value", "storing counter value",
) )

View file

@ -3,6 +3,7 @@ package counter
import ( import (
"fmt" "fmt"
"testing" "testing"
"time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -20,12 +21,19 @@ func TestCounterStoreLoop(t *testing.T) {
assert.NoError(t, err, "reading non-existent counter") assert.NoError(t, err, "reading non-existent counter")
assert.Equal(t, int64(0), v, "expecting 0 counter value on non-existent counter") assert.Equal(t, int64(0), v, "expecting 0 counter value on non-existent counter")
err = updateCounter(dbc, counterName, 5, true) err = updateCounter(dbc, counterName, 5, true, time.Now())
assert.NoError(t, err, "inserting counter") assert.NoError(t, err, "inserting counter")
err = updateCounter(dbc, counterName, 1, false) var rawCounter counter
assert.NoError(t, dbc.DB().First(&rawCounter, "name = ?", counterName).Error)
assert.Equal(t, rawCounter.FirstSeen, rawCounter.LastModified)
err = updateCounter(dbc, counterName, 1, false, time.Now())
assert.NoError(t, err, "updating counter") assert.NoError(t, err, "updating counter")
assert.NoError(t, dbc.DB().First(&rawCounter, "name = ?", counterName).Error)
assert.NotEqual(t, rawCounter.FirstSeen, rawCounter.LastModified)
v, err = getCounterValue(dbc, counterName) v, err = getCounterValue(dbc, counterName)
assert.NoError(t, err, "reading existent counter") assert.NoError(t, err, "reading existent counter")
assert.Equal(t, int64(6), v, "expecting counter value on existing counter") assert.Equal(t, int64(6), v, "expecting counter value on existing counter")
@ -35,11 +43,13 @@ func TestCounterTopListAndRank(t *testing.T) {
dbc := database.GetTestDatabase(t) dbc := database.GetTestDatabase(t)
require.NoError(t, dbc.DB().AutoMigrate(&counter{})) require.NoError(t, dbc.DB().AutoMigrate(&counter{}))
testTime := time.Now().UTC()
counterTemplate := `#example:test:%v` counterTemplate := `#example:test:%v`
for i := 0; i < 6; i++ { for i := 0; i < 6; i++ {
require.NoError( require.NoError(
t, t,
updateCounter(dbc, fmt.Sprintf(counterTemplate, i), int64(i), true), updateCounter(dbc, fmt.Sprintf(counterTemplate, i), int64(i), true, testTime),
"inserting counter %d", i, "inserting counter %d", i,
) )
} }
@ -49,9 +59,9 @@ func TestCounterTopListAndRank(t *testing.T) {
assert.Len(t, cc, 3) assert.Len(t, cc, 3)
assert.Equal(t, []counter{ assert.Equal(t, []counter{
{Name: "#example:test:5", Value: 5}, {Name: "#example:test:5", Value: 5, FirstSeen: testTime, LastModified: testTime},
{Name: "#example:test:4", Value: 4}, {Name: "#example:test:4", Value: 4, FirstSeen: testTime, LastModified: testTime},
{Name: "#example:test:3", Value: 3}, {Name: "#example:test:3", Value: 3, FirstSeen: testTime, LastModified: testTime},
}, cc) }, cc)
rank, count, err := getCounterRank(dbc, rank, count, err := getCounterRank(dbc,