github.com/gopacket/gopacket@v1.1.0/pcap/gopacket_benchmark/benchmark.go (about)

     1  // Copyright 2012 Google, Inc. All rights reserved.
     2  //
     3  // Use of this source code is governed by a BSD-style license
     4  // that can be found in the LICENSE file in the root of the source
     5  // tree.
     6  
     7  // This benchmark reads in file <tempdir>/gopacket_benchmark.pcap and measures
     8  // the time it takes to decode all packets from that file.  If the file doesn't
     9  // exist, it's pulled down from a publicly available location.  However, you can
    10  // feel free to substitute your own file at that location, in which case the
    11  // benchmark will run on your own data.
    12  //
    13  // It's also useful for figuring out which packets may be causing errors.  Pass
    14  // in the --printErrors flag, and it'll print out error layers for each packet
    15  // that has them.  This includes any packets that it's just unable to decode,
    16  // which is a great way to find new protocols to decode, and get test packets to
    17  // write tests for them.
    18  package main
    19  
    20  import (
    21  	"compress/gzip"
    22  	"encoding/hex"
    23  	"flag"
    24  	"fmt"
    25  	"io"
    26  	"io/ioutil"
    27  	"net/http"
    28  	"os"
    29  	"runtime"
    30  	"runtime/pprof"
    31  	"time"
    32  
    33  	"github.com/gopacket/gopacket"
    34  	"github.com/gopacket/gopacket/layers"
    35  	"github.com/gopacket/gopacket/pcap"
    36  	"github.com/gopacket/gopacket/tcpassembly"
    37  )
    38  
    39  var decodeLazy *bool = flag.Bool("lazy", false, "If true, use lazy decoding")
    40  var decodeNoCopy *bool = flag.Bool("nocopy", true, "If true, avoid an extra copy when decoding packets")
    41  var printErrors *bool = flag.Bool("printErrors", false, "If true, check for and print error layers.")
    42  var printLayers *bool = flag.Bool("printLayers", false, "If true, print out the layers of each packet")
    43  var repeat *int = flag.Int("repeat", 5, "Read over the file N times")
    44  var cpuProfile *string = flag.String("cpuprofile", "", "If set, write CPU profile to filename")
    45  var url *string = flag.String("url", "http://www.ll.mit.edu/mission/communications/cyber/CSTcorpora/ideval/data/1999/training/week1/tuesday/inside.tcpdump.gz", "URL to gzip'd pcap file")
    46  
    47  type BufferPacketSource struct {
    48  	index int
    49  	data  [][]byte
    50  	ci    []gopacket.CaptureInfo
    51  }
    52  
    53  func NewBufferPacketSource(p gopacket.PacketDataSource) *BufferPacketSource {
    54  	start := time.Now()
    55  	b := &BufferPacketSource{}
    56  	for {
    57  		data, ci, err := p.ReadPacketData()
    58  		if err == io.EOF {
    59  			break
    60  		}
    61  		b.data = append(b.data, data)
    62  		b.ci = append(b.ci, ci)
    63  	}
    64  	duration := time.Since(start)
    65  	fmt.Printf("Reading packet data into memory: %d packets in %v, %v per packet\n", len(b.data), duration, duration/time.Duration(len(b.data)))
    66  	return b
    67  }
    68  
    69  func (b *BufferPacketSource) ReadPacketData() (data []byte, ci gopacket.CaptureInfo, err error) {
    70  	if b.index >= len(b.data) {
    71  		err = io.EOF
    72  		return
    73  	}
    74  	data = b.data[b.index]
    75  	ci = b.ci[b.index]
    76  	b.index++
    77  	return
    78  }
    79  
    80  func (b *BufferPacketSource) Reset() {
    81  	runtime.GC()
    82  	b.index = 0
    83  }
    84  
    85  func main() {
    86  	flag.Parse()
    87  	filename := os.TempDir() + string(os.PathSeparator) + "gopacket_benchmark.pcap"
    88  	if _, err := os.Stat(filename); err != nil {
    89  		// This URL points to a publicly available packet data set from a DARPA
    90  		// intrusion detection evaluation.  See
    91  		// http://www.ll.mit.edu/mission/communications/cyber/CSTcorpora/ideval/data/1999/training/week1/index.html
    92  		// for more details.
    93  		fmt.Println("Local pcap file", filename, "doesn't exist, reading from", *url)
    94  		if resp, err := http.Get(*url); err != nil {
    95  			panic(err)
    96  		} else if out, err := os.Create(filename); err != nil {
    97  			panic(err)
    98  		} else if gz, err := gzip.NewReader(resp.Body); err != nil {
    99  			panic(err)
   100  		} else if n, err := io.Copy(out, gz); err != nil {
   101  			panic(err)
   102  		} else if err := gz.Close(); err != nil {
   103  			panic(err)
   104  		} else if err := out.Close(); err != nil {
   105  			panic(err)
   106  		} else {
   107  			fmt.Println("Successfully read", n, "bytes from url, unzipped to local storage")
   108  		}
   109  	}
   110  	fmt.Println("Reading file once through to hopefully cache most of it")
   111  	if f, err := os.Open(filename); err != nil {
   112  		panic(err)
   113  	} else if n, err := io.Copy(ioutil.Discard, f); err != nil {
   114  		panic(err)
   115  	} else if err := f.Close(); err != nil {
   116  		panic(err)
   117  	} else {
   118  		fmt.Println("Read in file", filename, ", total of", n, "bytes")
   119  	}
   120  	if *cpuProfile != "" {
   121  		if cpu, err := os.Create(*cpuProfile); err != nil {
   122  			panic(err)
   123  		} else if err := pprof.StartCPUProfile(cpu); err != nil {
   124  			panic(err)
   125  		} else {
   126  			defer func() {
   127  				pprof.StopCPUProfile()
   128  				cpu.Close()
   129  			}()
   130  		}
   131  	}
   132  	var packetDataSource *BufferPacketSource
   133  	var packetSource *gopacket.PacketSource
   134  	fmt.Printf("Opening file %q for read\n", filename)
   135  	if h, err := pcap.OpenOffline(filename); err != nil {
   136  		panic(err)
   137  	} else {
   138  		fmt.Println("Reading all packets into memory with BufferPacketSource.")
   139  		start := time.Now()
   140  		packetDataSource = NewBufferPacketSource(h)
   141  		duration := time.Since(start)
   142  		fmt.Printf("Time to read packet data into memory from file: %v\n", duration)
   143  		packetSource = gopacket.NewPacketSource(packetDataSource, h.LinkType())
   144  		packetSource.DecodeOptions.Lazy = *decodeLazy
   145  		packetSource.DecodeOptions.NoCopy = *decodeNoCopy
   146  	}
   147  	fmt.Println()
   148  	for i := 0; i < *repeat; i++ {
   149  		packetDataSource.Reset()
   150  		fmt.Printf("Benchmarking decode %d/%d\n", i+1, *repeat)
   151  		benchmarkPacketDecode(packetSource)
   152  	}
   153  	fmt.Println()
   154  	for i := 0; i < *repeat; i++ {
   155  		packetDataSource.Reset()
   156  		fmt.Printf("Benchmarking decoding layer parser %d/%d\n", i+1, *repeat)
   157  		benchmarkLayerDecode(packetDataSource, false)
   158  	}
   159  	fmt.Println()
   160  	for i := 0; i < *repeat; i++ {
   161  		packetDataSource.Reset()
   162  		fmt.Printf("Benchmarking decoding layer parser with assembly %d/%d\n", i+1, *repeat)
   163  		benchmarkLayerDecode(packetDataSource, true)
   164  	}
   165  }
   166  
   167  func benchmarkPacketDecode(packetSource *gopacket.PacketSource) {
   168  	count, errors := 0, 0
   169  	start := time.Now()
   170  	for packet, err := packetSource.NextPacket(); err != io.EOF; packet, err = packetSource.NextPacket() {
   171  		if err != nil {
   172  			fmt.Println("Error reading in packet:", err)
   173  			continue
   174  		}
   175  		count++
   176  		var hasError bool
   177  		if *printErrors && packet.ErrorLayer() != nil {
   178  			fmt.Println("\n\n\nError decoding packet:", packet.ErrorLayer().Error())
   179  			fmt.Println(hex.Dump(packet.Data()))
   180  			fmt.Printf("%#v\n", packet.Data())
   181  			errors++
   182  			hasError = true
   183  		}
   184  		if *printLayers || hasError {
   185  			fmt.Printf("\n=== PACKET %d ===\n", count)
   186  			for _, l := range packet.Layers() {
   187  				fmt.Printf("--- LAYER %v ---\n%#v\n\n", l.LayerType(), l)
   188  			}
   189  			fmt.Println()
   190  		}
   191  	}
   192  	duration := time.Since(start)
   193  	fmt.Printf("\tRead in %v packets in %v, %v per packet\n", count, duration, duration/time.Duration(count))
   194  	if *printErrors {
   195  		fmt.Printf("%v errors, successfully decoded %.02f%%\n", errors, float64(count-errors)*100.0/float64(count))
   196  	}
   197  }
   198  
   199  type streamFactory struct {
   200  }
   201  
   202  func (s *streamFactory) New(netFlow, tcpFlow gopacket.Flow) tcpassembly.Stream {
   203  	return s
   204  }
   205  func (s *streamFactory) Reassembled([]tcpassembly.Reassembly) {
   206  }
   207  func (s *streamFactory) ReassemblyComplete() {
   208  }
   209  
   210  func benchmarkLayerDecode(source *BufferPacketSource, assemble bool) {
   211  	var tcp layers.TCP
   212  	var ip layers.IPv4
   213  	var eth layers.Ethernet
   214  	var udp layers.UDP
   215  	var icmp layers.ICMPv4
   216  	var payload gopacket.Payload
   217  	parser := gopacket.NewDecodingLayerParser(
   218  		layers.LayerTypeEthernet,
   219  		&eth, &ip, &icmp, &tcp, &udp, &payload)
   220  	pool := tcpassembly.NewStreamPool(&streamFactory{})
   221  	assembler := tcpassembly.NewAssembler(pool)
   222  	var decoded []gopacket.LayerType
   223  	start := time.Now()
   224  	packets, decodedlayers, assembled := 0, 0, 0
   225  	for {
   226  		packets++
   227  		data, ci, err := source.ReadPacketData()
   228  		if err == io.EOF {
   229  			break
   230  		} else if err != nil {
   231  			fmt.Println("Error reading packet: ", err)
   232  			continue
   233  		}
   234  		err = parser.DecodeLayers(data, &decoded)
   235  		for _, typ := range decoded {
   236  			decodedlayers++
   237  			if typ == layers.LayerTypeTCP && assemble {
   238  				assembled++
   239  				assembler.AssembleWithTimestamp(ip.NetworkFlow(), &tcp, ci.Timestamp)
   240  			}
   241  		}
   242  	}
   243  	if assemble {
   244  		assembler.FlushAll()
   245  	}
   246  	duration := time.Since(start)
   247  	fmt.Printf("\tRead in %d packets in %v, decoded %v layers, assembled %v packets: %v per packet\n", packets, duration, decodedlayers, assembled, duration/time.Duration(packets))
   248  }