diff --git a/server/channels/app/platform/fd_darwin.go b/server/channels/app/platform/fd_darwin.go new file mode 100644 index 00000000000..e022c3792e6 --- /dev/null +++ b/server/channels/app/platform/fd_darwin.go @@ -0,0 +1,30 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +//go:build darwin + +package platform + +import ( + "os" + "syscall" +) + +// getOpenFileDescriptors returns the number of open file descriptors for the current process. +func getOpenFileDescriptors() (int64, error) { + entries, err := os.ReadDir("/dev/fd") + if err != nil { + return -1, err + } + // Subtract 1 because the ReadDir call itself opens a file descriptor that appears in the listing. + return max(int64(len(entries))-1, 0), nil +} + +// getMaxFileDescriptors returns the soft file descriptor limit for the current process. +func getMaxFileDescriptors() (int64, error) { + var rlimit syscall.Rlimit + if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rlimit); err != nil { + return -1, err + } + return int64(rlimit.Cur), nil +} diff --git a/server/channels/app/platform/fd_linux.go b/server/channels/app/platform/fd_linux.go new file mode 100644 index 00000000000..59f6b3a3b9f --- /dev/null +++ b/server/channels/app/platform/fd_linux.go @@ -0,0 +1,35 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +//go:build linux + +package platform + +import ( + "fmt" + "math" + "os" + "syscall" +) + +// getOpenFileDescriptors returns the number of open file descriptors for the current process. +func getOpenFileDescriptors() (int64, error) { + entries, err := os.ReadDir("/proc/self/fd") + if err != nil { + return -1, err + } + // Subtract 1 because the ReadDir call itself opens a file descriptor that appears in the listing. + return max(int64(len(entries))-1, 0), nil +} + +// getMaxFileDescriptors returns the soft file descriptor limit for the current process. +func getMaxFileDescriptors() (int64, error) { + var rlimit syscall.Rlimit + if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rlimit); err != nil { + return -1, err + } + if rlimit.Cur > math.MaxInt64 { + return -1, fmt.Errorf("rlimit.Cur %d overflows int64", rlimit.Cur) + } + return int64(rlimit.Cur), nil +} diff --git a/server/channels/app/platform/fd_other.go b/server/channels/app/platform/fd_other.go new file mode 100644 index 00000000000..4f189dd03f6 --- /dev/null +++ b/server/channels/app/platform/fd_other.go @@ -0,0 +1,16 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +//go:build !linux && !darwin + +package platform + +// getOpenFileDescriptors returns -1 on unsupported platforms. +func getOpenFileDescriptors() (int64, error) { + return -1, nil +} + +// getMaxFileDescriptors returns -1 on unsupported platforms. +func getMaxFileDescriptors() (int64, error) { + return -1, nil +} diff --git a/server/channels/app/platform/fd_test.go b/server/channels/app/platform/fd_test.go new file mode 100644 index 00000000000..36c4cd0bd31 --- /dev/null +++ b/server/channels/app/platform/fd_test.go @@ -0,0 +1,27 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +package platform + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGetOpenFileDescriptors(t *testing.T) { + count, err := getOpenFileDescriptors() + require.NoError(t, err) + if count == -1 { + return + } + assert.Positive(t, count) +} + +func TestGetMaxFileDescriptors(t *testing.T) { + maxFDs, err := getMaxFileDescriptors() + require.NoError(t, err) + // -1 means unsupported platform; otherwise should be positive + assert.True(t, maxFDs == -1 || maxFDs > 0, "maxFDs should be -1 (unsupported) or positive, got %d", maxFDs) +} diff --git a/server/channels/app/platform/support_packet.go b/server/channels/app/platform/support_packet.go index 59390f2a6ba..917bbd87bcd 100644 --- a/server/channels/app/platform/support_packet.go +++ b/server/channels/app/platform/support_packet.go @@ -118,6 +118,14 @@ func (ps *PlatformService) getSupportPacketDiagnostics(rctx request.CTX) (*model installationType = unknownDataPoint } d.Server.InstallationType = installationType + d.Server.OpenFileDescriptors, err = getOpenFileDescriptors() + if err != nil { + rErr = multierror.Append(rErr, errors.Wrap(err, "error while getting open file descriptor count")) + } + d.Server.MaxFileDescriptors, err = getMaxFileDescriptors() + if err != nil { + rErr = multierror.Append(rErr, errors.Wrap(err, "error while getting max file descriptor limit")) + } d.Server.ProcessID = os.Getpid() /* Config */ diff --git a/server/channels/app/platform/support_packet_test.go b/server/channels/app/platform/support_packet_test.go index b8adf91d28a..7050c113265 100644 --- a/server/channels/app/platform/support_packet_test.go +++ b/server/channels/app/platform/support_packet_test.go @@ -210,6 +210,8 @@ func TestGetSupportPacketDiagnostics(t *testing.T) { 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) assert.Positive(t, d.Server.ProcessID) /* Config */ diff --git a/server/public/model/support_packet.go b/server/public/model/support_packet.go index baecc2f51e1..37bcdb25289 100644 --- a/server/public/model/support_packet.go +++ b/server/public/model/support_packet.go @@ -25,16 +25,18 @@ type SupportPacketDiagnostics struct { } `yaml:"license"` Server struct { - OS string `yaml:"os"` - Architecture string `yaml:"architecture"` - CPUCores int `yaml:"cpu_cores"` - TotalMemoryMB uint64 `yaml:"total_memory_mb"` - Hostname string `yaml:"hostname"` - ProcessID int `yaml:"process_id"` - Version string `yaml:"version"` - BuildHash string `yaml:"build_hash"` - GoVersion string `yaml:"go_version"` - InstallationType string `yaml:"installation_type"` + OS string `yaml:"os"` + Architecture string `yaml:"architecture"` + CPUCores int `yaml:"cpu_cores"` + TotalMemoryMB uint64 `yaml:"total_memory_mb"` + OpenFileDescriptors int64 `yaml:"open_file_descriptors"` + MaxFileDescriptors int64 `yaml:"max_file_descriptors"` + Hostname string `yaml:"hostname"` + ProcessID int `yaml:"process_id"` + Version string `yaml:"version"` + BuildHash string `yaml:"build_hash"` + GoVersion string `yaml:"go_version"` + InstallationType string `yaml:"installation_type"` } `yaml:"server"` Config struct {