util/dirwalk, metrics, portlist: add new package for fast directory walking

This is similar to the golang.org/x/tools/internal/fastwalk I'd
previously written but not recursive and using mem.RO.

The metrics package already had some Linux-specific directory reading
code in it. Move that out to a new general package that can be reused
by portlist too, which helps its scanning of all /proc files:

    name                old time/op    new time/op    delta
    FindProcessNames-8    2.79ms ± 6%    2.45ms ± 7%  -12.11%  (p=0.000 n=10+10)

    name                old alloc/op   new alloc/op   delta
    FindProcessNames-8    62.9kB ± 0%    33.5kB ± 0%  -46.76%  (p=0.000 n=9+10)

    name                old allocs/op  new allocs/op  delta
    FindProcessNames-8     2.25k ± 0%     0.38k ± 0%  -82.98%  (p=0.000 n=9+10)

Change-Id: I75db393032c328f12d95c39f71c9742c375f207a
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick
2022-11-05 14:26:29 -07:00
committed by Brad Fitzpatrick
parent 21ef7e5c35
commit db2cc393af
9 changed files with 422 additions and 178 deletions

54
util/dirwalk/dirwalk.go Normal file
View File

@@ -0,0 +1,54 @@
// Copyright (c) 2022 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 dirwalk contains code to walk a directory.
package dirwalk
import (
"io"
"io/fs"
"os"
"go4.org/mem"
)
var osWalkShallow func(name mem.RO, fn WalkFunc) error
// WalkFunc is the callback type used with WalkShallow.
//
// The name and de are only valid for the duration of func's call
// and should not be retained.
type WalkFunc func(name mem.RO, de fs.DirEntry) error
// WalkShallow reads the entries in the named directory and calls fn for each.
// It does not recurse into subdirectories.
//
// If fn returns an error, iteration stops and WalkShallow returns that value.
//
// On Linux, WalkShallow does not allocate, so long as certain methods on the
// WalkFunc's DirEntry are not called which necessarily allocate.
func WalkShallow(dirName mem.RO, fn WalkFunc) error {
if f := osWalkShallow; f != nil {
return f(dirName, fn)
}
of, err := os.Open(dirName.StringCopy())
if err != nil {
return err
}
defer of.Close()
for {
fis, err := of.ReadDir(100)
for _, de := range fis {
if err := fn(mem.S(de.Name()), de); err != nil {
return err
}
}
if err != nil {
if err == io.EOF {
return nil
}
return err
}
}
}