longer record creation

This commit is contained in:
Henry Dollman
2024-07-19 21:18:52 -04:00
parent e6c8af4b0c
commit 3e48e8babf
5 changed files with 205 additions and 39 deletions

2
go.mod
View File

@@ -1,4 +1,4 @@
module monitor-site module beszel
go 1.22.4 go 1.22.4

59
main.go
View File

@@ -1,6 +1,7 @@
package main package main
import ( import (
_ "beszel/migrations"
"bytes" "bytes"
"crypto/ed25519" "crypto/ed25519"
"encoding/json" "encoding/json"
@@ -8,7 +9,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"log" "log"
_ "monitor-site/migrations"
"net/http" "net/http"
"net/http/httputil" "net/http/httputil"
"net/mail" "net/mail"
@@ -82,39 +82,21 @@ func main() {
return nil return nil
}) })
// set up cron job to delete records older than 30 days // set up cron jobs
app.OnBeforeServe().Add(func(e *core.ServeEvent) error { app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
scheduler := cron.New() scheduler := cron.New()
scheduler.MustAdd("delete old records", "* 2 * * *", func() { // delete records that are older than the display period
// log.Println("Deleting old records...") scheduler.MustAdd("delete old records", "0 */2 * * *", func() {
// Get the current time deleteOldRecords("system_stats", "1m", time.Hour)
now := time.Now().UTC() deleteOldRecords("container_stats", "1m", time.Hour)
// Subtract one month deleteOldRecords("system_stats", "10m", 12*time.Hour)
oneMonthAgo := now.AddDate(0, 0, -30) deleteOldRecords("container_stats", "10m", 12*time.Hour)
// Format the time as a string deleteOldRecords("system_stats", "20m", 24*time.Hour)
timeString := oneMonthAgo.Format("2006-01-02 15:04:05") deleteOldRecords("container_stats", "20m", 24*time.Hour)
// collections to be cleaned deleteOldRecords("system_stats", "120m", 7*24*time.Hour)
collections := []string{"system_stats", "container_stats"} deleteOldRecords("container_stats", "120m", 7*24*time.Hour)
deleteOldRecords("system_stats", "480m", 30*24*time.Hour)
for _, collection := range collections { deleteOldRecords("container_stats", "480m", 30*24*time.Hour)
records, err := app.Dao().FindRecordsByFilter(
collection,
fmt.Sprintf("created <= \"%s\"", timeString), // filter
"", // sort
-1, // limit
0, // offset
)
if err != nil {
log.Println(err)
return
}
// delete records
for _, record := range records {
if err := app.Dao().DeleteRecord(record); err != nil {
log.Fatal(err)
}
}
}
}) })
scheduler.Start() scheduler.Start()
return nil return nil
@@ -176,10 +158,9 @@ func main() {
} }
// if server is set to pending (unpause), try to connect immediately // if server is set to pending (unpause), try to connect immediately
// commenting out because we don't want to get off of the one min schedule if newStatus == "pending" {
// if newStatus == "pending" { go updateSystem(newRecord)
// go updateSystem(newRecord) }
// }
// alerts // alerts
handleStatusAlerts(newStatus, oldRecord) handleStatusAlerts(newStatus, oldRecord)
@@ -193,6 +174,12 @@ func main() {
return nil return nil
}) })
app.OnModelAfterCreate("system_stats").Add(func(e *core.ModelEvent) error {
createLongerRecords(e.Model.(*models.Record))
// createLongerRecords(e.Model.(*models.Record).OriginalCopy(), e.Model.(*models.Record))
return nil
})
if err := app.Start(); err != nil { if err := app.Start(); err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@@ -44,13 +44,13 @@ The hub and agent are distributed as single binary files, as well as docker imag
## Environment Variables ## Environment Variables
## Hub ### Hub
| Name | Default | Description | | Name | Default | Description |
| ----------------------- | ------- | -------------------------------- | | ----------------------- | ------- | -------------------------------- |
| `DISABLE_PASSWORD_AUTH` | false | Disables password authentication | | `DISABLE_PASSWORD_AUTH` | false | Disables password authentication |
## Agent ### Agent
| Name | Default | Description | | Name | Default | Description |
| ------------ | ------- | ------------------------------------------------ | | ------------ | ------- | ------------------------------------------------ |

158
records.go Normal file
View File

@@ -0,0 +1,158 @@
package main
import (
"encoding/json"
"fmt"
"log"
"math"
"time"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/models"
"github.com/pocketbase/pocketbase/tools/types"
)
func createLongerRecords(shorterRecord *models.Record) {
shorterRecordType := shorterRecord.Get("type").(string)
systemId := shorterRecord.Get("system").(string)
// fmt.Println("create longer records", "recordType", shorterRecordType, "systemId", systemId)
var longerRecordType string
var timeAgo time.Duration
var expectedShorterRecords int
switch shorterRecordType {
case "1m":
longerRecordType = "10m"
timeAgo = -10 * time.Minute
expectedShorterRecords = 10
case "10m":
longerRecordType = "20m"
timeAgo = -20 * time.Minute
expectedShorterRecords = 2
case "20m":
longerRecordType = "120m"
timeAgo = -120 * time.Minute
expectedShorterRecords = 6
default:
longerRecordType = "480m"
timeAgo = -480 * time.Minute
expectedShorterRecords = 4
}
longerRecordPeriod := time.Now().UTC().Add(timeAgo + 10*time.Second).Format("2006-01-02 15:04:05")
// check creation time of last 10m record
lastLongerRecord, err := app.Dao().FindFirstRecordByFilter(
"system_stats",
"type = {:type} && system = {:system} && created > {:created}",
dbx.Params{"type": longerRecordType, "system": systemId, "created": longerRecordPeriod},
)
// return if longer record exists
if err == nil || lastLongerRecord != nil {
// log.Println("longer record found. returning")
return
}
// get shorter records from the past x minutes
// shorterRecordPeriod := time.Now().UTC().Add(timeAgo + time.Second).Format("2006-01-02 15:04:05")
allShorterRecords, err := app.Dao().FindRecordsByFilter(
"system_stats",
"type = {:type} && system = {:system} && created > {:created}",
"-created",
-1,
0,
dbx.Params{"type": shorterRecordType, "system": systemId, "created": longerRecordPeriod},
)
// return if not enough shorter records
if err != nil || len(allShorterRecords) < expectedShorterRecords {
// log.Println("not enough shorter records. returning")
return
}
// average the shorter records and create 10m record
averagedStats := averageSystemStats(allShorterRecords)
collection, _ := app.Dao().FindCollectionByNameOrId("system_stats")
tenMinRecord := models.NewRecord(collection)
tenMinRecord.Set("system", systemId)
tenMinRecord.Set("stats", averagedStats)
tenMinRecord.Set("type", longerRecordType)
if err := app.Dao().SaveRecord(tenMinRecord); err != nil {
fmt.Println("failed to save longer record", "err", err.Error())
}
}
// func averageSystemStats(records []*models.Record) SystemStats {
// numStats := len(records)
// firstStats := records[0].Get("stats").(SystemStats)
// sum := reflect.New(reflect.TypeOf(firstStats)).Elem()
// for _, record := range records {
// stats := record.Get("stats").(SystemStats)
// statValue := reflect.ValueOf(stats)
// for i := 0; i < statValue.NumField(); i++ {
// field := sum.Field(i)
// field.SetFloat(field.Float() + statValue.Field(i).Float())
// }
// }
// average := reflect.New(reflect.TypeOf(firstStats)).Elem()
// for i := 0; i < sum.NumField(); i++ {
// average.Field(i).SetFloat(twoDecimals(sum.Field(i).Float() / float64(numStats)))
// }
// return average.Interface().(SystemStats)
// }
func averageSystemStats(records []*models.Record) SystemStats {
var sum SystemStats
count := float64(len(records))
for _, record := range records {
var stats SystemStats
json.Unmarshal([]byte(record.Get("stats").(types.JsonRaw)), &stats)
sum.Cpu += stats.Cpu
sum.Mem += stats.Mem
sum.MemUsed += stats.MemUsed
sum.MemPct += stats.MemPct
sum.MemBuffCache += stats.MemBuffCache
sum.Disk += stats.Disk
sum.DiskUsed += stats.DiskUsed
sum.DiskPct += stats.DiskPct
sum.DiskRead += stats.DiskRead
sum.DiskWrite += stats.DiskWrite
sum.NetworkSent += stats.NetworkSent
sum.NetworkRecv += stats.NetworkRecv
}
return SystemStats{
Cpu: twoDecimals(sum.Cpu / count),
Mem: twoDecimals(sum.Mem / count),
MemUsed: twoDecimals(sum.MemUsed / count),
MemPct: twoDecimals(sum.MemPct / count),
MemBuffCache: twoDecimals(sum.MemBuffCache / count),
Disk: twoDecimals(sum.Disk / count),
DiskUsed: twoDecimals(sum.DiskUsed / count),
DiskPct: twoDecimals(sum.DiskPct / count),
DiskRead: twoDecimals(sum.DiskRead / count),
DiskWrite: twoDecimals(sum.DiskWrite / count),
NetworkSent: twoDecimals(sum.NetworkSent / count),
NetworkRecv: twoDecimals(sum.NetworkRecv / count),
}
}
/* Round float to two decimals */
func twoDecimals(value float64) float64 {
return math.Round(value*100) / 100
}
/* Delete records of specified collection and type that are older than timeLimit */
func deleteOldRecords(collection string, recordType string, timeLimit time.Duration) {
log.Println("Deleting old", recordType, "records...")
timeLimitStamp := time.Now().UTC().Add(timeLimit).Format("2006-01-02 15:04:05")
records, _ := app.Dao().FindRecordsByExpr(collection,
dbx.NewExp("type = {:type}", dbx.Params{"type": recordType}),
dbx.NewExp("created < {:created}", dbx.Params{"created": timeLimitStamp}),
)
for _, record := range records {
if err := app.Dao().DeleteRecord(record); err != nil {
log.Fatal(err)
}
}
}

View File

@@ -5,6 +5,8 @@ import {
DatabaseBackupIcon, DatabaseBackupIcon,
Github, Github,
LayoutDashboard, LayoutDashboard,
LockKeyholeIcon,
LogsIcon,
MailIcon, MailIcon,
Server, Server,
} from 'lucide-react' } from 'lucide-react'
@@ -97,6 +99,15 @@ export default function CommandPalette() {
<span>PocketBase</span> <span>PocketBase</span>
<CommandShortcut>Admin</CommandShortcut> <CommandShortcut>Admin</CommandShortcut>
</CommandItem> </CommandItem>
<CommandItem
onSelect={() => {
window.location.href = '/_/#/logs'
}}
>
<LogsIcon className="mr-2 h-4 w-4" />
<span>Logs</span>
<CommandShortcut>Admin</CommandShortcut>
</CommandItem>
<CommandItem <CommandItem
onSelect={() => { onSelect={() => {
window.location.href = '/_/#/settings/backups' window.location.href = '/_/#/settings/backups'
@@ -106,6 +117,16 @@ export default function CommandPalette() {
<span>Database backups</span> <span>Database backups</span>
<CommandShortcut>Admin</CommandShortcut> <CommandShortcut>Admin</CommandShortcut>
</CommandItem> </CommandItem>
<CommandItem
keywords={['oauth', 'oicd']}
onSelect={() => {
window.location.href = '/_/#/settings/auth-providers'
}}
>
<LockKeyholeIcon className="mr-2 h-4 w-4" />
<span>Auth Providers</span>
<CommandShortcut>Admin</CommandShortcut>
</CommandItem>
<CommandItem <CommandItem
keywords={['email']} keywords={['email']}
onSelect={() => { onSelect={() => {