~humaid/ns2-trace-go

6b3a62c81c4b33c71f97901b386521a9954ab921 — Humaid AlQassimi 1 year, 9 months ago
Initial commit
A  => go.mod +12 -0
@@ 1,12 @@
module git.sr.ht/~humaid/ns2-trace-go

go 1.13

require (
	github.com/blend/go-sdk v2.0.0+incompatible // indirect
	github.com/go-macaron/binding v1.0.1
	github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
	github.com/wcharczuk/go-chart v2.0.1+incompatible
	golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 // indirect
	gopkg.in/macaron.v1 v1.3.4
)

A  => go.sum +49 -0
@@ 1,49 @@
github.com/blend/go-sdk v2.0.0+incompatible h1:FL9X/of4ZYO5D2JJNI4vHrbXPfuSDbUa7h8JP9+E92w=
github.com/blend/go-sdk v2.0.0+incompatible/go.mod h1:3GUb0YsHFNTJ6hsJTpzdmCUl05o8HisKjx5OAlzYKdw=
github.com/go-macaron/binding v1.0.1 h1:4LASxd4EKsESZ6ZMyzNVX+TM4Yuex4bTHYyz/PQjsRA=
github.com/go-macaron/binding v1.0.1/go.mod h1:AG8Z6qkQM8s47aUDJOco/SNwJ8Czif2hMm7rc0abDog=
github.com/go-macaron/inject v0.0.0-20160627170012-d8a0b8677191 h1:NjHlg70DuOkcAMqgt0+XA+NHwtu66MkTVVgR4fFWbcI=
github.com/go-macaron/inject v0.0.0-20160627170012-d8a0b8677191/go.mod h1:VFI2o2q9kYsC4o7VP1HrEVosiZZTd+MVT3YZx4gqvJw=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4=
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w=
github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8=
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e h1:GSGeB9EAKY2spCABz6xOX5DbxZEXolK+nBSvmsQwRjM=
github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
github.com/wcharczuk/go-chart v2.0.1+incompatible h1:0pz39ZAycJFF7ju/1mepnk26RLVLBCWz1STcD3doU0A=
github.com/wcharczuk/go-chart v2.0.1+incompatible/go.mod h1:PF5tmL4EIx/7Wf+hEkpCqYi5He4u90sw+0+6FhrryuE=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190802220118-1d1727260058/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
golang.org/x/tools v0.0.0-20190805222050-c5a2fd39b72a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
gopkg.in/ini.v1 v1.46.0 h1:VeDZbLYGaupuvIrsYCEOe/L/2Pcs5n7hdO1ZTjporag=
gopkg.in/ini.v1 v1.46.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/macaron.v1 v1.3.4 h1:HvIscOwxhFhx3swWM/979wh2QMYyuXrNmrF9l+j3HZs=
gopkg.in/macaron.v1 v1.3.4/go.mod h1:/RoHTdC8ALpyJ3+QR36mKjwnT1F1dyYtsGM9Ate6ZFI=

A  => main.go +155 -0
@@ 1,155 @@
package main

import (
	"bufio"
	"bytes"
	"fmt"
	"github.com/go-macaron/binding"
	chart "github.com/wcharczuk/go-chart"
	"gopkg.in/macaron.v1"
	"html/template"
	"math/rand"
	"mime/multipart"
	"sort"
	"strconv"
)

var Analysises map[string]Analysis

type Analysis struct {
	Traces      []*TraceItem
	JitterStats []*JitterStat
	Stats       TraceStats
}

func main() {
	Analysises = make(map[string]Analysis)
	m := macaron.Classic()
	m.Use(macaron.Renderer())

	m.Get("/", func(ctx *macaron.Context) {
		ctx.HTML(200, "index")
	})

	m.Post("/", binding.MultipartForm(SubmitForm{}), func(ctx *macaron.Context,
		form SubmitForm, errs binding.Errors) {
		if len(errs) > 0 {
			ctx.PlainText(400, []byte(fmt.Sprintf("Form binding error: %s", errs)))
			return
		}
		f, err := form.File.Open()
		if err != nil {
			ctx.PlainText(400, []byte("File uploaded cannot be opened"))
			return
		}
		defer f.Close()
		buf := bufio.NewScanner(f)

		// TODO make sure this ID does not already exist
		analysisID := fmt.Sprint(rand.Intn(899999) + 100000)
		var trace []*TraceItem
		trace, err = GetTracesFromBuffer(buf)
		if err != nil {
			ctx.PlainText(400, []byte(fmt.Sprintf("Failed to parse file: %s", err)))
			return
		}
		Analysises[analysisID] = Analysis{
			Traces:      trace,
			JitterStats: CalculateJitters(trace),
			Stats:       CalculateStats(trace),
		}
		ctx.Redirect(fmt.Sprintf("/%s", analysisID))
	})
	m.Group("/:id", func() {
		m.Get("/", func(ctx *macaron.Context) {
			analysis, ok := Analysises[ctx.Params("id")]
			if !ok {
				ctx.PlainText(404, []byte("Analysis results does not exist"))
				return
			}
			ctx.Data["ID"] = ctx.Params("id")
			ctx.Data["Analysis"] = analysis
			ctx.HTML(200, "analysis")
		})
		m.Get("/:from/:to/:type", func(ctx *macaron.Context) {
			analysis, ok := Analysises[ctx.Params("id")]
			if !ok {
				ctx.PlainText(404, []byte("Analysis results does not exist"))
				return
			}
			var from, to int
			var pType string = ctx.Params("type")
			var err error

			from, err = strconv.Atoi(ctx.Params("from"))
			if err != nil {
				ctx.PlainText(400, []byte("Malformed request parameters"))
				return
			}
			to, err = strconv.Atoi(ctx.Params("to"))
			if err != nil {
				ctx.PlainText(400, []byte("Malformed request parameters"))
				return
			}

			var st *JitterStat = nil
			for _, stat := range analysis.JitterStats {
				if stat.FromNode == from && stat.ToNode == to && stat.PacketType == pType {
					st = stat
				}
			}
			if st == nil {
				ctx.PlainText(404, []byte("The jitter stat requested does not exist"))
				return
			}

			ctx.Data["ID"] = ctx.Params("id")
			ctx.Data["Stat"] = st

			var seqValues, jitterValues []float64

			for seq := range st.Jitter {
				seqValues = append(seqValues, float64(seq))
			}
			sort.Float64s(seqValues)

			for v := range seqValues {
				jitterValues = append(jitterValues, st.Jitter[v])
				//fmt.Printf("(%d - %f) ", v, st.Jitter[v])
			}
			//fmt.Println(jitterValues)

			graph := chart.Chart{
				YAxis: chart.YAxis{
					Range: &chart.ContinuousRange{
						Min: -0.5,
						Max: 0.5,
					},
				},
				Series: []chart.Series{
					chart.ContinuousSeries{
						XValues: seqValues,
						YValues: jitterValues,
					},
				},
			}

			var imgBuf bytes.Buffer
			err = graph.Render(chart.SVG, &imgBuf)
			if err != nil {
				ctx.PlainText(500, []byte(fmt.Sprintf("Failed to generate jitter graph: %s", err)))
				return
			}

			ctx.Data["Graph"] = template.HTML(imgBuf.String())

			ctx.HTML(200, "jitter")
		})
	})
	m.Run()
}

// SubmitForm holds the POST submission form for uploading the trace file.
type SubmitForm struct {
	File *multipart.FileHeader `form:"file" binding:"Required"`
}

A  => public/main.css +55 -0
@@ 1,55 @@
body {
  font-family: source sans pro,lucidia grande,segoe ui,roboto,sans-serif;
  font-size: 1.4em;
  margin: 0;
}

p {
  margin: 10px 0;
}

.container {
  max-width: 960px;
  position: relative;
  margin: 0 auto;
}

a:focus {
  outline: 2.5pt solid #ffbf47;
}

.title {
  text-align: center;
  margin-top: 30px;
  margin-bottom: 30px;
  padding: 0 30px 0 30px
}

.btn {
  font-size: 1em;
  display: inline-block;
  background-color: #1368f4;
  color: #fff;
  border: 1px solid black;
  padding: 2px 5px;
  text-decoration: none;
}

.btn:hover {
  text-decoration: underline;
  outline: 2.5pt solid #ffbf47;
}

input[type=text], input[type=email], textarea {
  border-radius: 0px;
  border: 1px solid black;
  padding: 2px 5px;
}
table {
	width: 100%;
}
th, td {
  padding: 15px;
  text-align: left;
  border-bottom: 1px solid #ddd;
}

A  => public/normalize-8.0.1.min.css +2 -0
@@ 1,2 @@
/*!normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css*/html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:#0000}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}


A  => templates/analysis.tmpl +35 -0
@@ 1,35 @@
{{ template "base/head" .}}
<div class="container">
	<h1 class="title">NS2 Trace File Analysis Result</h1>
	<p><a class="btn" href="/">&#9664; Try another file</a></p>
	<h2>Statistics</h2>
	<p>Total number of received packets: {{.Analysis.Stats.ReceivedPackets}}</p>
	<p>Total number of dropped packets: {{.Analysis.Stats.DroppedPackets}}</p>
	<p>Average number of hops: {{.Analysis.Stats.AvgHops}}</p>
	<p>Total number of active nodes: {{.Analysis.Stats.ActiveNodes}}</p>
	<p>Total bandwidth used: {{.Analysis.Stats.TotalBandwidth}}</p>
	<p>Total network time: {{.Analysis.Stats.NetworkTime}}</p>
	<p>Total trace file entries: {{.Analysis.Stats.TotalEntries}}</p>
	<h2>Jitter</h2>
	<table>
	<tr>
	<th>From Node</th>
	<th>To Node</th>
	<th>Packet Type</th>
	<th>Actions</th>
	</tr>
	{{range $jitter := .Analysis.JitterStats}}
	<tr>
<td>{{$jitter.FromNode}}</td>
<td>{{$jitter.ToNode}}</td>
<td>{{$jitter.PacketType}}</td>
<td><a class="btn"
href="{{$.ID}}/{{$jitter.FromNode}}/{{$jitter.ToNode}}/{{$jitter.PacketType}}">View
jitter</a></td>
</tr>
	{{end}}
	</table>
<br>
<br>
</div>
{{ template "base/footer" .}}

A  => templates/base/footer.tmpl +5 -0
@@ 1,5 @@
   <center><small>NS 2 Trace File Analyser is written by Humaid
   AlQassimi</small></center>
	</body>
</html>


A  => templates/base/head.tmpl +11 -0
@@ 1,11 @@
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <link rel="stylesheet" href="/normalize-8.0.1.min.css" />
        <link rel="stylesheet" href="/main.css" />
        <title>NS2 Trace File Analyser</title>
        <link rel="icon" href="data:;base64," />
    </head>
    <body>

A  => templates/index.tmpl +17 -0
@@ 1,17 @@
{{ template "base/head" .}}
<div class="container">
	<h1 class="title">NS Trace File Analyser</h1>

	<p>This website allows you to analyse trace files created by <a
	href="https://www.isi.edu/nsnam/ns/" target="_blank">NS2</a>.</p>

	<p><b>Important note:</b> Make sure you use 'trace-all' for generating your trace file,
	otherwise it may not be able to analyse your data properly.</p>

<h2>Upload your trace file</h2>
	<form method="post" enctype="multipart/form-data">
		<p>Trace File: <input type="file" name="file" /></p>
		<button type="submit" class="btn">Analyse</button>
	</form>
</div>
{{ template "base/footer" .}}

A  => templates/jitter.tmpl +10 -0
@@ 1,10 @@
{{ template "base/head" .}}
<div class="container">
	<h1 class="title">NS2 Trace File Analysis Result ({{.Analysis.Title}})</h1>
	<h2 class="title">Jitter graph for {{.Stat.FromNode}}->{{.Stat.ToNode}}
	({{.Stat.PacketType}})</h2>
	<p><a class="btn" href="/{{.ID}}">&#9664; Go back</a></p>
	{{.Graph}}

</div>
{{ template "base/footer" .}}

A  => trace.go +260 -0
@@ 1,260 @@
package main

import (
	"bufio"
	"strconv"
	"strings"
)

// EventType represents the event type of a trace entry.
type EventType int

const (
	REC  = iota // event: receive
	ENQ         // event: enqueue
	DEQ         // event: dequeue
	DROP        // event: drop
)

// TraceFlag represents a trace item flag.
type TraceFlag uint8

const (
	NONE = 0x0000
	ECN  = 0x0001 // Explicit Congestion Notification echo is enabled.
	PRI  = 0x0002 // Priority in IP header is enabled.
	CONA = 0x0004 // Congestion action.
	TCPF = 0x0008 // TCP fast start is used.
	ECNO = 0x0010 // Explicit Congestion Notification is on.
)

// TraceItem represents a trace file entry.
type TraceItem struct {
	Event          EventType
	Time           float64
	FromNode       int
	ToNode         int
	PacketType     string
	PacketSize     int
	Flags          int
	FlowID         int
	SourceAddr     Address
	DestAddr       Address
	SequenceNum    int
	UniquePacketID int
}

// Address holds the NS2 pseudo-address.
type Address struct {
	Address int
	Port    int
}

type JitterStat struct {
	FromNode   int
	ToNode     int
	PacketType string
	oldTime    float64         // 1
	oldSeq     float64         // 2
	Jitter     map[int]float64 // (sequence num, jitter)
}

type TraceStats struct {
	TotalEntries    int
	ReceivedPackets int
	DroppedPackets  int
	AvgHops         float32
	ActiveNodes     int
	TotalBandwidth  int
	NetworkTime     float64
}

func CalculateStats(traces []*TraceItem) (stat TraceStats) {
	var enq int
	var hop float32
	var nodes []int
	for _, trace := range traces {
		stat.TotalEntries++
		switch trace.Event {
		case REC:
			stat.ReceivedPackets++
		case DROP:
			stat.DroppedPackets++
		case ENQ:
			enq++
		case DEQ:
			// This allows us to accurately calculate hops
			if enq > 0 {
				enq--
				hop++
			}
		default:
			continue
		}
		if !hasNode(nodes, trace.FromNode) {
			nodes = append(nodes, trace.FromNode)
			stat.ActiveNodes++
		}
		if !hasNode(nodes, trace.ToNode) {
			nodes = append(nodes, trace.ToNode)
			stat.ActiveNodes++
		}

		stat.TotalBandwidth += trace.PacketSize
		if trace.Time > stat.NetworkTime {
			stat.NetworkTime = trace.Time
		}
	}
	stat.AvgHops = (hop / float32(stat.ReceivedPackets))
	return
}

func hasNode(nodes []int, node int) bool {
	for i := range nodes {
		if i == node {
			return true
		}
	}
	return false
}

func CalculateJitters(traces []*TraceItem) (s []*JitterStat) {
	for _, trace := range traces {
		if trace.Event != REC {
			continue
		}
		var js *JitterStat = nil
		index := -1
		for i, stat := range s {
			if stat.FromNode == trace.FromNode && stat.ToNode == trace.ToNode &&
				stat.PacketType == trace.PacketType {
				js = stat
				index = i
			}
		}

		if js == nil {
			js = &JitterStat{
				FromNode:   trace.FromNode,
				ToNode:     trace.ToNode,
				PacketType: trace.PacketType,
				oldTime:    0,
				oldSeq:     0,
				Jitter:     make(map[int]float64),
			}
		}

		diff := float64(trace.SequenceNum) - js.oldSeq
		if diff == 0 {
			diff = 1
		}
		if diff > 0 {
			js.Jitter[trace.SequenceNum] = ((trace.Time - js.oldTime) / diff)
			js.oldTime = float64(trace.Time)
			js.oldSeq = float64(trace.SequenceNum)
		}
		if index != -1 {
			s = removeJitter(s, index)
		}

		s = append(s, js)
	}
	return
}

func removeJitter(slice []*JitterStat, s int) []*JitterStat {
	return append(slice[:s], slice[s+1:]...)
}

// GetTracesFromBuffer takes an file buffer and converts it into a list of
// TraceItem.
func GetTracesFromBuffer(scanner *bufio.Scanner) ([]*TraceItem, error) {
	var traces []*TraceItem
	for scanner.Scan() {
		parts := strings.Split(scanner.Text(), " ")
		if len(parts) == 12 {
			ti := new(TraceItem)

			// Event
			switch parts[0] {
			case "r":
				ti.Event = REC
			case "+":
				ti.Event = ENQ
			case "-":
				ti.Event = DEQ
			case "d":
				ti.Event = DROP
			}

			// Time
			time, err := strconv.ParseFloat(parts[1], 64)
			if err != nil {
				return nil, err
			}
			ti.Time = time

			// FromNode
			var i int
			i, err = strconv.Atoi(parts[2])
			if err != nil {
				return nil, err
			}
			ti.FromNode = i

			// ToNode
			i, err = strconv.Atoi(parts[3])
			if err != nil {
				return nil, err
			}
			ti.ToNode = i

			// Packet type
			ti.PacketType = parts[4]

			// Packet size
			i, err = strconv.Atoi(parts[5])
			if err != nil {
				return nil, err
			}
			ti.PacketSize = i

			// TODO flags

			// Flow ID
			i, err = strconv.Atoi(parts[7])
			if err != nil {
				return nil, err
			}
			ti.FlowID = i

			// Source Address
			source := strings.Split(parts[8], ".")
			s1, _ := strconv.Atoi(source[0])
			s2, _ := strconv.Atoi(source[1])
			ti.SourceAddr = Address{s1, s2}

			// Destination Address
			dest := strings.Split(parts[9], ".")
			d1, _ := strconv.Atoi(dest[0])
			d2, _ := strconv.Atoi(dest[1])
			ti.DestAddr = Address{d1, d2}

			// Sequence Number
			i, err = strconv.Atoi(parts[10])
			if err != nil {
				return nil, err
			}
			ti.SequenceNum = i

			i, err = strconv.Atoi(parts[11])
			if err != nil {
				return nil, err
			}
			ti.UniquePacketID = i
			traces = append(traces, ti)
		}
	}

	return traces, nil
}