Merge branch 'master' into vtrace
diff --git a/services/debug/debug/browse.go b/services/debug/debug/browse.go
index bf0c535..2826698 100644
--- a/services/debug/debug/browse.go
+++ b/services/debug/debug/browse.go
@@ -15,13 +15,16 @@
 	"os/exec"
 	"runtime"
 	"strings"
+	"time"
 
 	"v.io/v23"
+	"v.io/v23/uniqueid"
 	"v.io/v23/context"
 	"v.io/v23/naming"
 	"v.io/v23/security"
 	"v.io/v23/services/logreader"
 	"v.io/v23/services/stats"
+	svtrace "v.io/v23/services/vtrace"
 	"v.io/v23/vdl"
 	"v.io/v23/verror"
 	"v.io/v23/vtrace"
@@ -29,6 +32,7 @@
 	"v.io/x/ref/lib/signals"
 	"v.io/x/ref/lib/v23cmd"
 	"v.io/x/ref/services/internal/pproflib"
+	"encoding/hex"
 )
 
 func init() {
@@ -99,6 +103,8 @@
 	http.Handle("/glob", &globHandler{ctx})
 	http.Handle(browseProfilesPath, &profilesHandler{ctx})
 	http.Handle(browseProfilesPath+"/", &profilesHandler{ctx})
+	http.Handle("/vtraces", &allTracesHandler{ctx})
+	http.Handle("/vtrace", &vtraceHandler{ctx})
 	http.Handle("/favicon.ico", http.NotFoundHandler())
 	addr := flagBrowseAddr
 	if len(addr) == 0 {
@@ -477,6 +483,230 @@
 	pproflib.PprofProxy(h.ctx, browseProfilesPath, name).ServeHTTP(w, r)
 }
 
+type allTracesHandler struct { ctx *context.T }
+
+func (a *allTracesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	var (
+		server = r.FormValue("n")
+		name = naming.Join(server, "__debug", "vtrace")
+	)
+	if len(server) == 0 {
+		w.WriteHeader(http.StatusBadRequest)
+		fmt.Fprintf(w, "Must specify a server with the URL query parameter 'n'")
+		return
+	}
+	ctx, tracer := newTracer(a.ctx)
+	stub := svtrace.StoreClient(name)
+
+	fmt.Println("calling out to", name)
+	call, err := stub.AllTraces(ctx)
+	if err != nil {
+		w.WriteHeader(http.StatusInternalServerError)
+		fmt.Fprintf(w, "Failed to get all trace ids on %s: %v", name, err)
+		return
+	}
+	stream := call.RecvStream()
+	var traces []string
+	for stream.Advance() {
+		id := stream.Value().Id
+		fmt.Printf("Got back: %x", id)
+		traces = append(traces, hex.EncodeToString(id[:]))
+	}
+
+	data := struct {
+		Ids       []string
+		StreamError error
+		CallError error
+		ServerName string
+		CommandLine string
+		Vtrace      *Tracer
+	}{
+		Ids: traces,
+		StreamError: stream.Err(),
+		CallError: call.Finish(),
+		CommandLine: fmt.Sprintf("debug vtraces %q", name),
+		ServerName: server,
+		Vtrace: tracer,
+	}
+	executeTemplate(a.ctx, w, r, tmplBrowseAllTraces, data)
+	fmt.Println("Done with template")
+}
+
+type divTree struct {
+	// Start is a value from 0-100 which is the percentage of time into the parent span's duration
+	// that this span started.
+	Start int
+	// End is a value from 0-100 which is the percentage of time into the parent span's duration
+	// that this span ended.
+	Width int
+	// VerticalOffset is the number of pixels from the top of the parent div this div should appear.
+	VerticalOffset int
+	Name string
+	Annotations []annotation
+	Children []divTree
+}
+
+type annotation struct {
+	// Position is a value from 0-100 which is the percentage of time into the span's duration that this
+	// annotation occured.
+	Position int
+	Msg string
+}
+
+func convertToTree(n *vtrace.Node, parentStart time.Time, parentEnd time.Time, verticalPosition int) *divTree {
+	startTime := n.Span.Start
+	if startTime.IsZero() {
+		startTime = parentStart
+	}
+
+	endTime := n.Span.End
+	if endTime.IsZero() {
+		endTime = parentEnd
+	}
+
+	parentDuration := parentEnd.Sub(parentStart).Seconds()
+	fmt.Println("parent start is", parentStart, "start", startTime, "parentEnd", parentEnd, "end", endTime)
+	start := int(100 * startTime.Sub(parentStart).Seconds() / parentDuration)
+	width := int(100 * endTime.Sub(parentStart).Seconds() / parentDuration) - start
+	fmt.Println("start:", start, "end", width)
+	top := &divTree{
+		Start: start,
+		Width: width,
+		Name: n.Span.Name,
+		VerticalOffset: verticalPosition,
+	}
+	top.Annotations = make([]annotation, len(n.Span.Annotations))
+	for i, a := range n.Span.Annotations {
+		top.Annotations[i].Msg = a.Message
+		if a.When.IsZero() {
+			top.Annotations[i].Position = 0
+			continue
+		}
+		top.Annotations[i].Position = int(100 * a.When.Sub(parentStart).Seconds() / parentDuration) - start
+	}
+	top.Children = make([]divTree, len(n.Children))
+	pos := 2
+	for i, c := range n.Children {
+		top.Children[i] = *convertToTree(c, startTime, endTime, pos)
+		pos += 2
+	}
+	return top
+
+}
+
+func findStartTime(n *vtrace.Node) time.Time {
+	if !n.Span.Start.IsZero() {
+		return n.Span.Start
+	}
+	var startTime time.Time
+	for _, a := range n.Span.Annotations {
+		startTime = a.When
+		if !startTime.IsZero() {
+			break
+		}
+	}
+	for _, c := range n.Children {
+		childStartTime := findStartTime(c)
+		if startTime.IsZero() || (!childStartTime.IsZero() && startTime.After(childStartTime)) {
+			startTime = childStartTime
+		}
+		if !startTime.IsZero() {
+			break;
+		}
+	}
+	return startTime
+}
+
+func findEndTime(n *vtrace.Node) time.Time {
+	if !n.Span.End.IsZero() {
+		return n.Span.End
+	}
+
+	size := len(n.Span.Annotations)
+	var endTime time.Time
+	for i := range n.Span.Annotations {
+		endTime = n.Span.Annotations[size - 1 - i].When
+		if !endTime.IsZero() {
+			break
+		}
+	}
+
+	size = len(n.Children)
+	for i := range n.Children {
+		childEndTime := findEndTime(n.Children[size - 1 - i])
+		if endTime.IsZero() || (!childEndTime.IsZero() && childEndTime.After(endTime)) {
+			endTime = childEndTime
+		}
+		if !endTime.IsZero() {
+			break
+		}
+	}
+	return endTime
+}
+
+type vtraceHandler struct {ctx *context.T}
+
+func (v *vtraceHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	var (
+		server = r.FormValue("n")
+		traceId = r.FormValue("t")
+		name   = naming.Join(server, "__debug", "vtrace")
+	)
+	if len(server) == 0 {
+		w.WriteHeader(http.StatusBadRequest)
+		fmt.Fprintf(w, "Must specify a server with the URL query parameter 'n'")
+		return
+	}
+
+	stub := svtrace.StoreClient(name)
+	ctx, tracer := newTracer(v.ctx)
+	id, err := uniqueid.FromHexString(traceId)
+
+	if err != nil {
+		w.WriteHeader(http.StatusBadRequest)
+		fmt.Fprintf(w, "Invalid trace id %s: %v", traceId, err)
+		return
+	}
+
+	trace, err := stub.Trace(ctx, id)
+	if err != nil {
+		w.WriteHeader(http.StatusNotFound)
+		fmt.Fprintf(w, "Unknown trace id: %s", traceId)
+		return
+	}
+
+	var buf bytes.Buffer
+
+	vtrace.FormatTrace(&buf, &trace, nil)
+	node := vtrace.BuildTree(&trace)
+
+	if node == nil {
+		w.WriteHeader(http.StatusInternalServerError)
+		fmt.Fprintf(w, "coud not find root span for trace")
+		return
+	}
+
+	tree := convertToTree(node, findStartTime(node), findEndTime(node), 0)
+	data := struct{
+		Id  string
+		Root *divTree
+		ServerName string
+		CommandLine string
+		DebugTrace string
+		Vtrace      *Tracer
+	}{
+		Id: traceId,
+		Root: tree,
+		ServerName: server,
+		CommandLine: fmt.Sprintf("debug vtraces %q", name),
+		Vtrace: tracer,
+		DebugTrace: buf.String(),
+
+	}
+	executeTemplate(v.ctx, w, r, tmplBrowseVtrace, data)
+}
+
+
 func writeEvent(w http.ResponseWriter, data string) {
 	fmt.Fprintf(w, "data: %s\n\n", strings.TrimSpace(data))
 }
@@ -490,6 +720,8 @@
 	content = "{{template `.header` .}}" + content + "{{template `.footer` .}}"
 	t := template.Must(tmplBrowseHeader.Clone())
 	t = template.Must(t.AddParseTree(tmplBrowseFooter.Name(), tmplBrowseFooter.Tree))
+	colors := []string{"red", "blue", "green"}
+	pos := 0
 	t = t.Funcs(template.FuncMap{
 		"verrorID":           verror.ErrorID,
 		"unmarshalPublicKey": security.UnmarshalPublicKey,
@@ -505,6 +737,11 @@
 			vdl.Convert(&ret, v)
 			return ret
 		},
+		"nextColor": func() string {
+			c := colors[pos]
+			pos = (pos + 1) % len(colors)
+			return c
+		},
 	})
 	t = template.Must(t.New(name).Parse(content))
 	return t
@@ -781,6 +1018,34 @@
   </div>
 </section>
 `)
+	tmplBrowseAllTraces = makeTemplate("alltraces", `
+<section class="section-center mdl-grid">
+  <ul>
+   {{$name := .ServerName}}
+   {{range .Ids}}
+   <li><a href="/vtrace?n={{$name}}&t={{.}}">{{.}}</a></li>
+   {{end}}
+  </ul>
+ </section>
+`)
+	tmplBrowseVtrace = makeTemplate("vtrace",`
+{{define ".span"}}
+<div style="position:relative;left:{{.Start}}%;width:{{.Width}}%;top:{{.VerticalOffset}}px;margin:0px;padding:0px">
+  <!-- Root span -->
+  <div title="{{.Name}}" style="position:relative;left:0%;width:100%;background:{{nextColor}};height:15px;display:block;margin:0px;padding:0px"></div>
+  {{range $i, $child := .Children}}
+  {{template ".span" $child}}
+  {{end}}
+</div>
+{{end}}
+<section class="section--center mdl-grid">
+  <h5>Vtrace for {{.Id}}</h5>
+  <pre>{{.DebugTrace}}</pre>
+  <div id="parent" class="mdl-cell mdl-cell--12-col">
+  {{template ".span" .Root}}
+  </div>
+</section>
+`)
 
 	tmplBrowseHeader = template.Must(template.New(".header").Parse(`
 {{define ".header"}}
@@ -813,6 +1078,7 @@
           <a class="mdl-navigation__link" href="/logs?n={{.ServerName}}">Logs</a>
           <a class="mdl-navigation__link" href="/glob?n={{.ServerName}}">Glob</a>
           <a class="mdl-navigation__link" href="/profiles?n={{.ServerName}}">Profiles</a>
+          <a class="mdl-navigation__link" href="/vtraces?n={{.ServerName}}">Traces</a>
         </nav>
       </div>
     </header>
@@ -824,6 +1090,7 @@
         <a class="mdl-navigation__link" href="/logs?n={{.ServerName}}">Logs</a>
         <a class="mdl-navigation__link" href="/glob?n={{.ServerName}}">Glob</a>
         <a class="mdl-navigation__link" href="/profiles?n={{.ServerName}}">Profiles</a>
+        <a class="mdl-navigation__link" href="/vtraces?n={{.ServerName}}">Traces</a>
       </nav>
     </div>
     <main class="mdl-layout__content">