Skip to content

Instantly share code, notes, and snippets.

@jimmidyson
Last active August 27, 2015 21:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jimmidyson/81448529975bc5466bb4 to your computer and use it in GitHub Desktop.
Save jimmidyson/81448529975bc5466bb4 to your computer and use it in GitHub Desktop.
# go test -bench=. -benchmem -benchtime=30s
testing: warning: no tests to run
PASS
BenchmarkSetNs 100000 464509 ns/op 14876 B/op 163 allocs/op
BenchmarkSysFs 100000 459523 ns/op 14735 B/op 157 allocs/op
ok github.com/jimmidyson/benchmark-setns 122.377s
package benchmarksetns
import (
"bufio"
"fmt"
"log"
"os/exec"
"regexp"
"runtime"
"strconv"
"strings"
"testing"
"github.com/fsouza/go-dockerclient"
"github.com/vishvananda/netns"
)
const (
image = "kubernetes/pause"
netStatsFile = "/proc/net/dev"
)
var (
client, _ = docker.NewClient("unix:///var/run/docker.sock")
ignoredDevicePrefixes = []string{"lo", "veth", "docker"}
re = regexp.MustCompile("[ ]*(.+):([ ]+[0-9]+){16}")
stats []interfaceStats
)
type interfaceStats struct {
// The name of the interface.
Name string `json:"name"`
// Cumulative count of bytes received.
RxBytes uint64 `json:"rx_bytes"`
// Cumulative count of packets received.
RxPackets uint64 `json:"rx_packets"`
// Cumulative count of receive errors encountered.
RxErrors uint64 `json:"rx_errors"`
// Cumulative count of packets dropped while receiving.
RxDropped uint64 `json:"rx_dropped"`
// Cumulative count of bytes transmitted.
TxBytes uint64 `json:"tx_bytes"`
// Cumulative count of packets transmitted.
TxPackets uint64 `json:"tx_packets"`
// Cumulative count of transmit errors encountered.
TxErrors uint64 `json:"tx_errors"`
// Cumulative count of packets dropped while transmitting.
TxDropped uint64 `json:"tx_dropped"`
}
func startContainer() *docker.Container {
img := docker.PullImageOptions{
Repository: "kubernetes/pause",
}
if err := client.PullImage(img, docker.AuthConfiguration{}); err != nil {
log.Fatal("Can't pull image:", err)
}
cfg := docker.Config{Image: "kubernetes/pause"}
createOpts := docker.CreateContainerOptions{Config: &cfg}
cntr, err := client.CreateContainer(createOpts)
if err != nil {
log.Fatal("Can't create container:", err)
}
if err := client.StartContainer(cntr.ID, &docker.HostConfig{}); err != nil {
log.Fatal("Can't start container:", err)
}
cntr, err = client.InspectContainer(cntr.ID)
if err != nil {
log.Fatal("Can't inspect container:", err)
}
return cntr
}
func removeContainer(cntr *docker.Container) {
removeOpts := docker.RemoveContainerOptions{
ID: cntr.ID,
Force: true,
}
err := client.RemoveContainer(removeOpts)
if err != nil {
log.Fatal("Can't remove container:", err)
}
}
func BenchmarkSetNs(b *testing.B) {
cntr := startContainer()
defer removeContainer(cntr)
stats = []interfaceStats{}
b.ResetTimer()
for n := 0; n < b.N; n++ {
stats = statsFromNs(cntr.State.Pid)
}
b.StopTimer()
}
func BenchmarkSysFs(b *testing.B) {
cntr := startContainer()
defer removeContainer(cntr)
stats = []interfaceStats{}
b.ResetTimer()
for n := 0; n < b.N; n++ {
stats = statsFromSysFs(cntr.State.Pid)
}
b.StopTimer()
}
func isIgnoredDevice(ifName string) bool {
for _, prefix := range ignoredDevicePrefixes {
if strings.HasPrefix(strings.ToLower(ifName), prefix) {
return true
}
}
return false
}
func scanInterfaceStats(file string) []interfaceStats {
var (
bkt uint64
)
// For some reason ioutil.ReadFile(netStatsFile) reads the file in
// the default netns when this code is invoked from docker.
// Executing "cat <netStatsFile>" works as expected.
data, err := exec.Command("cat", file).Output()
if err != nil {
log.Fatal("Can't cat net stats file:", err)
}
reader := strings.NewReader(string(data))
scanner := bufio.NewScanner(reader)
scanner.Split(bufio.ScanLines)
stats := []interfaceStats{}
for scanner.Scan() {
line := scanner.Text()
if re.MatchString(line) {
line = strings.Replace(line, ":", "", -1)
i := interfaceStats{}
_, err := fmt.Sscanf(line, "%s %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d",
&i.Name, &i.RxBytes, &i.RxPackets, &i.RxErrors, &i.RxDropped, &bkt, &bkt, &bkt,
&bkt, &i.TxBytes, &i.TxPackets, &i.TxErrors, &i.TxDropped, &bkt, &bkt, &bkt, &bkt)
if err != nil {
log.Fatal("Can't scan net stats:", err)
}
if !isIgnoredDevice(i.Name) {
stats = append(stats, i)
}
}
}
return stats
}
func statsFromNs(pid int) []interfaceStats {
// Lock the OS Thread so we only change the ns for this thread exclusively
runtime.LockOSThread()
defer runtime.UnlockOSThread()
// Save the current network namespace
origns, _ := netns.Get()
defer origns.Close()
// Switch to the pid netns
pidns, err := netns.GetFromPid(pid)
defer pidns.Close()
if err != nil {
log.Fatal("Can't get container pid ns", err)
}
netns.Set(pidns)
// Defer setting back to original ns
defer netns.Set(origns)
stats := scanInterfaceStats(netStatsFile)
return stats
}
func statsFromSysFs(pid int) []interfaceStats {
stats := scanInterfaceStats("/proc/" + strconv.Itoa(pid) + "/net/dev")
return stats
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment