diff --git a/changes/issue-1916-sched-query-stats b/changes/issue-1916-sched-query-stats new file mode 100644 index 0000000000..8a2d010e39 --- /dev/null +++ b/changes/issue-1916-sched-query-stats @@ -0,0 +1 @@ +* Properly record scheduled query statistics after the first recording. diff --git a/server/datastore/mysql/hosts.go b/server/datastore/mysql/hosts.go index 6bb0809800..d33fedb2a3 100644 --- a/server/datastore/mysql/hosts.go +++ b/server/datastore/mysql/hosts.go @@ -209,7 +209,18 @@ func (d *Datastore) saveHostPackStats(host *fleet.Host) error { user_time, wall_time ) - VALUES %s + VALUES %s ON DUPLICATE KEY UPDATE + scheduled_query_id = VALUES(scheduled_query_id), + host_id = VALUES(host_id), + average_memory = VALUES(average_memory), + denylisted = VALUES(denylisted), + executions = VALUES(executions), + schedule_interval = VALUES(schedule_interval), + last_executed = VALUES(last_executed), + output_size = VALUES(output_size), + system_time = VALUES(system_time), + user_time = VALUES(user_time), + wall_time = VALUES(wall_time) `, values) if _, err := tx.Exec(sql, args...); err != nil { return errors.Wrap(err, "insert pack stats") diff --git a/server/datastore/mysql/hosts_test.go b/server/datastore/mysql/hosts_test.go index b280ef3666..bff1ecf66d 100644 --- a/server/datastore/mysql/hosts_test.go +++ b/server/datastore/mysql/hosts_test.go @@ -249,6 +249,152 @@ func TestSaveHostPackStats(t *testing.T) { require.Len(t, host.PackStats, 2) } +func TestSaveHostPackStatsOverwrites(t *testing.T) { + ds := CreateMySQLDS(t) + defer ds.Close() + + host, err := ds.NewHost(&fleet.Host{ + DetailUpdatedAt: time.Now(), + LabelUpdatedAt: time.Now(), + SeenTime: time.Now(), + NodeKey: "1", + UUID: "1", + Hostname: "foo.local", + PrimaryIP: "192.168.1.1", + PrimaryMac: "30-65-EC-6F-C4-58", + }) + require.NoError(t, err) + require.NotNil(t, host) + + // Pack and query must exist for stats to save successfully + pack1 := test.NewPack(t, ds, "test1") + query1 := test.NewQuery(t, ds, "time", "select * from time", 0, true) + squery1 := test.NewScheduledQuery(t, ds, pack1.ID, query1.ID, 30, true, true, "time-scheduled") + pack2 := test.NewPack(t, ds, "test2") + squery2 := test.NewScheduledQuery(t, ds, pack2.ID, query1.ID, 30, true, true, "time-scheduled") + query2 := test.NewQuery(t, ds, "processes", "select * from processes", 0, true) + + execTime1 := time.Unix(1620325191, 0).UTC() + + host.PackStats = []fleet.PackStats{ + { + PackName: "test1", + QueryStats: []fleet.ScheduledQueryStats{ + { + ScheduledQueryName: squery1.Name, + ScheduledQueryID: squery1.ID, + QueryName: query1.Name, + PackName: pack1.Name, + PackID: pack1.ID, + AverageMemory: 8000, + Denylisted: false, + Executions: 164, + Interval: 30, + LastExecuted: execTime1, + OutputSize: 1337, + SystemTime: 150, + UserTime: 180, + WallTime: 0, + }, + }, + }, + { + PackName: "test2", + QueryStats: []fleet.ScheduledQueryStats{ + { + ScheduledQueryName: squery2.Name, + ScheduledQueryID: squery2.ID, + QueryName: query2.Name, + PackName: pack2.Name, + PackID: pack2.ID, + AverageMemory: 431, + Denylisted: true, + Executions: 1, + Interval: 30, + LastExecuted: execTime1, + OutputSize: 134, + SystemTime: 1656, + UserTime: 18453, + WallTime: 10, + }, + }, + }, + } + + require.NoError(t, ds.SaveHost(host)) + + host, err = ds.Host(host.ID) + require.NoError(t, err) + + sort.Slice(host.PackStats, func(i, j int) bool { + return host.PackStats[i].PackName < host.PackStats[j].PackName + }) + + require.Len(t, host.PackStats, 2) + assert.Equal(t, host.PackStats[0].PackName, "test1") + assert.Equal(t, execTime1, host.PackStats[0].QueryStats[0].LastExecuted) + + execTime2 := execTime1.Add(24 * time.Hour) + + host.PackStats = []fleet.PackStats{ + { + PackName: "test1", + QueryStats: []fleet.ScheduledQueryStats{ + { + ScheduledQueryName: squery1.Name, + ScheduledQueryID: squery1.ID, + QueryName: query1.Name, + PackName: pack1.Name, + PackID: pack1.ID, + AverageMemory: 8000, + Denylisted: false, + Executions: 164, + Interval: 30, + LastExecuted: execTime2, + OutputSize: 1337, + SystemTime: 150, + UserTime: 180, + WallTime: 0, + }, + }, + }, + { + PackName: "test2", + QueryStats: []fleet.ScheduledQueryStats{ + { + ScheduledQueryName: squery2.Name, + ScheduledQueryID: squery2.ID, + QueryName: query2.Name, + PackName: pack2.Name, + PackID: pack2.ID, + AverageMemory: 431, + Denylisted: true, + Executions: 1, + Interval: 30, + LastExecuted: execTime1, + OutputSize: 134, + SystemTime: 1656, + UserTime: 18453, + WallTime: 10, + }, + }, + }, + } + + require.NoError(t, ds.SaveHost(host)) + + gotHost, err := ds.Host(host.ID) + require.NoError(t, err) + + sort.Slice(gotHost.PackStats, func(i, j int) bool { + return gotHost.PackStats[i].PackName < gotHost.PackStats[j].PackName + }) + + require.Len(t, gotHost.PackStats, 2) + assert.Equal(t, gotHost.PackStats[0].PackName, "test1") + assert.Equal(t, execTime2, gotHost.PackStats[0].QueryStats[0].LastExecuted) +} + func TestIgnoresTeamPackStats(t *testing.T) { ds := CreateMySQLDS(t) defer ds.Close() diff --git a/server/datastore/mysql/software_test.go b/server/datastore/mysql/software_test.go index 1eaf732fbf..810ecd7544 100644 --- a/server/datastore/mysql/software_test.go +++ b/server/datastore/mysql/software_test.go @@ -3,6 +3,7 @@ package mysql import ( "fmt" "math/rand" + "sort" "strings" "testing" "time" @@ -310,6 +311,7 @@ func TestLoadSupportsTonsOfCVEs(t *testing.T) { require.NoError(t, ds.SaveHostSoftware(host)) require.NoError(t, ds.LoadHostSoftware(host)) + sort.Slice(host.Software, func(i, j int) bool { return host.Software[i].Name < host.Software[j].Name }) require.NoError(t, ds.AddCPEForSoftware(host.Software[0], "somecpe")) require.NoError(t, ds.AddCPEForSoftware(host.Software[1], "someothercpewithoutvulns")) for i := 0; i < 1000; i++ { @@ -324,7 +326,7 @@ func TestLoadSupportsTonsOfCVEs(t *testing.T) { for _, software := range host.Software { switch software.Name { - case "foo": + case "bar": assert.Equal(t, "somecpe", software.GenerateCPE) require.Len(t, software.Vulnerabilities, 1000) assert.True(t, strings.HasPrefix(software.Vulnerabilities[0].CVE, "cve-")) @@ -332,10 +334,10 @@ func TestLoadSupportsTonsOfCVEs(t *testing.T) { "https://nvd.nist.gov/vuln/detail/"+software.Vulnerabilities[0].CVE, software.Vulnerabilities[0].DetailsLink, ) - case "bar": + case "blah": assert.Len(t, software.Vulnerabilities, 0) assert.Equal(t, "someothercpewithoutvulns", software.GenerateCPE) - case "blah": + case "foo": assert.Len(t, software.Vulnerabilities, 0) } }