mattermost/server/public/model/support_packet.go
Ben Schumacher 7739b349a0
[MM-68578] Add support packet DB performance diagnostics (#36324)
* Add support packet DB diagnostics for pool and pg_stat

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

* Fix support packet mock store for new DB diagnostics

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

* Add context timeouts to support packet pg diagnostics

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

* Move support packet DB diagnostics queries into sqlstore

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

* Fix support packet diagnostics lint and partial data handling

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

* Stabilize support packet pool idle assertion

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

* Relax live support packet DB counter assertions

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

* Fix deterministic pool diagnostics test wiring

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

* Mock support packet diagnostics in app test store

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

* Move SupportPacketDatabaseDiagnostics out of public model

The struct is an internal store→platform transport (no yaml tags, never
serialized directly) so it doesn't belong in server/public/model where
it would form a public API contract for plugins. Move it into the store
package as the natural return type of GetSupportPacketDatabaseDiagnostics.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Describe SupportPacketDatabaseDiagnostics by content, not history

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* wording

* Align store diagnostics with sqlstore conventions

- Rename Store.GetSupportPacketDatabaseDiagnostics to Store.GetDiagnostics
  and rename the holding files to diagnostics{,_test}.go.
- Drop the queryRowScanner / rowScanner / sqlQueryRowScanner test seam.
  The collectors now use the sqlxDBWrapper master handle and bind result
  rows into local structs via sqlx GetContext, matching how the rest of
  the sqlstore package talks to Postgres.
- Replace the hand-rolled mock-based unit tests for the Postgres
  collector with an integration test driven through StoreTest, the
  pattern used by the other sqlstore tests (e.g. schema_dump_test.go).
  The pure pool-stats unit test (TestApplyDBPoolStats) is kept.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Drop MasterDBStats/ReplicaDBStats from the Store interface

After GetDiagnostics moved into the store layer, no caller outside the
sqlstore package itself reads MasterDBStats/ReplicaDBStats through the
Store interface — the diagnostics collector calls them on the concrete
*SqlStore receiver. Remove them from the interface, the retry/timer
layer wrappers, the storetest fake, and the generated mock; drop the
now-redundant fixedDBStatsStore shim methods and mock setups in the
support packet tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Rename SupportPacketDatabaseDiagnostics to DatabaseDiagnostics

Now that the type lives in the store package and is returned by
Store.GetDiagnostics, the SupportPacket prefix is just legacy framing —
support packets are one consumer of the data, not its identity. Rename
to store.DatabaseDiagnostics for consistency with the package and method
name.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Inline diagnostics SQL into the collector functions

Each pg_stat query has a single caller, so a package-level constant just
adds indirection between the function and the SQL it owns. Move the
query strings to local consts inside the collectors that use them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Fix Connectios typo to Connections

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Fix sqlstore diagnostics build: call GetMaster().DB() method

The recent rebase brought in a change that turned the SqlStore DB
field into a method, so passing ss.GetMaster().DB to
collectPostgresDatabaseDiagnostics (which expects *sqlx.DB) no longer
compiles. Call the method instead.

* Fix gofmt alignment in SupportPacketDiagnostics

The post-merge struct had extra spaces on MasterConnections /
ReplicaConnections that broke gofmt alignment.

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 12:45:18 +02:00

248 lines
9.8 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
"time"
)
const (
CurrentSupportPacketVersion = 2
SupportPacketErrorFile = "warning.txt"
)
type SupportPacketDiagnostics struct {
Version int `yaml:"version"`
License struct {
Company string `yaml:"company"`
Users int `yaml:"users"`
SkuShortName string `yaml:"sku_short_name"`
IsTrial bool `yaml:"is_trial,omitempty"`
IsGovSKU bool `yaml:"is_gov_sku,omitempty"`
} `yaml:"license"`
Server struct {
OS string `yaml:"os"`
Architecture string `yaml:"architecture"`
CPUCores int `yaml:"cpu_cores"`
TotalMemoryMB uint64 `yaml:"total_memory_mb"`
ContainerCPULimit float64 `yaml:"container_cpu_limit,omitempty"`
ContainerMemoryLimitMB uint64 `yaml:"container_memory_limit_mb,omitempty"`
OpenFileDescriptors int64 `yaml:"open_file_descriptors"`
MaxFileDescriptors int64 `yaml:"max_file_descriptors"`
Hostname string `yaml:"hostname"`
ProcessID int `yaml:"process_id"`
StartedAt time.Time `yaml:"started_at"`
HostStartedAt time.Time `yaml:"host_started_at,omitempty"`
Version string `yaml:"version"`
BuildHash string `yaml:"build_hash"`
GoVersion string `yaml:"go_version"`
InstallationType string `yaml:"installation_type"`
} `yaml:"server"`
Config struct {
Source string `yaml:"store_type"`
} `yaml:"config"`
Database struct {
Type string `yaml:"type"`
Version string `yaml:"version"`
SchemaVersion string `yaml:"schema_version"`
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"`
CacheHitRatio *float64 `yaml:"cache_hit_ratio,omitempty"`
Deadlocks *int64 `yaml:"deadlocks,omitempty"`
TempFiles *int64 `yaml:"temp_files,omitempty"`
TempBytesMB *float64 `yaml:"temp_bytes_mb,omitempty"`
Rollbacks *int64 `yaml:"rollbacks,omitempty"`
IdleInTransactionCount *int64 `yaml:"idle_in_transaction_count,omitempty"`
LongestQueryDurationSeconds *float64 `yaml:"longest_query_duration_seconds,omitempty"`
WaitingForLockCount *int64 `yaml:"waiting_for_lock_count,omitempty"`
PostsDeadTuples *int64 `yaml:"posts_dead_tuples,omitempty"`
PostsLastAutovacuum *time.Time `yaml:"posts_last_autovacuum,omitempty"`
} `yaml:"database"`
FileStore struct {
Status string `yaml:"file_status"`
Error string `yaml:"erorr,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 {
Connections int `yaml:"connections"`
} `yaml:"websocket"`
Cluster struct {
ID string `yaml:"id"`
NumberOfNodes int `yaml:"number_of_nodes"`
} `yaml:"cluster"`
Notifications struct {
Email struct {
Status string `yaml:"status"`
Error string `yaml:"error,omitempty"`
} `yaml:"email,omitempty"`
Push struct {
Status string `yaml:"status"`
Error string `yaml:"error,omitempty"`
} `yaml:"push,omitempty"`
} `yaml:"notifications,omitempty"`
LDAP struct {
Status string `yaml:"status,omitempty"`
Error string `yaml:"error,omitempty"`
ServerName string `yaml:"server_name,omitempty"`
ServerVersion string `yaml:"server_version,omitempty"`
} `yaml:"ldap"`
SAML struct {
ProviderType string `yaml:"provider_type,omitempty"`
Status string `yaml:"status,omitempty"`
Error string `yaml:"error,omitempty"`
} `yaml:"saml"`
ElasticSearch struct {
Status string `yaml:"status,omitempty"`
Backend string `yaml:"backend,omitempty"`
ServerVersion string `yaml:"server_version,omitempty"`
ServerPlugins []string `yaml:"server_plugins,omitempty"`
Error string `yaml:"error,omitempty"`
} `yaml:"elastic"`
OAuthProviders OAuthProviders `yaml:"oauth_providers,omitempty"`
}
// OAuthProviderStatus reports the connectivity status of a single OAuth2/OpenID Connect provider.
type OAuthProviderStatus struct {
Status string `yaml:"status,omitempty"` // ok / fail / disabled
Error string `yaml:"error,omitempty"`
}
// OAuthProviders aggregates the connectivity status for the configured OAuth2/OpenID Connect providers.
type OAuthProviders struct {
GitLab OAuthProviderStatus `yaml:"gitlab,omitempty"`
Google OAuthProviderStatus `yaml:"google,omitempty"`
Office365 OAuthProviderStatus `yaml:"office365,omitempty"`
OpenID OAuthProviderStatus `yaml:"openid,omitempty"`
}
type SupportPacketStats struct {
RegisteredUsers int64 `yaml:"registered_users"`
ActiveUsers int64 `yaml:"active_users"`
DailyActiveUsers int64 `yaml:"daily_active_users"`
MonthlyActiveUsers int64 `yaml:"monthly_active_users"`
DeactivatedUsers int64 `yaml:"deactivated_users"`
Guests int64 `yaml:"guests"`
SingleChannelGuests int64 `yaml:"single_channel_guests"`
BotAccounts int64 `yaml:"bot_accounts"`
Posts int64 `yaml:"posts"`
Channels int64 `yaml:"channels"`
Teams int64 `yaml:"teams"`
SlashCommands int64 `yaml:"slash_commands"`
IncomingWebhooks int64 `yaml:"incoming_webhooks"`
OutgoingWebhooks int64 `yaml:"outgoing_webhooks"`
}
// SupportPacketJobList contains the list of latest run enterprise job runs.
// It is included in the Support Packet.
type SupportPacketJobList struct {
LDAPSyncJobs []*Job `yaml:"ldap_sync_jobs"`
DataRetentionJobs []*Job `yaml:"data_retention_jobs"`
MessageExportJobs []*Job `yaml:"message_export_jobs"`
ElasticPostIndexingJobs []*Job `yaml:"elastic_post_indexing_jobs"`
ElasticPostAggregationJobs []*Job `yaml:"elastic_post_aggregation_jobs"`
MigrationJobs []*Job `yaml:"migration_jobs"`
}
// SupportPacketPermissionInfo contains the list of schemes and the list of roles.
// It is included in the Support Packet.
type SupportPacketPermissionInfo struct {
Roles []*Role `yaml:"roles"`
Schemes []*Scheme `yaml:"schemes"`
}
// SupportPacketConfig contains the Mattermost configuration. In contrast to [Config], it also contains the list of Feature Flags.
// It is included in the Support Packet.
type SupportPacketConfig struct {
*Config
FeatureFlags FeatureFlags `json:"FeatureFlags"`
}
// SupportPacketPluginList contains the list of enabled and disabled plugins.
// It is included in the Support Packet.
type SupportPacketPluginList struct {
Enabled []Manifest `json:"enabled"`
Disabled []Manifest `json:"disabled"`
}
// SupportPacketDatabaseSchema contains the database schema information.
// It is included in the Support Packet.
type SupportPacketDatabaseSchema struct {
DatabaseCollation string `yaml:"database_collation,omitempty"`
DatabaseEncoding string `yaml:"database_encoding,omitempty"`
Tables []DatabaseTable `yaml:"tables"`
}
// DatabaseTable represents a table in the database schema.
type DatabaseTable struct {
Name string `yaml:"name"`
Collation string `yaml:"collation,omitempty"`
Options map[string]string `yaml:"options,omitempty"`
Columns []DatabaseColumn `yaml:"columns"`
Indexes []DatabaseIndex `yaml:"indexes,omitempty"`
}
// DatabaseColumn represents a column in a database table.
type DatabaseColumn struct {
Name string `yaml:"name"`
DataType string `yaml:"data_type"`
MaxLength int64 `yaml:"max_length,omitempty"`
IsNullable bool `yaml:"is_nullable"`
}
// DatabaseIndex represents an index in a database table.
type DatabaseIndex struct {
Name string `yaml:"name"`
Definition string `yaml:"definition"`
}
type FileData struct {
Filename string
Body []byte
}
type SupportPacketOptions struct {
IncludeLogs bool `json:"include_logs"` // IncludeLogs is the option to include server logs
PluginPackets []string `json:"plugin_packets"` // PluginPackets is a list of pluginids to call hooks
}
// SupportPacketOptionsFromReader decodes a json-encoded request from the given io.Reader.
func SupportPacketOptionsFromReader(reader io.Reader) (*SupportPacketOptions, error) {
var r *SupportPacketOptions
err := json.NewDecoder(reader).Decode(&r)
if err != nil {
return nil, err
}
return r, nil
}