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

testing: an improved router benchmark that only runs one router #4444

Merged
merged 44 commits into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
d90161d
router: The beginings of a new benchmark using an isolated router.
jiceatscion Nov 15, 2023
9d6e297
router: add a check for forwarded packets.
jiceatscion Nov 16, 2023
943d527
router: make sure brload terminates.
jiceatscion Nov 17, 2023
ef94d2b
router: new benchmark made to work.
jiceatscion Nov 17, 2023
15765c0
router: Enlarge the test to get more stable measurements.
jiceatscion Nov 17, 2023
2a67ef0
router: newbenchmark improvements
jiceatscion Nov 20, 2023
a1a379c
router: move br_load into new_benchmark.
jiceatscion Nov 21, 2023
c4ac941
router: mild improvements to the benchmark code.
jiceatscion Nov 21, 2023
e619296
router: cleanup newbenchmark.
jiceatscion Nov 21, 2023
45abaf2
router: improvements to newbenchmark.
jiceatscion Nov 22, 2023
f72f52e
router: comment fic in brload.
jiceatscion Nov 22, 2023
4bc839c
Merge branch 'scionproto:master' into router_newbenchmark
jiceatscion Nov 23, 2023
345ce20
router: remove leftover debug log.
jiceatscion Nov 23, 2023
3828d1d
router: satisfy linter.
jiceatscion Nov 23, 2023
7899630
router: fully revert dataplane.go to master's.
jiceatscion Nov 23, 2023
5e2fe0f
router: relax router_benchmark's timeout; it seems to have become a b…
jiceatscion Nov 23, 2023
e8a523a
router: Stop consuming forwarded packets while benchmarking.
jiceatscion Nov 23, 2023
e9b1093
router: 4 cores is just as good as 6 now that we don't consume packets.
jiceatscion Nov 23, 2023
171acad
router: bump br_transit CI's expectations.
jiceatscion Nov 23, 2023
f84224c
router: newbenchmark improvements.
jiceatscion Nov 23, 2023
7cf275e
router: do not build a private router image for the newbenchmark test.
jiceatscion Nov 24, 2023
165249e
router: prettify cmdline flag management (by some definition of pretty)
jiceatscion Nov 24, 2023
0ee2d30
router: dropped CS/DS from the topology of router_newbenchmark. Not n…
jiceatscion Nov 24, 2023
21a406b
router: reduce newbenchmark expectations a tiny bit after observing a…
jiceatscion Nov 24, 2023
40adc9e
router: slight cleanup of the newbenchmark test script.
jiceatscion Nov 27, 2023
55fd64b
router: Please lint and replace sudoA() with sudo().
jiceatscion Nov 27, 2023
0c746a8
router: implement reviewers suggestions and some more cleanup.
jiceatscion Nov 28, 2023
9a2e96e
router: fix failed formatting of prom query.
jiceatscion Nov 28, 2023
653d885
router: some minor improvements to router_newbenchmark.
jiceatscion Nov 28, 2023
06563d9
router: make the linter happy by exporting the entry points of topo.go
jiceatscion Nov 28, 2023
f1819fe
router: mild improvements to newbenchmark's test harness.
jiceatscion Nov 29, 2023
296e592
router: Small cleanup of the test harness.
jiceatscion Nov 30, 2023
d559086
router: added the remaining 3 newbenchmark traffic types cases.
jiceatscion Dec 1, 2023
f24dc46
router: Actually enable the new test cases. Somehow I hadn't submitte…
jiceatscion Dec 1, 2023
4166d27
router: Move brload into its own subdirectory.
jiceatscion Dec 4, 2023
a9ab27e
router: make it more obvious how we construct public IPs.
jiceatscion Dec 4, 2023
7b25bf4
router: unexport interfaceName. It's used only locally.
jiceatscion Dec 4, 2023
9cf8262
router: Reviwer's suggestions
jiceatscion Dec 4, 2023
21f6013
router: change go version dependency to 1.21 (so the linter doesn't c…
jiceatscion Dec 5, 2023
f3e7e89
router: remove type conversions around use of max().
jiceatscion Dec 6, 2023
d6b4764
router: update CI's expectations of the newbenchmark results.
jiceatscion Dec 6, 2023
e2265ab
Merge branch 'master' into router_newbenchmark
matzf Dec 7, 2023
149076c
Merge branch 'master' into router_newbenchmark
matzf Dec 7, 2023
48d285f
Merge branch 'master' into router_newbenchmark
matzf Dec 7, 2023
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
2 changes: 1 addition & 1 deletion acceptance/common/docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ def assert_no_networks(writer=None):
writer.write("Docker networking assertions are OFF\n")
return

allowed_nets = ['bridge', 'host', 'none']
allowed_nets = ['bridge', 'host', 'none', 'benchmark']
unexpected_nets = []
for net in _get_networks():
if net.name not in allowed_nets:
Expand Down
2 changes: 1 addition & 1 deletion acceptance/router_benchmark/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ def _run(self):
"-name", "router_benchmark",
"-cmd", "./bin/end2endblast",
"-attempts", 1500000,
"-timeout", "120s", # Timeout is for all attempts together
"-timeout", "180s", # Timeout is for all attempts together
"-parallelism", 100,
"-subset", "noncore#core#remoteISD"
].run_tee()
Expand Down
28 changes: 28 additions & 0 deletions acceptance/router_newbenchmark/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
load("//acceptance/common:raw.bzl", "raw_test")

exports_files([
"conf",
"test.py",
])

args = [
"--executable",
"brload:$(location //acceptance/router_newbenchmark/brload:brload)",
"--container-loader=posix-router:latest#$(location //docker:posix_router)",
]

data = [
":conf",
"//docker:posix_router",
"//acceptance/router_newbenchmark/brload:brload",
]

raw_test(
name = "test",
src = "test.py",
args = args,
data = data,
homedir = "$(rootpath //docker:posix_router)",
# This test uses sudo and accesses /var/run/netns.
local = True,
)
27 changes: 27 additions & 0 deletions acceptance/router_newbenchmark/brload/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
load("//tools/lint:go.bzl", "go_library")
load("//:scion.bzl", "scion_go_binary")

go_library(
name = "go_default_library",
srcs = ["main.go"],
importpath = "github.com/scionproto/scion/acceptance/router_newbenchmark/brload",
visibility = ["//visibility:private"],
deps = [
"//acceptance/router_newbenchmark/cases:go_default_library",
"//pkg/log:go_default_library",
"//pkg/private/serrors:go_default_library",
"//pkg/scrypto:go_default_library",
"//pkg/slayers:go_default_library",
"//private/keyconf:go_default_library",
"@com_github_google_gopacket//:go_default_library",
"@com_github_google_gopacket//afpacket:go_default_library",
"@com_github_google_gopacket//layers:go_default_library",
"@com_github_spf13_cobra//:go_default_library",
],
)

scion_go_binary(
name = "brload",
embed = [":go_default_library"],
visibility = ["//visibility:public"],
)
316 changes: 316 additions & 0 deletions acceptance/router_newbenchmark/brload/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,316 @@
// Copyright 2023 SCION Association
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
"errors"
"fmt"
"hash"
"net"
"os"
"path/filepath"
"reflect"
"strings"
"time"

"github.com/google/gopacket"
"github.com/google/gopacket/afpacket"
"github.com/google/gopacket/layers"
"github.com/spf13/cobra"

"github.com/scionproto/scion/acceptance/router_newbenchmark/cases"
"github.com/scionproto/scion/pkg/log"
"github.com/scionproto/scion/pkg/private/serrors"
"github.com/scionproto/scion/pkg/scrypto"
"github.com/scionproto/scion/pkg/slayers"
"github.com/scionproto/scion/private/keyconf"
)

type Case func(payload string, mac hash.Hash, numDistinct int) (string, string, [][]byte)

type caseChoice string

func (c *caseChoice) String() string {
return string(*c)
}

func (c *caseChoice) Set(v string) error {
_, ok := allCases[v]
if !ok {
return errors.New("No such case")
}
*c = caseChoice(v)
return nil
}

func (c *caseChoice) Type() string {
return "string enum"
}

func (c *caseChoice) Allowed() string {
return fmt.Sprintf("One of: %v", reflect.ValueOf(allCases).MapKeys())
}

var (
allCases = map[string]Case{
"in": cases.In,
"out": cases.Out,
"in_transit": cases.InTransit,
"out_transit": cases.OutTransit,
"br_transit": cases.BrTransit,
}
logConsole string
dir string
numPackets int
numStreams int
caseToRun caseChoice
interfaces []string
)

func main() {
rootCmd := &cobra.Command{
Use: "brload",
Short: "Generates traffic into a specific router of a specific topology",
}
intfCmd := &cobra.Command{
Use: "show-interfaces",
Short: "Provides a terse list of the interfaces that this test requires",
Run: func(cmd *cobra.Command, args []string) {
os.Exit(showInterfaces(cmd))
},
}
runCmd := &cobra.Command{
Use: "run",
Short: "Executes the test",
Run: func(cmd *cobra.Command, args []string) {
os.Exit(run(cmd))
},
}
runCmd.Flags().IntVar(&numPackets, "num-packets", 10, "Number of packets to send")
runCmd.Flags().IntVar(&numStreams, "num-streams", 4,
"Number of independent streams (flowID) to use")
runCmd.Flags().StringVar(&logConsole, "log.console", "error",
"Console logging level: debug|info|error|etc.")
runCmd.Flags().StringVar(&dir, "artifacts", "", "Artifacts directory")
runCmd.Flags().Var(&caseToRun, "case", "Case to run. "+caseToRun.Allowed())
runCmd.Flags().StringArrayVar(&interfaces, "interface", []string{},
`label=host_interface,mac,peer_mac where:
host_interface: use this to exchange traffic with interface <label>
mac: the mac address of interface <label>
peer_mac: the mac address of <host_interface>`)
runCmd.MarkFlagRequired("case")
runCmd.MarkFlagRequired("interface")

rootCmd.AddCommand(intfCmd)
rootCmd.AddCommand(runCmd)
rootCmd.CompletionOptions.HiddenDefaultCmd = true

if rootCmd.Execute() != nil {
os.Exit(1)
}
os.Exit(0)
}

func showInterfaces(cmd *cobra.Command) int {
fmt.Println(cases.ListInterfaces())
return 0
}

func run(cmd *cobra.Command) int {
logCfg := log.Config{Console: log.ConsoleConfig{Level: logConsole}}
if err := log.Setup(logCfg); err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
return 1
}
defer log.HandlePanic()

caseFunc := allCases[string(caseToRun)] // key already checked.

artifactsDir := dir
if v := os.Getenv("TEST_ARTIFACTS_DIR"); v != "" {
artifactsDir = v
}

if artifactsDir == "" {
log.Error("Artifacts directory not configured")
return 1
}

hfMAC, err := loadKey(artifactsDir)
if err != nil {
log.Error("Loading keys failed", "err", err)
return 1
}

cases.InitInterfaces(interfaces)
handles, err := openDevices()
if err != nil {
log.Error("Loading devices failed", "err", err)
return 1
}

registerScionPorts()

log.Info("BRLoad acceptance tests:")

payloadString := "actualpayloadbytes"
caseDevIn, caseDevOut, rawPkts := caseFunc(payloadString, hfMAC, numStreams)

writePktTo, ok := handles[caseDevIn]
if !ok {
log.Error("device not found", "device", caseDevIn)
return 1
}

readPktFrom, ok := handles[caseDevOut]
if !ok {
log.Error("device not found", "device", caseDevOut)
return 1
}

// Try and pick-up one packet and check the payload. If that works, we're content
// that this test works.
packetSource := gopacket.NewPacketSource(readPktFrom, layers.LinkTypeEthernet)
packetChan := packetSource.Packets()
listenerChan := make(chan int)

go func() {
defer log.HandlePanic()
defer close(listenerChan)
listenerChan <- receivePackets(packetChan, payloadString)
}()

// We started everything that could be started. So the best window for perf mertics
// opens somewhere around now.
metricsBegin := time.Now().Unix()
for i := 0; i < numPackets; i++ {
if err := writePktTo.WritePacketData(rawPkts[i%numStreams]); err != nil {
log.Error("writing input packet", "case", string(caseToRun), "error", err)
return 1
}
}
metricsEnd := time.Now().Unix()
// The test harness looks for this output.
fmt.Printf("metricsBegin: %d metricsEnd: %d\n", metricsBegin, metricsEnd)

// Get the results from the packet listener.
// Give it one second as in very short tests (<1M pkts) we get here before the first packet.
outcome := 0
timeout := time.After(1 * time.Second)
for outcome == 0 {
select {
case outcome = <-listenerChan:
if outcome == 0 {
log.Error("Listener never saw a valid packet being forwarded")
return 1
}
case <-timeout:
// If our listener is still stuck there, unstick it. Closing the device doesn't cause
// the packet channel to close (presumably a bug). Close the channel ourselves. After
// this, the next loop is guaranteed an outcome.
close(packetChan)
}
}

fmt.Printf("Listener results: %d\n", outcome)
return 0
}

// receivePkts consume some or all (at least one if it arrives) of the packets
// arriving on the given handle and checks that they contain the given payload.
// The number of consumed packets is returned.
// Currently we are content with receiving a single correct packet and we terminate after
// that.
func receivePackets(packetChan chan gopacket.Packet, payload string) int {
numRcv := 0

for {
got, ok := <-packetChan
if !ok {
// No more packets
log.Info("No more Packets")
return numRcv
}
if err := got.ErrorLayer(); err != nil {
log.Error("error decoding packet", "err", err)
continue
}
layer := got.Layer(gopacket.LayerTypePayload)
if layer == nil {
log.Error("error fetching packet payload: no PayLoad")
continue
}
if string(layer.LayerContents()) == payload {
// To return the count of all packets received, just remove the "return" below.
// Return will occur once packetChan closes (which happens after a short timeout at
// the end of the test.
numRcv++
return numRcv
}
}
}

// initDevices inventories the available network interfaces, picks the ones that a case may inject
// traffic into, and associates them with a AF Packet interface. It returns the packet interfaces
// corresponding to each network interface.
func openDevices() (map[string]*afpacket.TPacket, error) {
devs, err := net.Interfaces()
if err != nil {
return nil, serrors.WrapStr("listing network interfaces", err)
}

handles := make(map[string]*afpacket.TPacket)

for _, dev := range devs {
if !strings.HasPrefix(dev.Name, "veth_") || !strings.HasSuffix(dev.Name, "_host") {
continue
}
handle, err := afpacket.NewTPacket(afpacket.OptInterface(dev.Name))
if err != nil {
return nil, serrors.WrapStr("creating TPacket", err)
}
handles[dev.Name] = handle
}

return handles, nil
}

// loadKey loads the keys that the router under test uses to sign hop fields.
func loadKey(artifactsDir string) (hash.Hash, error) {
keysDir := filepath.Join(artifactsDir, "conf", "keys")
mk, err := keyconf.LoadMaster(keysDir)
if err != nil {
return nil, err
}
macGen, err := scrypto.HFMacFactory(mk.Key0)
if err != nil {
return nil, err
}
return macGen(), nil
}

// registerScionPorts registers the following UDP ports in gopacket such as SCION is the
// next layer. In other words, map the following ports to expect SCION as the payload.
func registerScionPorts() {
for i := 30041; i < 30043; i++ {
layers.RegisterUDPPortLayerType(layers.UDPPort(i), slayers.LayerTypeSCION)
}
for i := 30000; i < 30010; i++ {
layers.RegisterUDPPortLayerType(layers.UDPPort(i), slayers.LayerTypeSCION)
}
for i := 50000; i < 50010; i++ {
layers.RegisterUDPPortLayerType(layers.UDPPort(i), slayers.LayerTypeSCION)
}
}
Loading
Loading