Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify the data structure #16

Open
wants to merge 4 commits into
base: pcronin/performance-feature-branch
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 73 additions & 2 deletions cmd/routesum/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,66 @@ package main
import (
"bufio"
"bytes"
"flag"
"fmt"
"io"
"os"
"runtime/pprof"

"github.com/PatrickCronin/routesum/pkg/routesum"
"github.com/pkg/errors"
)

func main() {
if err := summarize(os.Stdin, os.Stdout); err != nil {
cpuProfile := flag.String("cpuprofile", "", "write cpu profile to file")
memProfile := flag.String("memprofile", "", "write mem profile to file")
showMemStats := flag.Bool(
"show-mem-stats",
false,
"Whether or not to write memory usage stats to STDERR. (This functionity requires the use of `unsafe`, so may not be perfect.)", //nolint: lll
)
flag.Parse()

var cpuProfOut io.Writer
if *cpuProfile != "" {
var err error
if cpuProfOut, err = os.Create(*cpuProfile); err != nil {
fmt.Fprint(os.Stderr, errors.Wrap(err, "create cpu profile output file").Error())
os.Exit(1)
}
}

var memProfOut io.WriteCloser
if *memProfile != "" {
var err error
if memProfOut, err = os.Create(*memProfile); err != nil {
fmt.Fprint(os.Stderr, errors.Wrap(err, "create mem profile output file").Error())
}
}

var memStatsOut io.Writer
if *showMemStats {
memStatsOut = os.Stderr
}

if err := summarize(os.Stdin, os.Stdout, memStatsOut, cpuProfOut, memProfOut); err != nil {
fmt.Fprintf(os.Stderr, "summarize: %s\n", err.Error())
os.Exit(1)
}
}

func summarize(in io.Reader, out io.Writer) error {
func summarize(
in io.Reader,
out, memStatsOut, cpuProfOut io.Writer,
memProfOut io.WriteCloser,
) error {
if cpuProfOut != nil {
if err := pprof.StartCPUProfile(cpuProfOut); err != nil {
return errors.Wrap(err, "start cpu profiling")
}
defer pprof.StopCPUProfile()
}

rs := routesum.NewRouteSum()
scanner := bufio.NewScanner(in)
for scanner.Scan() {
Expand All @@ -32,6 +77,32 @@ func summarize(in io.Reader, out io.Writer) error {
}
}

if memProfOut != nil {
if err := pprof.WriteHeapProfile(memProfOut); err != nil {
return errors.Wrap(err, "write mem profile")
}
if err := memProfOut.Close(); err != nil {
return errors.Wrap(err, "close mem profile")
}
}

if memStatsOut != nil {
numInternalNodes, numLeafNodes, internalNodesTotalSize, leafNodesTotalSize := rs.MemUsage()
fmt.Fprintf(memStatsOut,
`Num internal nodes: %d
Num leaf nodes: %d
Size of all internal nodes: %d
Size of all leaf nodes: %d
Total size of data structure: %d
`,
numInternalNodes,
numLeafNodes,
internalNodesTotalSize,
leafNodesTotalSize,
internalNodesTotalSize+leafNodesTotalSize,
)
}

for _, s := range rs.SummaryStrings() {
if _, err := out.Write([]byte(s + "\n")); err != nil {
return fmt.Errorf("write output: %w", err)
Expand Down
42 changes: 37 additions & 5 deletions cmd/routesum/main_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package main

import (
"io"
"regexp"
"strings"
"testing"

Expand All @@ -9,12 +11,42 @@ import (
)

func TestSummarize(t *testing.T) {
tests := []struct {
name string
showMemStats bool
expected *regexp.Regexp
}{
{
name: "without memory statistics",
showMemStats: false,
expected: regexp.MustCompile(`^$`),
},
{
name: "with memory statistics",
showMemStats: true,
expected: regexp.MustCompile(`Num internal nodes`),
},
}

inStr := "\n192.0.2.0\n192.0.2.1\n"
in := strings.NewReader(inStr)
var out strings.Builder

err := summarize(in, &out)
require.NoError(t, err, "summarize does not throw an error")
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
in := strings.NewReader(inStr)
var out strings.Builder

var memStatsBuilder strings.Builder
var memStatsOut io.Writer

if test.showMemStats {
memStatsOut = &memStatsBuilder
}

err := summarize(in, &out, memStatsOut, nil, nil)
require.NoError(t, err, "summarize does not throw an error")

assert.Equal(t, "192.0.2.0/31\n", out.String(), "read expected output")
assert.Equal(t, "192.0.2.0/31\n", out.String(), "read expected output")
assert.Regexp(t, test.expected, memStatsBuilder.String(), "read expected memory stats")
})
}
}
18 changes: 14 additions & 4 deletions pkg/routesum/routesum.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,21 @@ import (
"strings"

"github.com/PatrickCronin/routesum/pkg/routesum/bitslice"
"github.com/PatrickCronin/routesum/pkg/routesum/rstrie"
"github.com/PatrickCronin/routesum/pkg/routesum/rstree"
"github.com/pkg/errors"
"inet.af/netaddr"
)

// RouteSum has methods supporting route summarization of networks and hosts
type RouteSum struct {
ipv4, ipv6 *rstrie.RSTrie
ipv4, ipv6 *rstree.RSTree
}

// NewRouteSum returns an initialized RouteSum object
func NewRouteSum() *RouteSum {
rs := new(RouteSum)
rs.ipv4 = rstrie.NewRSTrie()
rs.ipv6 = rstrie.NewRSTrie()
rs.ipv4 = rstree.NewRSTree()
rs.ipv6 = rstree.NewRSTree()

return rs
}
Expand Down Expand Up @@ -142,3 +142,13 @@ func ipv6FromBits(bits bitslice.BitSlice) netaddr.IP {
copy(byteArray[:], bytes[0:16])
return netaddr.IPv6Raw(byteArray)
}

// MemUsage provides information about memory usage.
func (rs *RouteSum) MemUsage() (uint, uint, uintptr, uintptr) {
ipv4NumInternalNodes, ipv4NumLeafNodes, ipv4InternalNodesTotalSize, ipv4LeafNodesTotalSize := rs.ipv4.MemUsage()
ipv6NumInternalNodes, ipv6NumLeafNodes, ipv6InternalNodesTotalSize, ipv6LeafNodesTotalSize := rs.ipv6.MemUsage()
return ipv4NumInternalNodes + ipv6NumInternalNodes,
ipv4NumLeafNodes + ipv6NumLeafNodes,
ipv4InternalNodesTotalSize + ipv6InternalNodesTotalSize,
ipv4LeafNodesTotalSize + ipv6LeafNodesTotalSize
}
73 changes: 73 additions & 0 deletions pkg/routesum/routesum_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -455,3 +455,76 @@ func TestSummarize(t *testing.T) { //nolint: funlen
})
}
}

func TestMemUsage(t *testing.T) { //nolint: funlen
tests := []struct {
name string
entries []string
expectedNumInternalNodes uint
expectedNumLeafNodes uint
}{
{
name: "new trie",
expectedNumInternalNodes: 0,
expectedNumLeafNodes: 0,
},
{
name: "one item, IPv4",
entries: []string{
"192.0.2.1",
},
expectedNumInternalNodes: 32,
expectedNumLeafNodes: 1,
},
{
name: "two items, IPv4, summarized",
entries: []string{
"192.0.2.1",
"192.0.2.0",
},
expectedNumInternalNodes: 31,
expectedNumLeafNodes: 1,
},
{
name: "two items, IPv4, unsummarized",
entries: []string{
"192.0.2.1",
"192.0.2.2",
},
expectedNumInternalNodes: 33,
expectedNumLeafNodes: 2,
},
{
name: "one item, IPv6",
entries: []string{
"2001:db8::1",
},
expectedNumInternalNodes: 128,
expectedNumLeafNodes: 1,
},
{
name: "one IPv4 address, one IPv6 address",
entries: []string{
"192.0.2.0",
"2001:db8::1",
},
expectedNumInternalNodes: 160,
expectedNumLeafNodes: 2,
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
rs := NewRouteSum()

for _, entry := range test.entries {
err := rs.InsertFromString(entry)
require.NoError(t, err)
}

numInternalNodes, numLeafNodes, _, _ := rs.MemUsage()
assert.Equal(t, test.expectedNumInternalNodes, numInternalNodes, "num internal nodes")
assert.Equal(t, test.expectedNumLeafNodes, numLeafNodes, "num leaf nodes")
})
}
}
Loading