Skip to content

Commit

Permalink
portlist: update some internals to use append-style APIs
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
bradfitz committed Oct 22, 2022
1 parent 67597bf commit 0e8af01
Show file tree
Hide file tree
Showing 13 changed files with 55 additions and 38 deletions.
17 changes: 11 additions & 6 deletions portlist/netstat.go
Expand Up @@ -55,7 +55,7 @@ type nothing struct{}
// formats that we can parse without special detection logic.
// Unfortunately, options to filter by proto or state are non-portable,
// so we'll filter for ourselves.
func parsePortsNetstat(output string) List {
func appendParsePortsNetstat(base []Port, output string) []Port {
m := map[Port]nothing{}
lines := strings.Split(string(output), "\n")

Expand Down Expand Up @@ -131,13 +131,18 @@ func parsePortsNetstat(output string) List {
lastline = ""
}

l := []Port{}
ret := base
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
}
4 changes: 2 additions & 2 deletions portlist/netstat_exec.go
Expand Up @@ -25,7 +25,7 @@ func hideWindow(c *exec.Cmd) *exec.Cmd {
return c
}

func listPortsNetstat(arg string) (List, error) {
func appendListeningPortsNetstat(base []Port, arg string) ([]Port, error) {
exe, err := exec.LookPath("netstat")
if err != nil {
return nil, fmt.Errorf("netstat: lookup: %v", err)
Expand All @@ -40,5 +40,5 @@ func listPortsNetstat(arg string) (List, error) {
return nil, fmt.Errorf("netstat: %v (%q)", err, stderr)
}

return parsePortsNetstat(string(output)), nil
return appendParsePortsNetstat(base, string(output)), nil
}
5 changes: 4 additions & 1 deletion portlist/netstat_test.go
Expand Up @@ -2,6 +2,9 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build !ios && !js
// +build !ios,!js

package portlist

import (
Expand Down Expand Up @@ -83,7 +86,7 @@ func TestParsePortsNetstat(t *testing.T) {
Port{"udp", 9353, "iTunes", ""},
}

pl := parsePortsNetstat(netstatOutput)
pl := appendParsePortsNetstat(nil, netstatOutput)
jgot, _ := json.MarshalIndent(pl, "", "\t")
jwant, _ := json.MarshalIndent(want, "", "\t")
if len(pl) != len(want) {
Expand Down
11 changes: 7 additions & 4 deletions portlist/poller.go
Expand Up @@ -19,13 +19,16 @@ type Poller struct {
// Run completes, after which Err can be checked.
C <-chan List

c chan List

// 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
// is called or the context is canceled.
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
prev List // most recent data
}
Expand All @@ -45,7 +48,7 @@ func NewPoller() (*Poller, error) {
// Do one initial poll synchronously so we can return an error
// early.
var err error
p.prev, err = getList(nil)
p.prev, err = p.getList()
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -76,7 +79,7 @@ func (p *Poller) Run(ctx context.Context) error {
for {
select {
case <-tick.C:
pl, err := getList(p.prev)
pl, err := p.getList()
if err != nil {
p.Err = err
return err
Expand Down
11 changes: 6 additions & 5 deletions portlist/portlist.go
Expand Up @@ -76,18 +76,19 @@ func (pl List) String() string {

var debugDisablePortlist = envknob.RegisterBool("TS_DEBUG_DISABLE_PORTLIST")

func getList(prev List) (List, error) {
func (p *Poller) getList() (List, error) {
if debugDisablePortlist() {
return nil, nil
}
pl, err := listPorts()
var err error
p.scratch, err = appendListeningPorts(p.scratch[:0])
if err != nil {
return nil, fmt.Errorf("listPorts: %s", err)
}
pl = sortAndDedup(pl)
if pl.sameInodes(prev) {
pl := sortAndDedup(p.scratch)
if pl.sameInodes(p.prev) {
// Nothing changed, skip inode lookup
return prev, nil
return p.prev, nil
}
pl, err = addProcesses(pl)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion portlist/portlist_ios.go
Expand Up @@ -14,7 +14,7 @@ import (

const pollInterval = 9999 * time.Hour

func listPorts() (List, error) {
func appendListeningPorts(base []Port) ([]Port, error) {
return nil, errors.New("not implemented")
}

Expand Down
8 changes: 4 additions & 4 deletions portlist/portlist_js.go
Expand Up @@ -8,10 +8,10 @@ import "time"

const pollInterval = 365 * 24 * time.Hour

func listPorts() (List, error) {
return nil, nil
func appendListeningPorts(base []Port) ([]Port, error) {
return base, nil
}

func addProcesses(pl []Port) ([]Port, error) {
return pl, nil
func addProcesses([]Port) error {
return nil
}
6 changes: 3 additions & 3 deletions portlist/portlist_linux.go
Expand Up @@ -35,11 +35,11 @@ const (
v4Any = "00000000:0000"
)

func listPorts() (List, error) {
func appendListeningPorts(base []Port) ([]Port, error) {
ret := base
if sawProcNetPermissionErr.Load() {
return nil, nil
return ret, nil
}
var ret []Port

var br *bufio.Reader
for _, fname := range sockfiles {
Expand Down
4 changes: 3 additions & 1 deletion portlist/portlist_linux_test.go
Expand Up @@ -132,8 +132,10 @@ func BenchmarkParsePorts(b *testing.B) {

func BenchmarkListPorts(b *testing.B) {
b.ReportAllocs()
var base []Port
for i := 0; i < b.N; i++ {
_, err := listPorts()
var err error
base, err = appendListeningPorts(base[:0])
if err != nil {
b.Fatal(err)
}
Expand Down
4 changes: 2 additions & 2 deletions portlist/portlist_macos.go
Expand Up @@ -21,8 +21,8 @@ import (
// We have to run netstat, which is a bit expensive, so don't do it too often.
const pollInterval = 5 * time.Second

func listPorts() (List, error) {
return listPortsNetstat("-na")
func appendListeningPorts(base []Port) ([]Port, error) {
return appendListeningPortsNetstat(base, "-na")
}

var lsofFailed int64 // atomic bool
Expand Down
4 changes: 2 additions & 2 deletions portlist/portlist_other.go
Expand Up @@ -12,8 +12,8 @@ import "time"
// We have to run netstat, which is a bit expensive, so don't do it too often.
const pollInterval = 5 * time.Second

func listPorts() (List, error) {
return listPortsNetstat("-na")
func appendListeningPorts(base []Port) ([]Port, error) {
return appendListeningPortsNetstat(base, "-na")
}

func addProcesses(pl []Port) ([]Port, error) {
Expand Down
9 changes: 6 additions & 3 deletions portlist/portlist_test.go
Expand Up @@ -14,7 +14,8 @@ import (
func TestGetList(t *testing.T) {
tstest.ResourceCheck(t)

pl, err := getList(nil)
var p Poller
pl, err := p.getList()
if err != nil {
t.Fatal(err)
}
Expand All @@ -34,7 +35,8 @@ func TestIgnoreLocallyBoundPorts(t *testing.T) {
defer ln.Close()
ta := ln.Addr().(*net.TCPAddr)
port := ta.Port
pl, err := getList(nil)
var p Poller
pl, err := p.getList()
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -194,8 +196,9 @@ func TestSameInodes(t *testing.T) {

func BenchmarkGetList(b *testing.B) {
b.ReportAllocs()
var p Poller
for i := 0; i < b.N; i++ {
_, err := getList(nil)
_, err := p.getList()
if err != nil {
b.Fatal(err)
}
Expand Down
8 changes: 4 additions & 4 deletions portlist/portlist_windows.go
Expand Up @@ -15,11 +15,11 @@ import (
// Forking on Windows is insanely expensive, so don't do it too often.
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
// 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.
return listPortsNetstat("-na")
return appendListeningPortsNetstat(base, "-na")
}

func addProcesses(pl []Port) ([]Port, error) {
Expand All @@ -31,9 +31,9 @@ func addProcesses(pl []Port) ([]Port, error) {
}
defer tok.Close()
if !tok.IsElevated() {
return listPortsNetstat("-na")
return appendListeningPortsNetstat(nil, "-na")
}
return listPortsNetstat("-nab")
return appendListeningPortsNetstat(nil, "-nab")
}

func init() {
Expand Down

0 comments on commit 0e8af01

Please sign in to comment.