mirror of
https://github.com/mattermost/mattermost.git
synced 2026-11-03 20:56:57 -06:00
[MM-71036] Health Dashboard PR01b: pointer-ize Support Packet diagnostics fields that can fail (#38888)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Mattermost Build <build@mattermost.com>
This commit is contained in:
9 files changed
+242
-132
No files matched your search
@@ -78,7 +78,8 @@ func TestBuildHealthSnapshotOnlyLeaderHasDiagnostics(t *testing.T) {
|
||||
if node.IsLeader {
|
||||
assert.Equal(t, "id-2", node.ClusterInfo.Id)
|
||||
require.NotNil(t, node.Diagnostics)
|
||||
assert.Equal(t, 3, node.Diagnostics.Diagnostics.Cluster.NumberOfNodes)
|
||||
require.NotNil(t, node.Diagnostics.Diagnostics.Cluster.NumberOfNodes)
|
||||
assert.Equal(t, 3, *node.Diagnostics.Diagnostics.Cluster.NumberOfNodes)
|
||||
} else {
|
||||
assert.Nil(t, node.Diagnostics, "follower %q", node.ClusterInfo.Id)
|
||||
}
|
||||
|
||||
@@ -190,8 +190,9 @@ func (ps *PlatformService) GetSupportPacketDiagnostics(rctx request.CTX) (*model
|
||||
totalMemoryBytes, err := getTotalMemory()
|
||||
if err != nil {
|
||||
fail(model.SectionServerHost, errors.Wrap(err, "error while getting total memory"))
|
||||
} else {
|
||||
d.Server.TotalMemoryMB = new(totalMemoryBytes / 1024 / 1024)
|
||||
}
|
||||
d.Server.TotalMemoryMB = totalMemoryBytes / 1024 / 1024
|
||||
containerLimits, err := getContainerLimits()
|
||||
if err != nil {
|
||||
rctx.Logger().Debug("Failed to get container limits for Support Packet", mlog.Err(err))
|
||||
@@ -199,9 +200,11 @@ func (ps *PlatformService) GetSupportPacketDiagnostics(rctx request.CTX) (*model
|
||||
d.Server.ContainerCPULimit = containerLimits.CPULimit
|
||||
d.Server.ContainerMemoryLimitMB = containerLimits.MemoryLimitMB
|
||||
}
|
||||
d.Server.Hostname, err = os.Hostname()
|
||||
serverHostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
fail(model.SectionServerHost, errors.Wrap(err, "error while getting hostname"))
|
||||
} else {
|
||||
d.Server.Hostname = new(serverHostname)
|
||||
}
|
||||
d.Server.ProcessID = os.Getpid()
|
||||
d.Server.StartedAt = ps.startTime.UTC()
|
||||
@@ -219,29 +222,36 @@ func (ps *PlatformService) GetSupportPacketDiagnostics(rctx request.CTX) (*model
|
||||
installationType = unknownDataPoint
|
||||
}
|
||||
d.Server.InstallationType = installationType
|
||||
d.Server.OpenFileDescriptors, err = getOpenFileDescriptors()
|
||||
openFileDescriptors, err := getOpenFileDescriptors()
|
||||
if err != nil {
|
||||
fail(model.SectionServerFDs, errors.Wrap(err, "error while getting open file descriptor count"))
|
||||
} else {
|
||||
d.Server.OpenFileDescriptors = new(openFileDescriptors)
|
||||
}
|
||||
d.Server.MaxFileDescriptors, err = getMaxFileDescriptors()
|
||||
maxFileDescriptors, err := getMaxFileDescriptors()
|
||||
if err != nil {
|
||||
fail(model.SectionServerFDs, errors.Wrap(err, "error while getting max file descriptor limit"))
|
||||
} else {
|
||||
d.Server.MaxFileDescriptors = new(maxFileDescriptors)
|
||||
}
|
||||
|
||||
/* Config */
|
||||
d.Config.Source = ps.DescribeConfig()
|
||||
|
||||
/* DB */
|
||||
d.Database.Type, d.Database.SchemaVersion, err = ps.DatabaseTypeAndSchemaVersion()
|
||||
databaseType, schemaVersion, err := ps.DatabaseTypeAndSchemaVersion()
|
||||
if err != nil {
|
||||
fail(model.SectionDatabaseIdentity, errors.Wrap(err, "error while getting DB type and schema version"))
|
||||
} else {
|
||||
d.Database.Type = new(databaseType)
|
||||
d.Database.SchemaVersion = new(schemaVersion)
|
||||
}
|
||||
|
||||
databaseVersion, err := ps.Store.GetDbVersion(false)
|
||||
if err != nil {
|
||||
fail(model.SectionDatabaseIdentity, errors.Wrap(err, "error while getting DB version"))
|
||||
} else {
|
||||
d.Database.Version = databaseVersion
|
||||
d.Database.Version = new(databaseVersion)
|
||||
}
|
||||
d.Database.MasterConnections = ps.Store.TotalMasterDbConnections()
|
||||
d.Database.ReplicaConnections = ps.Store.TotalReadDbConnections()
|
||||
@@ -269,9 +279,9 @@ func (ps *PlatformService) GetSupportPacketDiagnostics(rctx request.CTX) (*model
|
||||
if diskErr != nil {
|
||||
fail(model.SectionFilestoreDisk, errors.Wrap(diskErr, "error while getting disk space info"))
|
||||
} else {
|
||||
d.FileStore.FilesystemType = di.FilesystemType
|
||||
d.FileStore.TotalMB = di.TotalMB
|
||||
d.FileStore.AvailableMB = di.AvailableMB
|
||||
d.FileStore.FilesystemType = new(di.FilesystemType)
|
||||
d.FileStore.TotalMB = new(di.TotalMB)
|
||||
d.FileStore.AvailableMB = new(di.AvailableMB)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,8 +296,10 @@ func (ps *PlatformService) GetSupportPacketDiagnostics(rctx request.CTX) (*model
|
||||
if e != nil {
|
||||
fail(model.SectionCluster, errors.Wrap(e, "error while getting cluster infos"))
|
||||
} else {
|
||||
d.Cluster.NumberOfNodes = max(len(clusterInfo), 1) // clusterInfo is empty if the node is the only one in the cluster
|
||||
d.Cluster.NumberOfNodes = new(max(len(clusterInfo), 1)) // clusterInfo is empty if the node is the only one in the cluster
|
||||
}
|
||||
} else {
|
||||
d.Cluster.NumberOfNodes = new(1)
|
||||
}
|
||||
|
||||
/* LDAP */
|
||||
@@ -416,18 +428,18 @@ func (ps *PlatformService) applyStoreDiagnostics(rctx request.CTX, diagnostics *
|
||||
return nil
|
||||
}
|
||||
|
||||
diagnostics.Database.MasterConnectionsInUse = storeDiagnostics.MasterConnectionsInUse
|
||||
diagnostics.Database.MasterConnectionsIdle = storeDiagnostics.MasterConnectionsIdle
|
||||
diagnostics.Database.MasterPoolWaitCount = storeDiagnostics.MasterPoolWaitCount
|
||||
diagnostics.Database.MasterPoolWaitDurationMs = storeDiagnostics.MasterPoolWaitDurationMs
|
||||
diagnostics.Database.MasterConnectionsClosedMaxIdle = storeDiagnostics.MasterConnectionsClosedMaxIdle
|
||||
diagnostics.Database.MasterConnectionsClosedMaxLifetime = storeDiagnostics.MasterConnectionsClosedMaxLifetime
|
||||
diagnostics.Database.ReplicaConnectionsInUse = storeDiagnostics.ReplicaConnectionsInUse
|
||||
diagnostics.Database.ReplicaConnectionsIdle = storeDiagnostics.ReplicaConnectionsIdle
|
||||
diagnostics.Database.ReplicaPoolWaitCount = storeDiagnostics.ReplicaPoolWaitCount
|
||||
diagnostics.Database.ReplicaPoolWaitDurationMs = storeDiagnostics.ReplicaPoolWaitDurationMs
|
||||
diagnostics.Database.ReplicaConnectionsClosedMaxIdle = storeDiagnostics.ReplicaConnectionsClosedMaxIdle
|
||||
diagnostics.Database.ReplicaConnectionsClosedMaxLifetime = storeDiagnostics.ReplicaConnectionsClosedMaxLifetime
|
||||
diagnostics.Database.MasterConnectionsInUse = new(storeDiagnostics.MasterConnectionsInUse)
|
||||
diagnostics.Database.MasterConnectionsIdle = new(storeDiagnostics.MasterConnectionsIdle)
|
||||
diagnostics.Database.MasterPoolWaitCount = new(storeDiagnostics.MasterPoolWaitCount)
|
||||
diagnostics.Database.MasterPoolWaitDurationMs = new(storeDiagnostics.MasterPoolWaitDurationMs)
|
||||
diagnostics.Database.MasterConnectionsClosedMaxIdle = new(storeDiagnostics.MasterConnectionsClosedMaxIdle)
|
||||
diagnostics.Database.MasterConnectionsClosedMaxLifetime = new(storeDiagnostics.MasterConnectionsClosedMaxLifetime)
|
||||
diagnostics.Database.ReplicaConnectionsInUse = new(storeDiagnostics.ReplicaConnectionsInUse)
|
||||
diagnostics.Database.ReplicaConnectionsIdle = new(storeDiagnostics.ReplicaConnectionsIdle)
|
||||
diagnostics.Database.ReplicaPoolWaitCount = new(storeDiagnostics.ReplicaPoolWaitCount)
|
||||
diagnostics.Database.ReplicaPoolWaitDurationMs = new(storeDiagnostics.ReplicaPoolWaitDurationMs)
|
||||
diagnostics.Database.ReplicaConnectionsClosedMaxIdle = new(storeDiagnostics.ReplicaConnectionsClosedMaxIdle)
|
||||
diagnostics.Database.ReplicaConnectionsClosedMaxLifetime = new(storeDiagnostics.ReplicaConnectionsClosedMaxLifetime)
|
||||
diagnostics.Database.CacheHitRatio = storeDiagnostics.CacheHitRatio
|
||||
diagnostics.Database.Deadlocks = storeDiagnostics.Deadlocks
|
||||
diagnostics.Database.TempFiles = storeDiagnostics.TempFiles
|
||||
|
||||
@@ -285,15 +285,23 @@ func TestGetSupportPacketDiagnostics(t *testing.T) {
|
||||
/* Server information */
|
||||
assert.NotEmpty(t, d.Server.OS)
|
||||
assert.NotEmpty(t, d.Server.Architecture)
|
||||
assert.NotEmpty(t, d.Server.Hostname)
|
||||
require.NotNil(t, d.Server.Hostname)
|
||||
assert.NotEmpty(t, *d.Server.Hostname)
|
||||
assert.Equal(t, model.CurrentVersion, d.Server.Version)
|
||||
// BuildHash is not present in tests
|
||||
assert.NotEmpty(t, d.Server.GoVersion)
|
||||
assert.Equal(t, "docker", d.Server.InstallationType)
|
||||
assert.Positive(t, d.Server.CPUCores)
|
||||
assert.Positive(t, d.Server.TotalMemoryMB)
|
||||
assert.True(t, d.Server.OpenFileDescriptors == -1 || d.Server.OpenFileDescriptors > 0, "OpenFileDescriptors should be -1 (unsupported) or positive, got %d", d.Server.OpenFileDescriptors)
|
||||
assert.True(t, d.Server.MaxFileDescriptors == -1 || d.Server.MaxFileDescriptors > 0, "MaxFileDescriptors should be -1 (unsupported) or positive, got %d", d.Server.MaxFileDescriptors)
|
||||
if runtime.GOOS == "linux" || runtime.GOOS == "darwin" {
|
||||
require.NotNil(t, d.Server.TotalMemoryMB)
|
||||
assert.Positive(t, *d.Server.TotalMemoryMB)
|
||||
} else {
|
||||
assert.Nil(t, d.Server.TotalMemoryMB)
|
||||
}
|
||||
require.NotNil(t, d.Server.OpenFileDescriptors)
|
||||
require.NotNil(t, d.Server.MaxFileDescriptors)
|
||||
assert.True(t, *d.Server.OpenFileDescriptors == -1 || *d.Server.OpenFileDescriptors > 0, "OpenFileDescriptors should be -1 (unsupported) or positive, got %d", *d.Server.OpenFileDescriptors)
|
||||
assert.True(t, *d.Server.MaxFileDescriptors == -1 || *d.Server.MaxFileDescriptors > 0, "MaxFileDescriptors should be -1 (unsupported) or positive, got %d", *d.Server.MaxFileDescriptors)
|
||||
assert.Positive(t, d.Server.ProcessID)
|
||||
assert.False(t, d.Server.StartedAt.IsZero())
|
||||
if runtime.GOOS == "linux" || runtime.GOOS == "darwin" {
|
||||
@@ -307,37 +315,50 @@ func TestGetSupportPacketDiagnostics(t *testing.T) {
|
||||
assert.Equal(t, "memory://", d.Config.Source)
|
||||
|
||||
/* DB */
|
||||
assert.NotEmpty(t, d.Database.Type)
|
||||
assert.NotEmpty(t, d.Database.Version)
|
||||
assert.NotEmpty(t, d.Database.SchemaVersion)
|
||||
require.NotNil(t, d.Database.Type)
|
||||
assert.NotEmpty(t, *d.Database.Type)
|
||||
require.NotNil(t, d.Database.Version)
|
||||
assert.NotEmpty(t, *d.Database.Version)
|
||||
require.NotNil(t, d.Database.SchemaVersion)
|
||||
assert.NotEmpty(t, *d.Database.SchemaVersion)
|
||||
assert.NotZero(t, d.Database.MasterConnections)
|
||||
assert.Zero(t, d.Database.ReplicaConnections)
|
||||
assert.Zero(t, d.Database.SearchConnections)
|
||||
assert.GreaterOrEqual(t, d.Database.MasterConnectionsInUse, 0)
|
||||
assert.GreaterOrEqual(t, d.Database.MasterConnectionsIdle, 0)
|
||||
assert.GreaterOrEqual(t, d.Database.MasterPoolWaitCount, int64(0))
|
||||
assert.GreaterOrEqual(t, d.Database.MasterPoolWaitDurationMs, int64(0))
|
||||
assert.GreaterOrEqual(t, d.Database.MasterConnectionsClosedMaxIdle, int64(0))
|
||||
assert.GreaterOrEqual(t, d.Database.MasterConnectionsClosedMaxLifetime, int64(0))
|
||||
assert.GreaterOrEqual(t, d.Database.ReplicaConnectionsInUse, 0)
|
||||
assert.GreaterOrEqual(t, d.Database.ReplicaConnectionsIdle, 0)
|
||||
assert.GreaterOrEqual(t, d.Database.ReplicaPoolWaitCount, int64(0))
|
||||
assert.GreaterOrEqual(t, d.Database.ReplicaPoolWaitDurationMs, int64(0))
|
||||
assert.GreaterOrEqual(t, d.Database.ReplicaConnectionsClosedMaxIdle, int64(0))
|
||||
assert.GreaterOrEqual(t, d.Database.ReplicaConnectionsClosedMaxLifetime, int64(0))
|
||||
require.NotNil(t, d.Database.MasterConnectionsInUse)
|
||||
assert.GreaterOrEqual(t, *d.Database.MasterConnectionsInUse, 0)
|
||||
for _, v := range []*int{d.Database.MasterConnectionsIdle, d.Database.ReplicaConnectionsInUse, d.Database.ReplicaConnectionsIdle} {
|
||||
require.NotNil(t, v)
|
||||
assert.GreaterOrEqual(t, *v, 0)
|
||||
}
|
||||
for _, v := range []*int64{
|
||||
d.Database.MasterPoolWaitCount,
|
||||
d.Database.MasterPoolWaitDurationMs,
|
||||
d.Database.MasterConnectionsClosedMaxIdle,
|
||||
d.Database.MasterConnectionsClosedMaxLifetime,
|
||||
d.Database.ReplicaPoolWaitCount,
|
||||
d.Database.ReplicaPoolWaitDurationMs,
|
||||
d.Database.ReplicaConnectionsClosedMaxIdle,
|
||||
d.Database.ReplicaConnectionsClosedMaxLifetime,
|
||||
} {
|
||||
require.NotNil(t, v)
|
||||
assert.GreaterOrEqual(t, *v, int64(0))
|
||||
}
|
||||
|
||||
/* File store */
|
||||
assert.Equal(t, "OK", d.FileStore.Status)
|
||||
assert.Empty(t, d.FileStore.Error)
|
||||
assert.Equal(t, "local", d.FileStore.Driver)
|
||||
if runtime.GOOS == "linux" || runtime.GOOS == "darwin" {
|
||||
assert.NotEmpty(t, d.FileStore.FilesystemType, "FilesystemType should not be empty on supported platforms")
|
||||
assert.Positive(t, d.FileStore.TotalMB, "TotalMB should be positive on supported platforms")
|
||||
assert.Positive(t, d.FileStore.AvailableMB, "AvailableMB should be positive on supported platforms")
|
||||
require.NotNil(t, d.FileStore.FilesystemType)
|
||||
require.NotNil(t, d.FileStore.TotalMB)
|
||||
require.NotNil(t, d.FileStore.AvailableMB)
|
||||
assert.NotEmpty(t, *d.FileStore.FilesystemType, "FilesystemType should not be empty on supported platforms")
|
||||
assert.Positive(t, *d.FileStore.TotalMB, "TotalMB should be positive on supported platforms")
|
||||
assert.Positive(t, *d.FileStore.AvailableMB, "AvailableMB should be positive on supported platforms")
|
||||
} else {
|
||||
assert.Empty(t, d.FileStore.FilesystemType)
|
||||
assert.Zero(t, d.FileStore.TotalMB)
|
||||
assert.Zero(t, d.FileStore.AvailableMB)
|
||||
assert.Nil(t, d.FileStore.FilesystemType)
|
||||
assert.Nil(t, d.FileStore.TotalMB)
|
||||
assert.Nil(t, d.FileStore.AvailableMB)
|
||||
}
|
||||
|
||||
/* Websockets */
|
||||
@@ -345,7 +366,8 @@ func TestGetSupportPacketDiagnostics(t *testing.T) {
|
||||
|
||||
/* Cluster */
|
||||
assert.Empty(t, d.Cluster.ID)
|
||||
assert.Zero(t, d.Cluster.NumberOfNodes)
|
||||
require.NotNil(t, d.Cluster.NumberOfNodes)
|
||||
assert.Equal(t, 1, *d.Cluster.NumberOfNodes)
|
||||
assert.False(t, d.Cluster.IsLeader)
|
||||
|
||||
/* LDAP */
|
||||
@@ -439,9 +461,9 @@ func TestGetSupportPacketDiagnostics(t *testing.T) {
|
||||
|
||||
assert.Equal(t, "OK", packet.FileStore.Status)
|
||||
assert.Equal(t, "amazons3", packet.FileStore.Driver)
|
||||
assert.Empty(t, packet.FileStore.FilesystemType)
|
||||
assert.Zero(t, packet.FileStore.TotalMB)
|
||||
assert.Zero(t, packet.FileStore.AvailableMB)
|
||||
assert.Nil(t, packet.FileStore.FilesystemType)
|
||||
assert.Nil(t, packet.FileStore.TotalMB)
|
||||
assert.Nil(t, packet.FileStore.AvailableMB)
|
||||
})
|
||||
|
||||
t.Run("no LDAP info if LDAP sync is disabled", func(t *testing.T) {
|
||||
@@ -984,18 +1006,19 @@ func TestGetSupportPacketDiagnostics(t *testing.T) {
|
||||
})
|
||||
|
||||
packet := getDiagnostics(t)
|
||||
assert.Equal(t, 3, packet.Database.MasterConnectionsInUse)
|
||||
assert.Equal(t, 7, packet.Database.MasterConnectionsIdle)
|
||||
assert.Equal(t, int64(11), packet.Database.MasterPoolWaitCount)
|
||||
assert.Equal(t, int64(2025), packet.Database.MasterPoolWaitDurationMs)
|
||||
assert.Equal(t, int64(13), packet.Database.MasterConnectionsClosedMaxIdle)
|
||||
assert.Equal(t, int64(17), packet.Database.MasterConnectionsClosedMaxLifetime)
|
||||
assert.Equal(t, 5, packet.Database.ReplicaConnectionsInUse)
|
||||
assert.Equal(t, 9, packet.Database.ReplicaConnectionsIdle)
|
||||
assert.Equal(t, int64(19), packet.Database.ReplicaPoolWaitCount)
|
||||
assert.Equal(t, int64(4090), packet.Database.ReplicaPoolWaitDurationMs)
|
||||
assert.Equal(t, int64(23), packet.Database.ReplicaConnectionsClosedMaxIdle)
|
||||
assert.Equal(t, int64(29), packet.Database.ReplicaConnectionsClosedMaxLifetime)
|
||||
require.NotNil(t, packet.Database.MasterConnectionsInUse)
|
||||
assert.Equal(t, 3, *packet.Database.MasterConnectionsInUse)
|
||||
assert.Equal(t, new(7), packet.Database.MasterConnectionsIdle)
|
||||
assert.Equal(t, new(int64(11)), packet.Database.MasterPoolWaitCount)
|
||||
assert.Equal(t, new(int64(2025)), packet.Database.MasterPoolWaitDurationMs)
|
||||
assert.Equal(t, new(int64(13)), packet.Database.MasterConnectionsClosedMaxIdle)
|
||||
assert.Equal(t, new(int64(17)), packet.Database.MasterConnectionsClosedMaxLifetime)
|
||||
assert.Equal(t, new(5), packet.Database.ReplicaConnectionsInUse)
|
||||
assert.Equal(t, new(9), packet.Database.ReplicaConnectionsIdle)
|
||||
assert.Equal(t, new(int64(19)), packet.Database.ReplicaPoolWaitCount)
|
||||
assert.Equal(t, new(int64(4090)), packet.Database.ReplicaPoolWaitDurationMs)
|
||||
assert.Equal(t, new(int64(23)), packet.Database.ReplicaConnectionsClosedMaxIdle)
|
||||
assert.Equal(t, new(int64(29)), packet.Database.ReplicaConnectionsClosedMaxLifetime)
|
||||
})
|
||||
|
||||
t.Run("OpenID disabled", func(t *testing.T) {
|
||||
@@ -1176,7 +1199,7 @@ func TestGetSupportPacketDiagnosticsSectionErrors(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
requireSectionErrors := func(t *testing.T, failed map[model.NodeSection][]string) {
|
||||
requireSectionErrors := func(t *testing.T, failed map[model.NodeSection][]string) *model.SupportPacketDiagnostics {
|
||||
t.Helper()
|
||||
|
||||
nodeDiagnostics, err := th.Service.GetSupportPacketDiagnostics(th.Context)
|
||||
@@ -1204,6 +1227,7 @@ func TestGetSupportPacketDiagnosticsSectionErrors(t *testing.T) {
|
||||
assert.ErrorContains(t, err, msg)
|
||||
}
|
||||
}
|
||||
return nodeDiagnostics.Diagnostics
|
||||
}
|
||||
|
||||
t.Run("all sections present and clean", func(t *testing.T) {
|
||||
@@ -1213,17 +1237,20 @@ func TestGetSupportPacketDiagnosticsSectionErrors(t *testing.T) {
|
||||
t.Run("DB schema version fails", func(t *testing.T) {
|
||||
setStore(t, &failingDiagnosticsStore{schemaVersionErr: errors.New("schema down")})
|
||||
|
||||
requireSectionErrors(t, map[model.NodeSection][]string{
|
||||
d := requireSectionErrors(t, map[model.NodeSection][]string{
|
||||
model.SectionDatabaseIdentity: {"error while getting DB type and schema version"},
|
||||
})
|
||||
assert.Nil(t, d.Database.Type)
|
||||
assert.Nil(t, d.Database.SchemaVersion)
|
||||
})
|
||||
|
||||
t.Run("DB version fails", func(t *testing.T) {
|
||||
setStore(t, &failingDiagnosticsStore{dbVersionErr: errors.New("version down")})
|
||||
|
||||
requireSectionErrors(t, map[model.NodeSection][]string{
|
||||
d := requireSectionErrors(t, map[model.NodeSection][]string{
|
||||
model.SectionDatabaseIdentity: {"error while getting DB version"},
|
||||
})
|
||||
assert.Nil(t, d.Database.Version)
|
||||
})
|
||||
|
||||
t.Run("both DB identity sites fail", func(t *testing.T) {
|
||||
@@ -1240,9 +1267,21 @@ func TestGetSupportPacketDiagnosticsSectionErrors(t *testing.T) {
|
||||
t.Run("store diagnostics fail", func(t *testing.T) {
|
||||
setStore(t, &failingDiagnosticsStore{diagnosticsErr: errors.New("stats down")})
|
||||
|
||||
requireSectionErrors(t, map[model.NodeSection][]string{
|
||||
d := requireSectionErrors(t, map[model.NodeSection][]string{
|
||||
model.SectionDatabaseStats: {"error while collecting support packet database diagnostics"},
|
||||
})
|
||||
assert.Nil(t, d.Database.MasterConnectionsInUse)
|
||||
assert.Nil(t, d.Database.MasterConnectionsIdle)
|
||||
assert.Nil(t, d.Database.MasterPoolWaitCount)
|
||||
assert.Nil(t, d.Database.MasterPoolWaitDurationMs)
|
||||
assert.Nil(t, d.Database.MasterConnectionsClosedMaxIdle)
|
||||
assert.Nil(t, d.Database.MasterConnectionsClosedMaxLifetime)
|
||||
assert.Nil(t, d.Database.ReplicaConnectionsInUse)
|
||||
assert.Nil(t, d.Database.ReplicaConnectionsIdle)
|
||||
assert.Nil(t, d.Database.ReplicaPoolWaitCount)
|
||||
assert.Nil(t, d.Database.ReplicaPoolWaitDurationMs)
|
||||
assert.Nil(t, d.Database.ReplicaConnectionsClosedMaxIdle)
|
||||
assert.Nil(t, d.Database.ReplicaConnectionsClosedMaxLifetime)
|
||||
})
|
||||
|
||||
t.Run("disk space fails", func(t *testing.T) {
|
||||
@@ -1265,9 +1304,12 @@ func TestGetSupportPacketDiagnosticsSectionErrors(t *testing.T) {
|
||||
cfg.FileSettings.Directory = model.NewPointer(filepath.Join(t.TempDir(), "missing"))
|
||||
})
|
||||
|
||||
requireSectionErrors(t, map[model.NodeSection][]string{
|
||||
d := requireSectionErrors(t, map[model.NodeSection][]string{
|
||||
model.SectionFilestoreDisk: {"error while getting disk space info"},
|
||||
})
|
||||
assert.Nil(t, d.FileStore.FilesystemType)
|
||||
assert.Nil(t, d.FileStore.TotalMB)
|
||||
assert.Nil(t, d.FileStore.AvailableMB)
|
||||
})
|
||||
|
||||
t.Run("cluster infos fail", func(t *testing.T) {
|
||||
@@ -1283,9 +1325,10 @@ func TestGetSupportPacketDiagnosticsSectionErrors(t *testing.T) {
|
||||
})
|
||||
th.Service.clusterIFace = cluster
|
||||
|
||||
requireSectionErrors(t, map[model.NodeSection][]string{
|
||||
d := requireSectionErrors(t, map[model.NodeSection][]string{
|
||||
model.SectionCluster: {"error while getting cluster infos"},
|
||||
})
|
||||
assert.Nil(t, d.Cluster.NumberOfNodes)
|
||||
})
|
||||
|
||||
t.Run("LDAP vendor info fails", func(t *testing.T) {
|
||||
@@ -1523,39 +1566,39 @@ func TestSupportPacketMarshalGolden(t *testing.T) {
|
||||
diagnostics.License.IsNonProduction = true
|
||||
diagnostics.Server.OS = "linux"
|
||||
diagnostics.Server.Architecture = "amd64"
|
||||
diagnostics.Server.Hostname = "mm-host"
|
||||
diagnostics.Server.Hostname = new("mm-host")
|
||||
diagnostics.Server.InstallationType = "docker"
|
||||
diagnostics.Server.CPUCores = 8
|
||||
diagnostics.Server.TotalMemoryMB = 32768
|
||||
diagnostics.Server.TotalMemoryMB = new(uint64(32768))
|
||||
diagnostics.Server.ContainerCPULimit = 6.5
|
||||
diagnostics.Server.ContainerMemoryLimitMB = 16384
|
||||
diagnostics.Server.ProcessID = 90210
|
||||
diagnostics.Server.StartedAt = time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC)
|
||||
diagnostics.Server.HostStartedAt = time.Date(2025, 12, 30, 0, 0, 0, 0, time.UTC)
|
||||
diagnostics.Server.OpenFileDescriptors = 512
|
||||
diagnostics.Server.MaxFileDescriptors = 8192
|
||||
diagnostics.Server.OpenFileDescriptors = new(int64(512))
|
||||
diagnostics.Server.MaxFileDescriptors = new(int64(8192))
|
||||
diagnostics.Server.Version = "11.0.0"
|
||||
diagnostics.Server.BuildHash = "abc123"
|
||||
diagnostics.Server.GoVersion = "go1.26"
|
||||
diagnostics.Config.Source = "memory://"
|
||||
diagnostics.Database.Type = "postgres"
|
||||
diagnostics.Database.Version = "16.4"
|
||||
diagnostics.Database.SchemaVersion = "123"
|
||||
diagnostics.Database.Type = new("postgres")
|
||||
diagnostics.Database.Version = new("16.4")
|
||||
diagnostics.Database.SchemaVersion = new("123")
|
||||
diagnostics.Database.MasterConnections = 40
|
||||
diagnostics.Database.ReplicaConnections = 20
|
||||
diagnostics.Database.SearchConnections = 10
|
||||
diagnostics.Database.MasterConnectionsInUse = 5
|
||||
diagnostics.Database.MasterConnectionsIdle = 35
|
||||
diagnostics.Database.MasterPoolWaitCount = 100
|
||||
diagnostics.Database.MasterPoolWaitDurationMs = 220
|
||||
diagnostics.Database.MasterConnectionsClosedMaxIdle = 2
|
||||
diagnostics.Database.MasterConnectionsClosedMaxLifetime = 1
|
||||
diagnostics.Database.ReplicaConnectionsInUse = 3
|
||||
diagnostics.Database.ReplicaConnectionsIdle = 17
|
||||
diagnostics.Database.ReplicaPoolWaitCount = 12
|
||||
diagnostics.Database.ReplicaPoolWaitDurationMs = 66
|
||||
diagnostics.Database.ReplicaConnectionsClosedMaxIdle = 4
|
||||
diagnostics.Database.ReplicaConnectionsClosedMaxLifetime = 3
|
||||
diagnostics.Database.MasterConnectionsInUse = new(5)
|
||||
diagnostics.Database.MasterConnectionsIdle = new(35)
|
||||
diagnostics.Database.MasterPoolWaitCount = new(int64(100))
|
||||
diagnostics.Database.MasterPoolWaitDurationMs = new(int64(220))
|
||||
diagnostics.Database.MasterConnectionsClosedMaxIdle = new(int64(2))
|
||||
diagnostics.Database.MasterConnectionsClosedMaxLifetime = new(int64(1))
|
||||
diagnostics.Database.ReplicaConnectionsInUse = new(3)
|
||||
diagnostics.Database.ReplicaConnectionsIdle = new(17)
|
||||
diagnostics.Database.ReplicaPoolWaitCount = new(int64(12))
|
||||
diagnostics.Database.ReplicaPoolWaitDurationMs = new(int64(66))
|
||||
diagnostics.Database.ReplicaConnectionsClosedMaxIdle = new(int64(4))
|
||||
diagnostics.Database.ReplicaConnectionsClosedMaxLifetime = new(int64(3))
|
||||
diagnostics.Database.CacheHitRatio = &cacheHitRatio
|
||||
diagnostics.Database.Deadlocks = &deadlocks
|
||||
diagnostics.Database.TempFiles = &tempFiles
|
||||
@@ -1568,12 +1611,12 @@ func TestSupportPacketMarshalGolden(t *testing.T) {
|
||||
diagnostics.Database.PostsLastAutovacuum = &postsLastAutovacuum
|
||||
diagnostics.FileStore.Status = model.StatusOk
|
||||
diagnostics.FileStore.Driver = model.ImageDriverLocal
|
||||
diagnostics.FileStore.FilesystemType = "ext4"
|
||||
diagnostics.FileStore.TotalMB = 204800
|
||||
diagnostics.FileStore.AvailableMB = 102400
|
||||
diagnostics.FileStore.FilesystemType = new("ext4")
|
||||
diagnostics.FileStore.TotalMB = new(uint64(204800))
|
||||
diagnostics.FileStore.AvailableMB = new(uint64(102400))
|
||||
diagnostics.Websocket.Connections = 77
|
||||
diagnostics.Cluster.ID = "cluster-id"
|
||||
diagnostics.Cluster.NumberOfNodes = 3
|
||||
diagnostics.Cluster.NumberOfNodes = new(3)
|
||||
diagnostics.Notifications.Email.Status = model.StatusOk
|
||||
diagnostics.Notifications.Push.Status = model.StatusFail
|
||||
diagnostics.Notifications.Push.Error = "proxy timeout"
|
||||
|
||||
@@ -35,8 +35,8 @@ func (n *NodeSnapshot) SchemaVersion() (string, bool) {
|
||||
if n != nil && n.ClusterInfo != nil && n.ClusterInfo.SchemaVersion != "" {
|
||||
return n.ClusterInfo.SchemaVersion, true
|
||||
}
|
||||
if diag, ok := n.Diag(); ok && diag.Database.SchemaVersion != "" && n.sectionOK(model.SectionDatabaseIdentity) {
|
||||
return diag.Database.SchemaVersion, true
|
||||
if diag, ok := n.Diag(); ok && model.SafeDereference(diag.Database.SchemaVersion) != "" && n.sectionOK(model.SectionDatabaseIdentity) {
|
||||
return *diag.Database.SchemaVersion, true
|
||||
}
|
||||
|
||||
return "", false
|
||||
|
||||
@@ -51,7 +51,7 @@ func TestSchemaVersion(t *testing.T) {
|
||||
Diagnostics: &model.SupportPacketDiagnostics{},
|
||||
},
|
||||
}
|
||||
node.Diagnostics.Diagnostics.Database.SchemaVersion = "diag"
|
||||
node.Diagnostics.Diagnostics.Database.SchemaVersion = new("diag")
|
||||
|
||||
version, ok := node.SchemaVersion()
|
||||
require.True(t, ok)
|
||||
@@ -65,7 +65,7 @@ func TestSchemaVersion(t *testing.T) {
|
||||
Errors: model.SectionErrors{model.SectionDatabaseIdentity: nil},
|
||||
},
|
||||
}
|
||||
node.Diagnostics.Diagnostics.Database.SchemaVersion = "diag"
|
||||
node.Diagnostics.Diagnostics.Database.SchemaVersion = new("diag")
|
||||
|
||||
version, ok := node.SchemaVersion()
|
||||
require.True(t, ok)
|
||||
@@ -79,7 +79,32 @@ func TestSchemaVersion(t *testing.T) {
|
||||
Errors: model.SectionErrors{model.SectionDatabaseIdentity: errors.New("collect failed")},
|
||||
},
|
||||
}
|
||||
node.Diagnostics.Diagnostics.Database.SchemaVersion = "diag"
|
||||
node.Diagnostics.Diagnostics.Database.SchemaVersion = new("diag")
|
||||
|
||||
_, ok := node.SchemaVersion()
|
||||
require.False(t, ok)
|
||||
})
|
||||
|
||||
t.Run("nil schema version in diagnostics", func(t *testing.T) {
|
||||
node := &NodeSnapshot{
|
||||
Diagnostics: &model.NodeDiagnostics{
|
||||
Diagnostics: &model.SupportPacketDiagnostics{},
|
||||
Errors: model.SectionErrors{model.SectionDatabaseIdentity: nil},
|
||||
},
|
||||
}
|
||||
|
||||
_, ok := node.SchemaVersion()
|
||||
require.False(t, ok)
|
||||
})
|
||||
|
||||
t.Run("empty schema version from an older packet", func(t *testing.T) {
|
||||
node := &NodeSnapshot{
|
||||
Diagnostics: &model.NodeDiagnostics{
|
||||
Diagnostics: &model.SupportPacketDiagnostics{},
|
||||
Errors: model.SectionErrors{model.SectionDatabaseIdentity: nil},
|
||||
},
|
||||
}
|
||||
node.Diagnostics.Diagnostics.Database.SchemaVersion = new("")
|
||||
|
||||
_, ok := node.SchemaVersion()
|
||||
require.False(t, ok)
|
||||
|
||||
@@ -179,7 +179,7 @@ func readNodes(z *budgetedZip, root []string, byNode map[string][]string) ([]*he
|
||||
if len(byNode) == 0 && slices.Contains(root, model.SupportPacketDiagnosticsFileName) {
|
||||
node := read("", model.SupportPacketDiagnosticsFileName)
|
||||
if diag, ok := node.Diag(); ok {
|
||||
node.Hostname = diag.Server.Hostname
|
||||
node.Hostname = model.SafeDereference(diag.Server.Hostname)
|
||||
}
|
||||
nodes = append(nodes, node)
|
||||
}
|
||||
|
||||
@@ -35,8 +35,8 @@ func TestSectionErrorsThreeStates(t *testing.T) {
|
||||
|
||||
func TestSectionErrorsFailClosedWithinSection(t *testing.T) {
|
||||
diagnostics := &SupportPacketDiagnostics{}
|
||||
diagnostics.Server.OpenFileDescriptors = 5000
|
||||
diagnostics.Server.MaxFileDescriptors = 0
|
||||
diagnostics.Server.OpenFileDescriptors = new(int64(5000))
|
||||
diagnostics.Server.MaxFileDescriptors = new(int64(0))
|
||||
|
||||
node := NodeDiagnostics{
|
||||
Diagnostics: diagnostics,
|
||||
@@ -48,8 +48,8 @@ func TestSectionErrorsFailClosedWithinSection(t *testing.T) {
|
||||
_, ok := node.Errors[SectionServerFDs]
|
||||
require.True(t, ok)
|
||||
require.EqualError(t, node.Errors[SectionServerFDs], "max file descriptors read failed")
|
||||
assert.EqualValues(t, 5000, node.Diagnostics.Server.OpenFileDescriptors)
|
||||
assert.EqualValues(t, 0, node.Diagnostics.Server.MaxFileDescriptors)
|
||||
assert.EqualValues(t, 5000, *node.Diagnostics.Server.OpenFileDescriptors)
|
||||
assert.EqualValues(t, 0, *node.Diagnostics.Server.MaxFileDescriptors)
|
||||
}
|
||||
|
||||
func TestNodeAndWorkspaceSectionsDoNotOverlap(t *testing.T) {
|
||||
|
||||
@@ -16,6 +16,7 @@ const (
|
||||
SupportPacketPluginsFileName = "plugins.json"
|
||||
)
|
||||
|
||||
// SupportPacketDiagnostics reports one node's diagnostics. A nil pointer field was not collected, which is not the same as zero.
|
||||
type SupportPacketDiagnostics struct {
|
||||
Version int `yaml:"version"`
|
||||
|
||||
@@ -31,14 +32,14 @@ type SupportPacketDiagnostics struct {
|
||||
|
||||
Server struct {
|
||||
// Machine
|
||||
OS string `yaml:"os"`
|
||||
Architecture string `yaml:"architecture"`
|
||||
Hostname string `yaml:"hostname"`
|
||||
InstallationType string `yaml:"installation_type"`
|
||||
OS string `yaml:"os"`
|
||||
Architecture string `yaml:"architecture"`
|
||||
Hostname *string `yaml:"hostname,omitempty"`
|
||||
InstallationType string `yaml:"installation_type"`
|
||||
|
||||
// Capacity
|
||||
CPUCores int `yaml:"cpu_cores"`
|
||||
TotalMemoryMB uint64 `yaml:"total_memory_mb"`
|
||||
TotalMemoryMB *uint64 `yaml:"total_memory_mb,omitempty"`
|
||||
ContainerCPULimit float64 `yaml:"container_cpu_limit,omitempty"`
|
||||
ContainerMemoryLimitMB uint64 `yaml:"container_memory_limit_mb,omitempty"`
|
||||
|
||||
@@ -46,8 +47,8 @@ type SupportPacketDiagnostics struct {
|
||||
ProcessID int `yaml:"process_id"`
|
||||
StartedAt time.Time `yaml:"started_at"`
|
||||
HostStartedAt time.Time `yaml:"host_started_at,omitempty"`
|
||||
OpenFileDescriptors int64 `yaml:"open_file_descriptors"`
|
||||
MaxFileDescriptors int64 `yaml:"max_file_descriptors"`
|
||||
OpenFileDescriptors *int64 `yaml:"open_file_descriptors,omitempty"`
|
||||
MaxFileDescriptors *int64 `yaml:"max_file_descriptors,omitempty"`
|
||||
|
||||
// Software
|
||||
Version string `yaml:"version"`
|
||||
@@ -60,24 +61,24 @@ type SupportPacketDiagnostics struct {
|
||||
} `yaml:"config"`
|
||||
|
||||
Database struct {
|
||||
Type string `yaml:"type"`
|
||||
Version string `yaml:"version"`
|
||||
SchemaVersion string `yaml:"schema_version"`
|
||||
Type *string `yaml:"type,omitempty"`
|
||||
Version *string `yaml:"version,omitempty"`
|
||||
SchemaVersion *string `yaml:"schema_version,omitempty"`
|
||||
MasterConnections int `yaml:"master_connections"`
|
||||
ReplicaConnections int `yaml:"replica_connections"`
|
||||
SearchConnections int `yaml:"search_connections"`
|
||||
MasterConnectionsInUse int `yaml:"master_connections_in_use"`
|
||||
MasterConnectionsIdle int `yaml:"master_connections_idle"`
|
||||
MasterPoolWaitCount int64 `yaml:"master_pool_wait_count"`
|
||||
MasterPoolWaitDurationMs int64 `yaml:"master_pool_wait_duration_ms"`
|
||||
MasterConnectionsClosedMaxIdle int64 `yaml:"master_connections_closed_max_idle"`
|
||||
MasterConnectionsClosedMaxLifetime int64 `yaml:"master_connections_closed_max_lifetime"`
|
||||
ReplicaConnectionsInUse int `yaml:"replica_connections_in_use"`
|
||||
ReplicaConnectionsIdle int `yaml:"replica_connections_idle"`
|
||||
ReplicaPoolWaitCount int64 `yaml:"replica_pool_wait_count"`
|
||||
ReplicaPoolWaitDurationMs int64 `yaml:"replica_pool_wait_duration_ms"`
|
||||
ReplicaConnectionsClosedMaxIdle int64 `yaml:"replica_connections_closed_max_idle"`
|
||||
ReplicaConnectionsClosedMaxLifetime int64 `yaml:"replica_connections_closed_max_lifetime"`
|
||||
MasterConnectionsInUse *int `yaml:"master_connections_in_use,omitempty"`
|
||||
MasterConnectionsIdle *int `yaml:"master_connections_idle,omitempty"`
|
||||
MasterPoolWaitCount *int64 `yaml:"master_pool_wait_count,omitempty"`
|
||||
MasterPoolWaitDurationMs *int64 `yaml:"master_pool_wait_duration_ms,omitempty"`
|
||||
MasterConnectionsClosedMaxIdle *int64 `yaml:"master_connections_closed_max_idle,omitempty"`
|
||||
MasterConnectionsClosedMaxLifetime *int64 `yaml:"master_connections_closed_max_lifetime,omitempty"`
|
||||
ReplicaConnectionsInUse *int `yaml:"replica_connections_in_use,omitempty"`
|
||||
ReplicaConnectionsIdle *int `yaml:"replica_connections_idle,omitempty"`
|
||||
ReplicaPoolWaitCount *int64 `yaml:"replica_pool_wait_count,omitempty"`
|
||||
ReplicaPoolWaitDurationMs *int64 `yaml:"replica_pool_wait_duration_ms,omitempty"`
|
||||
ReplicaConnectionsClosedMaxIdle *int64 `yaml:"replica_connections_closed_max_idle,omitempty"`
|
||||
ReplicaConnectionsClosedMaxLifetime *int64 `yaml:"replica_connections_closed_max_lifetime,omitempty"`
|
||||
CacheHitRatio *float64 `yaml:"cache_hit_ratio,omitempty"`
|
||||
Deadlocks *int64 `yaml:"deadlocks,omitempty"`
|
||||
TempFiles *int64 `yaml:"temp_files,omitempty"`
|
||||
@@ -91,12 +92,12 @@ type SupportPacketDiagnostics struct {
|
||||
} `yaml:"database"`
|
||||
|
||||
FileStore struct {
|
||||
Status string `yaml:"file_status"`
|
||||
Error string `yaml:"error,omitempty"`
|
||||
Driver string `yaml:"file_driver"`
|
||||
FilesystemType string `yaml:"filesystem_type,omitempty"`
|
||||
TotalMB uint64 `yaml:"total_mb,omitempty"`
|
||||
AvailableMB uint64 `yaml:"available_mb,omitempty"`
|
||||
Status string `yaml:"file_status"`
|
||||
Error string `yaml:"error,omitempty"`
|
||||
Driver string `yaml:"file_driver"`
|
||||
FilesystemType *string `yaml:"filesystem_type,omitempty"`
|
||||
TotalMB *uint64 `yaml:"total_mb,omitempty"`
|
||||
AvailableMB *uint64 `yaml:"available_mb,omitempty"`
|
||||
} `yaml:"file_store"`
|
||||
|
||||
Websocket struct {
|
||||
@@ -105,7 +106,7 @@ type SupportPacketDiagnostics struct {
|
||||
|
||||
Cluster struct {
|
||||
ID string `yaml:"id"`
|
||||
NumberOfNodes int `yaml:"number_of_nodes"`
|
||||
NumberOfNodes *int `yaml:"number_of_nodes,omitempty"`
|
||||
IsLeader bool `yaml:"is_leader,omitempty"`
|
||||
} `yaml:"cluster"`
|
||||
|
||||
|
||||
@@ -70,3 +70,31 @@ outgoing_webhooks: 14
|
||||
|
||||
assert.Equal(t, expected, string(data))
|
||||
}
|
||||
|
||||
func TestSupportPacketDiagnosticsYAMLOmitsNilAndKeepsZero(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var diagnostics SupportPacketDiagnostics
|
||||
diagnostics.Server.OpenFileDescriptors = new(int64(0))
|
||||
diagnostics.Database.MasterConnectionsInUse = new(0)
|
||||
diagnostics.FileStore.AvailableMB = new(uint64(0))
|
||||
|
||||
data, err := yaml.Marshal(&diagnostics)
|
||||
require.NoError(t, err)
|
||||
|
||||
body := string(data)
|
||||
assert.Contains(t, body, "open_file_descriptors: 0\n")
|
||||
assert.Contains(t, body, "master_connections_in_use: 0\n")
|
||||
assert.Contains(t, body, "available_mb: 0\n")
|
||||
assert.NotContains(t, body, "max_file_descriptors")
|
||||
assert.NotContains(t, body, "schema_version")
|
||||
|
||||
var roundTrip SupportPacketDiagnostics
|
||||
err = yaml.Unmarshal(data, &roundTrip)
|
||||
require.NoError(t, err)
|
||||
assert.Nil(t, roundTrip.Server.MaxFileDescriptors)
|
||||
assert.Nil(t, roundTrip.Database.SchemaVersion)
|
||||
assert.Equal(t, new(int64(0)), roundTrip.Server.OpenFileDescriptors)
|
||||
assert.Equal(t, new(0), roundTrip.Database.MasterConnectionsInUse)
|
||||
assert.Equal(t, new(uint64(0)), roundTrip.FileStore.AvailableMB)
|
||||
}
|
||||
Reference in new issue
Block a user