portlist: update some internals to use append-style APIs

In prep for reducing garbage, being able to reuse memory.  So far this
doesn't actually reuse much. This is just changing signatures around.

But some improvement in any case:

    bradfitz@tsdev:~/src/tailscale.com$ ~/go/bin/benchstat before after
    name       old time/op    new time/op    delta
    GetList-8    11.8ms ± 9%     9.9ms ± 3%  -15.98%  (p=0.000 n=10+10)

    name       old alloc/op   new alloc/op   delta
    GetList-8    99.5kB ± 2%    91.9kB ± 0%   -7.62%  (p=0.000 n=9+9)

    name       old allocs/op  new allocs/op  delta
    GetList-8     3.05k ± 1%     2.93k ± 0%   -3.83%  (p=0.000 n=8+9)

More later, once parsers can reuse strings from previous parses.

Updates #5958

Change-Id: I76cd5048246dd24d11c4e263d8bb8041747fb2b0
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
pull/6019/head
Brad Fitzpatrick 2 years ago committed by Brad Fitzpatrick
parent 67597bfc9e
commit 46ce80758d

@ -55,7 +55,7 @@ type nothing struct{}
// formats that we can parse without special detection logic. // formats that we can parse without special detection logic.
// Unfortunately, options to filter by proto or state are non-portable, // Unfortunately, options to filter by proto or state are non-portable,
// so we'll filter for ourselves. // so we'll filter for ourselves.
func parsePortsNetstat(output string) List { func appendParsePortsNetstat(base []Port, output string) []Port {
m := map[Port]nothing{} m := map[Port]nothing{}
lines := strings.Split(string(output), "\n") lines := strings.Split(string(output), "\n")
@ -131,13 +131,18 @@ func parsePortsNetstat(output string) List {
lastline = "" lastline = ""
} }
l := []Port{} ret := base
for p := range m { for p := range m {
l = append(l, p) ret = append(ret, p)
} }
sort.Slice(l, func(i, j int) bool {
return (&l[i]).lessThan(&l[j]) // Only sort the part we appended. It's up to the caller to sort the whole
// thing if they'd like. In practice the caller's base will have len 0,
// though, so the whole thing will be sorted.
toSort := ret[len(base):]
sort.Slice(toSort, func(i, j int) bool {
return (&toSort[i]).lessThan(&toSort[j])
}) })
return l return ret
} }

@ -25,7 +25,7 @@ func hideWindow(c *exec.Cmd) *exec.Cmd {
return c return c
} }
func listPortsNetstat(arg string) (List, error) { func appendListeningPortsNetstat(base []Port, arg string) ([]Port, error) {
exe, err := exec.LookPath("netstat") exe, err := exec.LookPath("netstat")
if err != nil { if err != nil {
return nil, fmt.Errorf("netstat: lookup: %v", err) return nil, fmt.Errorf("netstat: lookup: %v", err)
@ -40,5 +40,5 @@ func listPortsNetstat(arg string) (List, error) {
return nil, fmt.Errorf("netstat: %v (%q)", err, stderr) return nil, fmt.Errorf("netstat: %v (%q)", err, stderr)
} }
return parsePortsNetstat(string(output)), nil return appendParsePortsNetstat(base, string(output)), nil
} }

@ -2,6 +2,9 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build !ios && !js
// +build !ios,!js
package portlist package portlist
import ( import (
@ -83,7 +86,7 @@ func TestParsePortsNetstat(t *testing.T) {
Port{"udp", 9353, "iTunes", ""}, Port{"udp", 9353, "iTunes", ""},
} }
pl := parsePortsNetstat(netstatOutput) pl := appendParsePortsNetstat(nil, netstatOutput)
jgot, _ := json.MarshalIndent(pl, "", "\t") jgot, _ := json.MarshalIndent(pl, "", "\t")
jwant, _ := json.MarshalIndent(want, "", "\t") jwant, _ := json.MarshalIndent(want, "", "\t")
if len(pl) != len(want) { if len(pl) != len(want) {

@ -19,13 +19,16 @@ type Poller struct {
// Run completes, after which Err can be checked. // Run completes, after which Err can be checked.
C <-chan List C <-chan List
c chan List
// Err is the error from the final GetList call. It is only // Err is the error from the final GetList call. It is only
// valid to read once C has been closed. Err is nil if Close // valid to read once C has been closed. Err is nil if Close
// is called or the context is canceled. // is called or the context is canceled.
Err error Err error
// scatch is memory for Poller.getList to reuse between calls.
scratch []Port
c chan List // the unconstrained version of the exported C above
quitCh chan struct{} // close this to force exit quitCh chan struct{} // close this to force exit
prev List // most recent data prev List // most recent data
} }
@ -45,7 +48,7 @@ func NewPoller() (*Poller, error) {
// Do one initial poll synchronously so we can return an error // Do one initial poll synchronously so we can return an error
// early. // early.
var err error var err error
p.prev, err = getList(nil) p.prev, err = p.getList()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -76,7 +79,7 @@ func (p *Poller) Run(ctx context.Context) error {
for { for {
select { select {
case <-tick.C: case <-tick.C:
pl, err := getList(p.prev) pl, err := p.getList()
if err != nil { if err != nil {
p.Err = err p.Err = err
return err return err

@ -76,18 +76,19 @@ func (pl List) String() string {
var debugDisablePortlist = envknob.RegisterBool("TS_DEBUG_DISABLE_PORTLIST") var debugDisablePortlist = envknob.RegisterBool("TS_DEBUG_DISABLE_PORTLIST")
func getList(prev List) (List, error) { func (p *Poller) getList() (List, error) {
if debugDisablePortlist() { if debugDisablePortlist() {
return nil, nil return nil, nil
} }
pl, err := listPorts() var err error
p.scratch, err = appendListeningPorts(p.scratch[:0])
if err != nil { if err != nil {
return nil, fmt.Errorf("listPorts: %s", err) return nil, fmt.Errorf("listPorts: %s", err)
} }
pl = sortAndDedup(pl) pl := sortAndDedup(p.scratch)
if pl.sameInodes(prev) { if pl.sameInodes(p.prev) {
// Nothing changed, skip inode lookup // Nothing changed, skip inode lookup
return prev, nil return p.prev, nil
} }
pl, err = addProcesses(pl) pl, err = addProcesses(pl)
if err != nil { if err != nil {

@ -14,7 +14,7 @@ import (
const pollInterval = 9999 * time.Hour const pollInterval = 9999 * time.Hour
func listPorts() (List, error) { func appendListeningPorts(base []Port) ([]Port, error) {
return nil, errors.New("not implemented") return nil, errors.New("not implemented")
} }

@ -8,8 +8,8 @@ import "time"
const pollInterval = 365 * 24 * time.Hour const pollInterval = 365 * 24 * time.Hour
func listPorts() (List, error) { func appendListeningPorts(base []Port) ([]Port, error) {
return nil, nil return base, nil
} }
func addProcesses(pl []Port) ([]Port, error) { func addProcesses(pl []Port) ([]Port, error) {

@ -35,11 +35,11 @@ const (
v4Any = "00000000:0000" v4Any = "00000000:0000"
) )
func listPorts() (List, error) { func appendListeningPorts(base []Port) ([]Port, error) {
ret := base
if sawProcNetPermissionErr.Load() { if sawProcNetPermissionErr.Load() {
return nil, nil return ret, nil
} }
var ret []Port
var br *bufio.Reader var br *bufio.Reader
for _, fname := range sockfiles { for _, fname := range sockfiles {

@ -132,8 +132,10 @@ func BenchmarkParsePorts(b *testing.B) {
func BenchmarkListPorts(b *testing.B) { func BenchmarkListPorts(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
var base []Port
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_, err := listPorts() var err error
base, err = appendListeningPorts(base[:0])
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }

@ -21,8 +21,8 @@ import (
// We have to run netstat, which is a bit expensive, so don't do it too often. // We have to run netstat, which is a bit expensive, so don't do it too often.
const pollInterval = 5 * time.Second const pollInterval = 5 * time.Second
func listPorts() (List, error) { func appendListeningPorts(base []Port) ([]Port, error) {
return listPortsNetstat("-na") return appendListeningPortsNetstat(base, "-na")
} }
var lsofFailed int64 // atomic bool var lsofFailed int64 // atomic bool

@ -12,8 +12,8 @@ import "time"
// We have to run netstat, which is a bit expensive, so don't do it too often. // We have to run netstat, which is a bit expensive, so don't do it too often.
const pollInterval = 5 * time.Second const pollInterval = 5 * time.Second
func listPorts() (List, error) { func appendListeningPorts(base []Port) ([]Port, error) {
return listPortsNetstat("-na") return appendListeningPortsNetstat(base, "-na")
} }
func addProcesses(pl []Port) ([]Port, error) { func addProcesses(pl []Port) ([]Port, error) {

@ -14,7 +14,8 @@ import (
func TestGetList(t *testing.T) { func TestGetList(t *testing.T) {
tstest.ResourceCheck(t) tstest.ResourceCheck(t)
pl, err := getList(nil) var p Poller
pl, err := p.getList()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -34,7 +35,8 @@ func TestIgnoreLocallyBoundPorts(t *testing.T) {
defer ln.Close() defer ln.Close()
ta := ln.Addr().(*net.TCPAddr) ta := ln.Addr().(*net.TCPAddr)
port := ta.Port port := ta.Port
pl, err := getList(nil) var p Poller
pl, err := p.getList()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -194,8 +196,9 @@ func TestSameInodes(t *testing.T) {
func BenchmarkGetList(b *testing.B) { func BenchmarkGetList(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
var p Poller
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_, err := getList(nil) _, err := p.getList()
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }

@ -15,11 +15,11 @@ import (
// Forking on Windows is insanely expensive, so don't do it too often. // Forking on Windows is insanely expensive, so don't do it too often.
const pollInterval = 5 * time.Second const pollInterval = 5 * time.Second
func listPorts() (List, error) { func appendListeningPorts(base []Port) ([]Port, error) {
// TODO(bradfitz): stop shelling out to netstat and use the // TODO(bradfitz): stop shelling out to netstat and use the
// net/netstat package instead. When doing so, be sure to filter // net/netstat package instead. When doing so, be sure to filter
// out all of 127.0.0.0/8 and not just 127.0.0.1. // out all of 127.0.0.0/8 and not just 127.0.0.1.
return listPortsNetstat("-na") return appendListeningPortsNetstat(base, "-na")
} }
func addProcesses(pl []Port) ([]Port, error) { func addProcesses(pl []Port) ([]Port, error) {
@ -31,9 +31,9 @@ func addProcesses(pl []Port) ([]Port, error) {
} }
defer tok.Close() defer tok.Close()
if !tok.IsElevated() { if !tok.IsElevated() {
return listPortsNetstat("-na") return appendListeningPortsNetstat(nil, "-na")
} }
return listPortsNetstat("-nab") return appendListeningPortsNetstat(nil, "-nab")
} }
func init() { func init() {

Loading…
Cancel
Save