diff --git a/derp/derp_server.go b/derp/derp_server.go index 9fe36a524..c45db299f 100644 --- a/derp/derp_server.go +++ b/derp/derp_server.go @@ -1609,20 +1609,12 @@ func (s *Server) expVarFunc(f func() interface{}) expvar.Func { }) } -func currentFDs() int { - // TODO(bradfitz): this could be more efficient, without making so much - // garbage. Probably doesn't matter but a Linux-only fast path would be - // reasonable for us using something like goimport's fastwalk. - ents, _ := os.ReadDir("/proc/self/fd") - return len(ents) -} - // ExpVar returns an expvar variable suitable for registering with expvar.Publish. func (s *Server) ExpVar() expvar.Var { m := new(metrics.Set) m.Set("gauge_memstats_sys0", expvar.Func(func() interface{} { return int64(s.memSys0) })) m.Set("gauge_watchers", s.expVarFunc(func() interface{} { return len(s.watchers) })) - m.Set("gauge_current_file_descriptors", expvar.Func(func() interface{} { return currentFDs() })) + m.Set("gauge_current_file_descriptors", expvar.Func(func() interface{} { return metrics.CurrentFDs() })) m.Set("gauge_current_connections", &s.curClients) m.Set("gauge_current_home_connections", &s.curHomeClients) m.Set("gauge_clients_total", expvar.Func(func() interface{} { return len(s.clientsMesh) })) diff --git a/metrics/fds_linux.go b/metrics/fds_linux.go new file mode 100644 index 000000000..4fc8c1870 --- /dev/null +++ b/metrics/fds_linux.go @@ -0,0 +1,13 @@ +// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package metrics + +import "os" + +func currentFDs() int { + // TODO(bradfitz): do this without so many allocations on Linux. + ents, _ := os.ReadDir("/proc/self/fd") + return len(ents) +} diff --git a/metrics/fds_notlinux.go b/metrics/fds_notlinux.go new file mode 100644 index 000000000..87cc8e4a0 --- /dev/null +++ b/metrics/fds_notlinux.go @@ -0,0 +1,10 @@ +// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !linux +// +build !linux + +package metrics + +func currentFDs() int { return 0 } diff --git a/metrics/metrics.go b/metrics/metrics.go index d057f270c..bc19a1849 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -47,3 +47,10 @@ func (m *LabelMap) GetFloat(key string) *expvar.Float { m.AddFloat(key, 0.0) return m.Map.Get(key).(*expvar.Float) } + +// CurrentFDs reports how many file descriptors are currently open. +// +// It only works on Linux. It returns zero otherwise. +func CurrentFDs() int { + return currentFDs() +} diff --git a/metrics/metrics_test.go b/metrics/metrics_test.go new file mode 100644 index 000000000..ee9d3c1d5 --- /dev/null +++ b/metrics/metrics_test.go @@ -0,0 +1,28 @@ +// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package metrics + +import ( + "runtime" + "testing" +) + +func TestCurrentFileDescriptors(t *testing.T) { + if runtime.GOOS != "linux" { + t.Skipf("skipping on %v", runtime.GOOS) + } + if n := CurrentFDs(); n < 3 { + t.Errorf("got %v; want >= 3", n) + } else { + t.Logf("got %v", n) + } +} + +func BenchmarkCurrentFileDescriptors(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _ = CurrentFDs() + } +}