From 85f0638f4f21144ed6e73b617a01aa7c632cc064 Mon Sep 17 00:00:00 2001 From: Sharon Katz <121527325+sharon-fdm@users.noreply.github.com> Date: Tue, 15 Apr 2025 11:30:07 -0400 Subject: [PATCH] Add statistic to measure ABM pending hosts (#28226) Co-authored-by: Lucas Manuel Rodriguez --- changes/C10298-additional-stats | 1 + cmd/fleet/serve_test.go | 3 ++- server/datastore/mysql/hosts.go | 12 ++++++++++++ server/datastore/mysql/statistics.go | 7 +++++++ server/datastore/mysql/statistics_test.go | 4 ++++ server/fleet/statistics.go | 1 + 6 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 changes/C10298-additional-stats diff --git a/changes/C10298-additional-stats b/changes/C10298-additional-stats new file mode 100644 index 0000000000..09fa4a620c --- /dev/null +++ b/changes/C10298-additional-stats @@ -0,0 +1 @@ +- Added an additional statistic item to count ABM pending hosts. diff --git a/cmd/fleet/serve_test.go b/cmd/fleet/serve_test.go index 2d32c659ab..446e8bd6be 100644 --- a/cmd/fleet/serve_test.go +++ b/cmd/fleet/serve_test.go @@ -91,6 +91,7 @@ func TestMaybeSendStatistics(t *testing.T) { FleetVersion: "1.2.3", LicenseTier: "premium", NumHostsEnrolled: 999, + NumHostsABMPending: 888, NumUsers: 99, NumSoftwareVersions: 100, NumHostSoftwares: 101, @@ -140,7 +141,7 @@ func TestMaybeSendStatistics(t *testing.T) { require.NoError(t, err) assert.True(t, recorded) require.True(t, cleanedup) - assert.Equal(t, `{"anonymousIdentifier":"ident","fleetVersion":"1.2.3","licenseTier":"premium","organization":"Fleet","numHostsEnrolled":999,"numUsers":99,"numSoftwareVersions":100,"numHostSoftwares":101,"numSoftwareTitles":102,"numHostSoftwareInstalledPaths":103,"numSoftwareCPEs":104,"numSoftwareCVEs":105,"numTeams":9,"numPolicies":0,"numQueries":200,"numLabels":3,"softwareInventoryEnabled":true,"vulnDetectionEnabled":true,"systemUsersEnabled":true,"hostsStatusWebHookEnabled":true,"mdmMacOsEnabled":false,"hostExpiryEnabled":false,"mdmWindowsEnabled":false,"liveQueryDisabled":false,"numWeeklyActiveUsers":111,"numWeeklyPolicyViolationDaysActual":0,"numWeeklyPolicyViolationDaysPossible":0,"hostsEnrolledByOperatingSystem":{"linux":[{"version":"1.2.3","numEnrolled":22}]},"hostsEnrolledByOrbitVersion":[],"hostsEnrolledByOsqueryVersion":[],"storedErrors":[],"numHostsNotResponding":0,"aiFeaturesDisabled":true,"maintenanceWindowsEnabled":true,"maintenanceWindowsConfigured":true,"numHostsFleetDesktopEnabled":1984}`, requestBody) + assert.Equal(t, `{"anonymousIdentifier":"ident","fleetVersion":"1.2.3","licenseTier":"premium","organization":"Fleet","numHostsEnrolled":999,"numHostsABMPending":888,"numUsers":99,"numSoftwareVersions":100,"numHostSoftwares":101,"numSoftwareTitles":102,"numHostSoftwareInstalledPaths":103,"numSoftwareCPEs":104,"numSoftwareCVEs":105,"numTeams":9,"numPolicies":0,"numQueries":200,"numLabels":3,"softwareInventoryEnabled":true,"vulnDetectionEnabled":true,"systemUsersEnabled":true,"hostsStatusWebHookEnabled":true,"mdmMacOsEnabled":false,"hostExpiryEnabled":false,"mdmWindowsEnabled":false,"liveQueryDisabled":false,"numWeeklyActiveUsers":111,"numWeeklyPolicyViolationDaysActual":0,"numWeeklyPolicyViolationDaysPossible":0,"hostsEnrolledByOperatingSystem":{"linux":[{"version":"1.2.3","numEnrolled":22}]},"hostsEnrolledByOrbitVersion":[],"hostsEnrolledByOsqueryVersion":[],"storedErrors":[],"numHostsNotResponding":0,"aiFeaturesDisabled":true,"maintenanceWindowsEnabled":true,"maintenanceWindowsConfigured":true,"numHostsFleetDesktopEnabled":1984}`, requestBody) } func TestMaybeSendStatisticsSkipsSendingIfNotNeeded(t *testing.T) { diff --git a/server/datastore/mysql/hosts.go b/server/datastore/mysql/hosts.go index c2785befa8..72dd5385ec 100644 --- a/server/datastore/mysql/hosts.go +++ b/server/datastore/mysql/hosts.go @@ -5196,6 +5196,18 @@ func numHostsFleetDesktopEnabledDB(ctx context.Context, db sqlx.QueryerContext) return count, nil } +func numHostsABMPendingDB(ctx context.Context, db sqlx.QueryerContext) (int, error) { + var count int + const stmt = ` +SELECT COUNT(*) FROM hosts h LEFT JOIN host_mdm hmdm ON h.id=hmdm.host_id WHERE h.platform IN ('darwin', 'ios', 'ipados') AND hmdm.enrollment_status = 'Pending'; + ` + if err := sqlx.GetContext(ctx, db, &count, stmt); err != nil { + return 0, err + } + + return count, nil +} + func (ds *Datastore) GetMatchingHostSerials(ctx context.Context, serials []string) (map[string]*fleet.Host, error) { result := map[string]*fleet.Host{} if len(serials) == 0 { diff --git a/server/datastore/mysql/statistics.go b/server/datastore/mysql/statistics.go index ff43afd1fb..80be1c2860 100644 --- a/server/datastore/mysql/statistics.go +++ b/server/datastore/mysql/statistics.go @@ -29,6 +29,12 @@ func (ds *Datastore) ShouldSendStatistics(ctx context.Context, frequency time.Du if err != nil { return ctxerr.Wrap(ctx, err, "amount enrolled hosts by os") } + + numHostsABMPending, err := numHostsABMPendingDB(ctx, ds.reader(ctx)) + if err != nil { + return ctxerr.Wrap(ctx, err, "amount hosts that are ABM pending") + } + amountUsers, err := tableRowsCount(ctx, ds.reader(ctx), "users") if err != nil { return ctxerr.Wrap(ctx, err, "amount users") @@ -109,6 +115,7 @@ func (ds *Datastore) ShouldSendStatistics(ctx context.Context, frequency time.Du } stats.NumHostsEnrolled = amountEnrolledHosts + stats.NumHostsABMPending = numHostsABMPending stats.NumUsers = amountUsers stats.NumSoftwareVersions = amountSoftwaresVersions stats.NumHostSoftwares = amountHostSoftwares diff --git a/server/datastore/mysql/statistics_test.go b/server/datastore/mysql/statistics_test.go index ca6b58587b..cec9cc5ab3 100644 --- a/server/datastore/mysql/statistics_test.go +++ b/server/datastore/mysql/statistics_test.go @@ -63,6 +63,7 @@ func testStatisticsShouldSend(t *testing.T, ds *Datastore) { assert.Equal(t, "premium", stats.LicenseTier) assert.Equal(t, "Fleet", stats.Organization) assert.Equal(t, 0, stats.NumHostsEnrolled) + assert.Equal(t, 0, stats.NumHostsABMPending) assert.Equal(t, 0, stats.NumUsers) assert.Equal(t, 0, stats.NumSoftwareVersions) assert.Equal(t, 0, stats.NumHostSoftwares) @@ -212,6 +213,7 @@ func testStatisticsShouldSend(t *testing.T, ds *Datastore) { assert.Equal(t, "premium", stats.LicenseTier) assert.Equal(t, "Fleet", stats.Organization) assert.Equal(t, 1, stats.NumHostsEnrolled) + assert.Equal(t, 0, stats.NumHostsABMPending) assert.Equal(t, 2, stats.NumUsers) assert.Equal(t, 0, stats.NumSoftwareVersions) assert.Equal(t, 0, stats.NumHostSoftwares) @@ -321,6 +323,7 @@ func testStatisticsShouldSend(t *testing.T, ds *Datastore) { assert.Equal(t, "premium", stats.LicenseTier) assert.Equal(t, "Fleet", stats.Organization) assert.Equal(t, 5, stats.NumHostsEnrolled) + assert.Equal(t, 0, stats.NumHostsABMPending) assert.Equal(t, 2, stats.NumUsers) assert.Equal(t, 0, stats.NumQueries) assert.Equal(t, 0, stats.NumSoftwareVersions) @@ -370,6 +373,7 @@ func testStatisticsShouldSend(t *testing.T, ds *Datastore) { assert.Equal(t, "premium", stats.LicenseTier) assert.Equal(t, "Fleet", stats.Organization) assert.Equal(t, 5, stats.NumHostsEnrolled) + assert.Equal(t, 0, stats.NumHostsABMPending) assert.Equal(t, 2, stats.NumUsers) assert.Equal(t, 0, stats.NumQueries) assert.Equal(t, 0, stats.NumSoftwareVersions) diff --git a/server/fleet/statistics.go b/server/fleet/statistics.go index 18a9403480..1a8d138192 100644 --- a/server/fleet/statistics.go +++ b/server/fleet/statistics.go @@ -11,6 +11,7 @@ type StatisticsPayload struct { LicenseTier string `json:"licenseTier"` Organization string `json:"organization"` NumHostsEnrolled int `json:"numHostsEnrolled"` + NumHostsABMPending int `json:"numHostsABMPending"` NumUsers int `json:"numUsers"` NumSoftwareVersions int `json:"numSoftwareVersions"` NumHostSoftwares int `json:"numHostSoftwares"`