trace: initialize templates lazily

Parsing the templates can take a significant amount of
time(30-35ms) so it should not be done on the init phase.

Change-Id: I8f258b8028510e698a97b55faeac0d28a61b7b22
Reviewed-on: https://go-review.googlesource.com/21654
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
diff --git a/trace/events.go b/trace/events.go
index e66c7e3..d8daec1 100644
--- a/trace/events.go
+++ b/trace/events.go
@@ -21,11 +21,6 @@
 	"time"
 )
 
-var eventsTmpl = template.Must(template.New("events").Funcs(template.FuncMap{
-	"elapsed":   elapsed,
-	"trimSpace": strings.TrimSpace,
-}).Parse(eventsHTML))
-
 const maxEventsPerLog = 100
 
 type bucket struct {
@@ -101,7 +96,7 @@
 
 	famMu.RLock()
 	defer famMu.RUnlock()
-	if err := eventsTmpl.Execute(w, data); err != nil {
+	if err := eventsTmpl().Execute(w, data); err != nil {
 		log.Printf("net/trace: Failed executing template: %v", err)
 	}
 }
@@ -421,6 +416,19 @@
 	}
 }
 
+var eventsTmplCache *template.Template
+var eventsTmplOnce sync.Once
+
+func eventsTmpl() *template.Template {
+	eventsTmplOnce.Do(func() {
+		eventsTmplCache = template.Must(template.New("events").Funcs(template.FuncMap{
+			"elapsed":   elapsed,
+			"trimSpace": strings.TrimSpace,
+		}).Parse(eventsHTML))
+	})
+	return eventsTmplCache
+}
+
 const eventsHTML = `
 <html>
 	<head>
diff --git a/trace/histogram.go b/trace/histogram.go
index bb42aa5..9bf4286 100644
--- a/trace/histogram.go
+++ b/trace/histogram.go
@@ -12,6 +12,7 @@
 	"html/template"
 	"log"
 	"math"
+	"sync"
 
 	"golang.org/x/net/internal/timeseries"
 )
@@ -320,15 +321,20 @@
 
 func (h *histogram) html() template.HTML {
 	buf := new(bytes.Buffer)
-	if err := distTmpl.Execute(buf, h.newData()); err != nil {
+	if err := distTmpl().Execute(buf, h.newData()); err != nil {
 		buf.Reset()
 		log.Printf("net/trace: couldn't execute template: %v", err)
 	}
 	return template.HTML(buf.String())
 }
 
-// Input: data
-var distTmpl = template.Must(template.New("distTmpl").Parse(`
+var distTmplCache *template.Template
+var distTmplOnce sync.Once
+
+func distTmpl() *template.Template {
+	distTmplOnce.Do(func() {
+		// Input: data
+		distTmplCache = template.Must(template.New("distTmpl").Parse(`
 <table>
 <tr>
     <td style="padding:0.25em">Count: {{.Count}}</td>
@@ -354,3 +360,6 @@
 {{end}}
 </table>
 `))
+	})
+	return distTmplCache
+}
diff --git a/trace/trace.go b/trace/trace.go
index ecd766e..64f56a3 100644
--- a/trace/trace.go
+++ b/trace/trace.go
@@ -238,7 +238,7 @@
 
 	completedMu.RLock()
 	defer completedMu.RUnlock()
-	if err := pageTmpl.ExecuteTemplate(w, "Page", data); err != nil {
+	if err := pageTmpl().ExecuteTemplate(w, "Page", data); err != nil {
 		log.Printf("net/trace: Failed executing template: %v", err)
 	}
 }
@@ -902,10 +902,18 @@
 	return string(b)
 }
 
-var pageTmpl = template.Must(template.New("Page").Funcs(template.FuncMap{
-	"elapsed": elapsed,
-	"add":     func(a, b int) int { return a + b },
-}).Parse(pageHTML))
+var pageTmplCache *template.Template
+var pageTmplOnce sync.Once
+
+func pageTmpl() *template.Template {
+	pageTmplOnce.Do(func() {
+		pageTmplCache = template.Must(template.New("Page").Funcs(template.FuncMap{
+			"elapsed": elapsed,
+			"add":     func(a, b int) int { return a + b },
+		}).Parse(pageHTML))
+	})
+	return pageTmplCache
+}
 
 const pageHTML = `
 {{template "Prolog" .}}
diff --git a/trace/trace_test.go b/trace/trace_test.go
index c6aad86..bfd9dfe 100644
--- a/trace/trace_test.go
+++ b/trace/trace_test.go
@@ -70,6 +70,20 @@
 	}
 }
 
+// TestParseTemplate checks that all templates used by this package are valid
+// as they are parsed on first usage
+func TestParseTemplate(t *testing.T) {
+	if tmpl := distTmpl(); tmpl == nil {
+		t.Error("invalid template returned from distTmpl()")
+	}
+	if tmpl := pageTmpl(); tmpl == nil {
+		t.Error("invalid template returned from pageTmpl()")
+	}
+	if tmpl := eventsTmpl(); tmpl == nil {
+		t.Error("invalid template returned from eventsTmpl()")
+	}
+}
+
 func benchmarkTrace(b *testing.B, maxEvents, numEvents int) {
 	numSpans := (b.N + numEvents + 1) / numEvents