diff --git a/portlist/portlist_linux.go b/portlist/portlist_linux.go index 6488c4933..0f8a74cd6 100644 --- a/portlist/portlist_linux.go +++ b/portlist/portlist_linux.go @@ -17,6 +17,7 @@ import ( "syscall" "time" + "go4.org/mem" "golang.org/x/sys/unix" "tailscale.com/syncs" ) @@ -82,8 +83,11 @@ func parsePorts(r *bufio.Reader, proto string) ([]Port, error) { return nil, err } + fields := make([]mem.RO, 0, 20) // 17 current fields + some future slop + + var inoBuf []byte for err == nil { - line, err := r.ReadString('\n') + line, err := r.ReadSlice('\n') if err == io.EOF { break } @@ -92,37 +96,39 @@ func parsePorts(r *bufio.Reader, proto string) ([]Port, error) { } // sl local rem ... inode - words := strings.Fields(line) - local := words[1] - rem := words[2] - inode := words[9] + fields = mem.AppendFields(fields[:0], mem.B(line)) + local := fields[1] + rem := fields[2] + inode := fields[9] // If a port is bound to localhost, ignore it. // TODO: localhost is bigger than 1 IP, we need to ignore // more things. - if strings.HasPrefix(local, v4Localhost) || strings.HasPrefix(local, v6Localhost) { + if mem.HasPrefix(local, mem.S(v4Localhost)) || mem.HasPrefix(local, mem.S(v6Localhost)) { continue } - if rem != v4Any && rem != v6Any { + if !rem.Equal(mem.S(v4Any)) && !rem.Equal(mem.S(v6Any)) { // not a "listener" port continue } // Don't use strings.Split here, because it causes // allocations significant enough to show up in profiles. - i := strings.IndexByte(local, ':') + i := mem.IndexByte(local, ':') if i == -1 { - return nil, fmt.Errorf("%q unexpectedly didn't have a colon", local) + return nil, fmt.Errorf("%q unexpectedly didn't have a colon", local.StringCopy()) } - portv, err := strconv.ParseUint(local[i+1:], 16, 16) + portv, err := mem.ParseUint(local.SliceFrom(i+1), 16, 16) if err != nil { - return nil, fmt.Errorf("%#v: %s", local[9:], err) + return nil, fmt.Errorf("%#v: %s", local.SliceFrom(9).StringCopy(), err) } - inodev := fmt.Sprintf("socket:[%s]", inode) + inoBuf = append(inoBuf[:0], "socket:["...) + inoBuf = mem.Append(inoBuf, inode) + inoBuf = append(inoBuf, ']') ret = append(ret, Port{ Proto: proto, Port: uint16(portv), - inode: inodev, + inode: string(inoBuf), }) } diff --git a/portlist/portlist_linux_test.go b/portlist/portlist_linux_test.go index aac2028f9..911ea91b6 100644 --- a/portlist/portlist_linux_test.go +++ b/portlist/portlist_linux_test.go @@ -7,6 +7,7 @@ package portlist import ( "bufio" "bytes" + "io" "testing" "github.com/google/go-cmp/cmp" @@ -65,3 +66,37 @@ func TestParsePorts(t *testing.T) { }) } } + +func BenchmarkParsePorts(b *testing.B) { + b.ReportAllocs() + + var contents bytes.Buffer + contents.WriteString(` sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode + 0: 00000000000000000000000001000000:0277 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 35720 1 0000000000000000 100 0 0 10 0 + 1: 00000000000000000000000000000000:1F91 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 142240557 1 0000000000000000 100 0 0 10 0 + 2: 00000000000000000000000000000000:0016 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 34064 1 0000000000000000 100 0 0 10 0 +`) + for i := 0; i < 50000; i++ { + contents.WriteString(" 3: 69050120005716BC64906EBE009ECD4D:D506 0047062600000000000000006E171268:01BB 01 00000000:00000000 02:0000009E 00000000 1000 0 151042856 2 0000000000000000 21 4 28 10 -1\n") + } + + want := []Port{ + {Proto: "tcp", Port: 8081, inode: "socket:[142240557]"}, + {Proto: "tcp", Port: 22, inode: "socket:[34064]"}, + } + + r := bytes.NewReader(contents.Bytes()) + br := bufio.NewReader(&contents) + b.ResetTimer() + for i := 0; i < b.N; i++ { + r.Seek(0, io.SeekStart) + br.Reset(r) + got, err := parsePorts(br, "tcp") + if err != nil { + b.Fatal(err) + } + if len(got) != 2 || got[0].Port != 8081 || got[1].Port != 22 { + b.Fatalf("wrong result:\n got %+v\nwant %+v", got, want) + } + } +}