diff --git a/garnet/bin/traceutil/actions.go b/garnet/bin/traceutil/actions.go
index 7f59f1030daf6ebb53467bb0205985bd194e159d..d5e323a5ab915c599faaafd85ebb97de3dafe14a 100644
--- a/garnet/bin/traceutil/actions.go
+++ b/garnet/bin/traceutil/actions.go
@@ -195,15 +195,15 @@ func captureTrace(config *captureTraceConfig, conn *TargetConnection, traceOutpu
 	return err
 }
 
-func convertTrace(generator string, inputPath string, outputPath string,
-	title string) error {
-	fmt.Printf("Converting %s to %s... ", inputPath, outputPath)
+func convertTrace(generator string, outputPath string,
+	title string, inputPaths ...string) error {
+	fmt.Printf("Converting %v to %s... ", inputPaths, outputPath)
 	var args []string
 	args = append(args, "--output="+outputPath)
 	if title != "" {
 		args = append(args, "--title="+title)
 	}
-	args = append(args, inputPath)
+	args = append(args, inputPaths...)
 	err := runCommand(generator, args)
 	if err != nil {
 		fmt.Printf("failed: %s\n", err.Error())
diff --git a/garnet/bin/traceutil/cmd_convert.go b/garnet/bin/traceutil/cmd_convert.go
index f96357408942f063e8aad9bd38340fb0d44d5779..ed7311c5cae75fccbc026c3b51a25bad566b4bd6 100644
--- a/garnet/bin/traceutil/cmd_convert.go
+++ b/garnet/bin/traceutil/cmd_convert.go
@@ -42,7 +42,7 @@ func (cmd *cmdConvert) Execute(_ context.Context, f *flag.FlagSet,
 	for _, jsonFilename := range f.Args() {
 		htmlGenerator := getHtmlGenerator()
 		htmlFilename := replaceFilenameExt(jsonFilename, "html")
-		err := convertTrace(htmlGenerator, jsonFilename, htmlFilename, "")
+		err := convertTrace(htmlGenerator, htmlFilename, "", jsonFilename)
 		if err != nil {
 			ret = subcommands.ExitFailure
 		}
diff --git a/garnet/bin/traceutil/cmd_record.go b/garnet/bin/traceutil/cmd_record.go
index 16911db4273b24004ecade89be4ffff4d30ade4b..4e2a73f616417204940a12e6ff8990523582ec5d 100644
--- a/garnet/bin/traceutil/cmd_record.go
+++ b/garnet/bin/traceutil/cmd_record.go
@@ -3,10 +3,13 @@ package main
 import (
 	"bufio"
 	"context"
+	"encoding/csv"
+	"encoding/json"
 	"errors"
 	"flag"
 	"fmt"
 	"io"
+	"io/ioutil"
 	"os"
 	"os/exec"
 	"path"
@@ -37,10 +40,116 @@ type reportGenerator struct {
 
 type zedmon struct {
 	cmd    *exec.Cmd
+	stdout io.ReadCloser
+	csvout *csv.Reader
 	stderr io.ReadCloser
 	err    error
 }
 
+type zTraceReport struct {
+	DisplayTimeUnit string        `json:"displayTimeUnit"`
+	TraceEvents     []interface{} `json:"traceEvents"`
+	//SystemTraceEvents zSystemTraceEvents `json:"systemTraceEvents"`
+}
+
+type zSystemTraceEvents struct {
+	Type   string        `json:"type"`
+	Events []interface{} `json:"events"`
+}
+
+type zMetadataEvent struct {
+	Type string        `json:"ph"`
+	PID  int           `json:"pid"`
+	Name string        `json:"name"`
+	Args zMetadataArgs `json:"args"`
+}
+
+type zMetadataArgs struct {
+	Name string `json:"name"`
+}
+
+type zCompleteDurationEvent struct {
+	Type      string  `json:"ph"`
+	PID       int     `json:"pid"`
+	Name      string  `json:"name"`
+	Timestamp float64 `json:"ts"`
+	Duration  float64 `json:"dur"`
+}
+
+type zCounterEvent struct {
+	Type      string      `json:"ph"`
+	PID       int         `json:"pid"`
+	Name      string      `json:"name"`
+	Timestamp float64     `json:"ts"`
+	Values    zTraceValue `json:"args"`
+}
+
+type zTraceValue struct {
+	Voltage float32 `json:"voltage"`
+}
+
+const zedmonPID = 2053461101 // "zedm" = 0x7a65546d = 2053461101.
+
+func newZTraceReport(events []interface{}) zTraceReport {
+	return zTraceReport{
+		DisplayTimeUnit: "ns",
+		TraceEvents:     events,
+		// SystemTraceEvents: zSystemTraceEvents{
+		// 	Type:   "fuchsia",
+		// 	Events: events,
+		// },
+	}
+}
+
+func newZMetadataEvent() zMetadataEvent {
+	return zMetadataEvent{
+		Type: "M",
+		PID:  zedmonPID,
+		Name: "process_name",
+		Args: zMetadataArgs{
+			Name: "zedmon",
+		},
+	}
+}
+
+func newZCompleteDurationEvent(name string, ts time.Time, dur time.Duration) zCompleteDurationEvent {
+	tus := float64(ts.UnixNano()) / 1000
+	dus := float64(dur.Nanoseconds()) / 1000
+	return zCompleteDurationEvent{
+		Type:      "X",
+		PID:       zedmonPID,
+		Name:      name,
+		Timestamp: tus,
+		Duration:  dus,
+	}
+}
+
+func newZCounterEvents(ts time.Time, delta time.Duration, vShunt, vBus float32) []interface{} {
+	errStart := ts.Add(-delta / 2)
+	us := float64(ts.UnixNano()) / 1000
+	return []interface{}{
+		newZCompleteDurationEvent(fmt.Sprintf("shunt:%f;bus:%f", vShunt, vBus), errStart, delta),
+		zCounterEvent{
+			Type:      "C",
+			PID:       zedmonPID,
+			Name:      "Shunt voltage",
+			Timestamp: us,
+			Values: zTraceValue{
+				Voltage: vShunt,
+			},
+		},
+		zCounterEvent{
+			Type:      "C",
+			PID:       zedmonPID,
+			Name:      "Bus voltage",
+			Timestamp: us,
+			Values: zTraceValue{
+				Voltage: vBus,
+			},
+		},
+	}
+}
+
 var (
 	builtinReports = map[string]reportGenerator{
 		"html": {getHtmlGenerator(), "html"},
@@ -106,6 +215,7 @@ func (cmd *cmdRecord) Execute(_ context.Context, f *flag.FlagSet,
 		return subcommands.ExitFailure
 	}
 
+	// Establish connection to runtime host.
 	conn, err := NewTargetConnection(cmd.targetHostname, cmd.keyFile)
 	if err != nil {
 		fmt.Println(err.Error())
@@ -113,6 +223,8 @@ func (cmd *cmdRecord) Execute(_ context.Context, f *flag.FlagSet,
 	}
 	defer conn.Close()
 
+	// Zedmon: Sync clock with runtime host to enable eventual
+	// zedmon -> devhost -> runtime host clock domain transformation.
 	var fOffset, fDelta time.Duration
 	doZedmon := cmd.zedmon != ""
 	if doZedmon {
@@ -121,10 +233,11 @@ func (cmd *cmdRecord) Execute(_ context.Context, f *flag.FlagSet,
 			fmt.Printf("Error syncing with device clock: %v\n", err)
 			doZedmon = false
 		} else {
-			fmt.Printf("Synced device clock: Offset: %v, ±%v\n", fOffset, fDelta)
+			fmt.Printf("Synced fuchsia clock: Offset: %v, ±%v\n", fOffset, fDelta)
 		}
 	}
 
+	// Establish local and remote files for managing trace data.
 	prefix := cmd.getFilenamePrefix()
 	var localFilename string
 	if cmd.captureConfig.Binary {
@@ -140,7 +253,6 @@ func (cmd *cmdRecord) Execute(_ context.Context, f *flag.FlagSet,
 	} else {
 		remoteFilename = "/data/trace.json"
 	}
-
 	var localFile *os.File = nil
 	if cmd.captureConfig.Stream {
 		localFile, err = os.Create(localFilename)
@@ -158,25 +270,25 @@ func (cmd *cmdRecord) Execute(_ context.Context, f *flag.FlagSet,
 		cmd.captureConfig.Command = f.Args()
 	}
 
+	// Zedmon: Start capturing data from zedmon device.
 	var z *zedmon
-	var zOffset, zDelta time.Duration
+	var zDataChan chan []byte
+	var zErrChan chan error
 	if doZedmon {
 		z = newZedmon()
-		zOffset, zDelta, err = z.start(cmd.zedmon)
-		if err != nil {
-			fmt.Printf("Error syncing with zedmon clock: %v\n", err)
-			doZedmon = false
-		} else {
-			fmt.Printf("Synced zedmon clock: Offset: %v, ±%v\n", zOffset, zDelta)
-		}
+		zDataChan, zErrChan = z.run(fOffset, fDelta, cmd.zedmon)
 	}
 
+	// Capture trace data from runtime host.
 	err = captureTrace(cmd.captureConfig, conn, localFile)
 	if err != nil {
 		fmt.Println(err.Error())
 		return subcommands.ExitFailure
 	}
 
+	// Zedmon: Stop capturing data, emit errors that occurred in the meantime.
+	var zData []byte
+	zErrs := make([]error, 0)
 	if doZedmon {
 		err = z.stop()
 		if err != nil {
@@ -185,8 +297,33 @@ func (cmd *cmdRecord) Execute(_ context.Context, f *flag.FlagSet,
 		} else {
 			fmt.Printf("Zedmon data collection stopped cleanly\n")
 		}
-	}
 
+		timeout := make(chan bool, 1)
+		go func() {
+			time.Sleep(2 * time.Second)
+			timeout <- true
+		}()
+
+		func() {
+			for {
+				select {
+				case err := <-zErrChan:
+					fmt.Printf("Warning: Zedmon error: %v\n", err)
+					zErrs = append(zErrs, err)
+				case zData = <-zDataChan:
+					fmt.Printf("Collected %d-byte trace from zedmon\n", len(zData))
+					return
+				case <-timeout:
+					fmt.Printf("Failed to collect zedmon data: Channel read timeout\n")
+					return
+				}
+			}
+		}()
+
+		doZedmon = zData != nil
+	}
+
+	// Download file in non-streaming case.
 	if !cmd.captureConfig.Stream {
 		err = cmd.downloadFile(conn, "trace", remoteFilename, localFilename)
 		if err != nil {
@@ -195,6 +332,7 @@ func (cmd *cmdRecord) Execute(_ context.Context, f *flag.FlagSet,
 		}
 	}
 
+	// Benchmark results: Download corresponding file.
 	if len(cmd.captureConfig.BenchmarkResultsFile) > 0 {
 		err = cmd.downloadFile(conn, cmd.captureConfig.BenchmarkResultsFile,
 			cmd.captureConfig.BenchmarkResultsFile,
@@ -206,23 +344,32 @@ func (cmd *cmdRecord) Execute(_ context.Context, f *flag.FlagSet,
 		fmt.Println("done")
 	}
 
-	if doZedmon {
-		// TODO(markdittmer): Read from zedmon data and convert to trace input.
-	}
-
 	// TODO(TO-403): Remove remote file.  Add command line option to leave it.
 
 	title := cmd.getReportTitle()
 
 	// TODO(PT-113): Convert binary format to JSON and HTML when able.
 	if !cmd.captureConfig.Binary {
-		err = convertTrace(generatorPath, localFilename, outputFilename, title)
+		// Zedmon: Include additional zedmon trace data file in HTML output.
+		if doZedmon {
+			zFilename := "zedmon-" + prefix + ".json"
+			err = ioutil.WriteFile(zFilename, zData, 0644)
+			if err != nil {
+				fmt.Printf("Failed to write zedmon trace to file")
+				err = convertTrace(generatorPath, outputFilename, title, localFilename)
+			} else {
+				err = convertTrace(generatorPath, outputFilename, title, localFilename, zFilename)
+			}
+		} else {
+			err = convertTrace(generatorPath, outputFilename, title, localFilename)
+		}
 		if err != nil {
 			fmt.Println(err.Error())
 			return subcommands.ExitFailure
 		}
 	}
 
+	// Handle special report-to-stdout case.
 	if cmd.stdout {
 		report, openErr := os.Open(outputFilename)
 		if openErr != nil {
@@ -296,36 +443,119 @@ func newZedmon() *zedmon {
 	return &zedmon{}
 }
 
-var zRegExp = regexp.MustCompile("^Time offset: ([0-9]+)ns ± ([0-9]+)ns$")
+var zRegExp = regexp.MustCompile("Time offset: ([0-9]+)ns ± ([0-9]+)ns$")
 
 func (z *zedmon) fail(err error) error {
 	if z == nil {
 		return err
 	}
-	z.cmd = nil
-	z.stderr = nil
+	if z.stdout != nil {
+		z.stdout.Close()
+	}
 	if z.stderr != nil {
 		z.stderr.Close()
 	}
 	if z.cmd != nil && z.cmd.Process != nil {
 		z.cmd.Process.Kill()
 	}
+	z.cmd = nil
+	z.stdout = nil
+	z.csvout = nil
+	z.stderr = nil
 	return err
 }
 
+func (z *zedmon) run(fOffset, fDelta time.Duration, path string) (data chan []byte, errs chan error) {
+	data = make(chan []byte)
+	errs = make(chan error)
+	go z.doRun(fOffset, fDelta, path, data, errs)
+	return data, errs
+}
+
+func (z *zedmon) doRun(fOffset, fDelta time.Duration, path string, data chan []byte, errs chan error) {
+	// TODO(markdittmer): Add error delta to trace.
+	zOffset, zDelta, err := z.start(path)
+	if err != nil {
+		errs <- err
+		return
+	}
+	fmt.Printf("Synced zedmon clock: Offset: %v, ±%v\n", zOffset, zDelta)
+
+	offset := zOffset - fOffset
+	delta := 2 * (fDelta + zDelta)
+
+	events := make([]interface{}, 2)
+	events[0] = newZMetadataEvent()
+	var t0 time.Time
+	for {
+		strs, err := z.csvout.Read()
+		if err == io.EOF {
+			break
+		}
+		if err != nil {
+			errs <- z.fail(errors.New("Failed to parse CSV record"))
+			break
+		}
+		if len(strs) != 3 {
+			errs <- z.fail(errors.New("Unexpected CSV record length"))
+			break
+		}
+		ts, err := strconv.ParseInt(strs[0], 10, 64)
+		if err != nil {
+			errs <- z.fail(errors.New("Failed to parse timestamp from CSV"))
+			break
+		}
+		vShunt, err := strconv.ParseFloat(strs[1], 64)
+		if err != nil {
+			errs <- z.fail(errors.New("Failed to parse voltage from CSV"))
+			break
+		}
+		vBus, err := strconv.ParseFloat(strs[2], 64)
+		if err != nil {
+			errs <- z.fail(errors.New("Failed to parse voltage from CSV"))
+			break
+		}
+
+		t := time.Unix(int64(ts/1000000), int64((ts%1000000)*1000)).Add(offset)
+		if t0 == (time.Time{}) {
+			t0 = t
+		}
+		events = append(events, newZCounterEvents(t, delta, float32(vShunt), float32(vBus))...)
+	}
+	events[1] = newZCompleteDurationEvent("maxTimeSyncErr", t0, delta)
+
+	// Drop last event: may be partial line from CSV stream.
+	if len(events) > 2 {
+		events = events[:len(events)-1]
+	}
+
+	d, err := json.Marshal(newZTraceReport(events))
+	if err != nil {
+		errs <- err
+		return
+	}
+
+	data <- d
+}
+
 func (z *zedmon) start(path string) (offset time.Duration, delta time.Duration, err error) {
 	if z == nil {
 		return offset, delta, z.fail(errors.New("Nil zedmon"))
 	}
-	if z.cmd != nil || z.stderr != nil || z.err != nil {
+	if z.cmd != nil || z.stderr != nil || z.stdout != nil || z.csvout != nil || z.err != nil {
 		return offset, delta, z.fail(errors.New("Attempt to reuse zedmon object"))
 	}
 
-	z.cmd = exec.Command(path, "record")
+	z.cmd = exec.Command(path, "record", "-out", "-")
 	z.cmd.Dir, err = os.Getwd()
 	if err != nil {
 		return offset, delta, z.fail(errors.New("Failed to get working directory"))
 	}
+	z.stdout, err = z.cmd.StdoutPipe()
+	if err != nil {
+		return offset, delta, z.fail(err)
+	}
+	z.csvout = csv.NewReader(z.stdout)
 	z.stderr, err = z.cmd.StderrPipe()
 	if err != nil {
 		return offset, delta, z.fail(err)
@@ -338,7 +568,7 @@ func (z *zedmon) start(path string) (offset time.Duration, delta time.Duration,
 
 	nl := byte('\n')
 	for l, err := r.ReadBytes(nl); err == nil; l, err = r.ReadBytes(nl) {
-		matches := zRegExp.FindSubmatch(l)
+		matches := zRegExp.FindSubmatch(l[:len(l)-1])
 		if len(matches) != 3 {
 			continue
 		}
@@ -354,6 +584,7 @@ func (z *zedmon) start(path string) (offset time.Duration, delta time.Duration,
 			return offset, delta, z.fail(errors.New("Failed to parse time sync delta"))
 		}
 		delta = time.Nanosecond * time.Duration(d)
+		break
 	}
 
 	if err != nil {
diff --git a/garnet/bin/traceutil/target_connection.go b/garnet/bin/traceutil/target_connection.go
index 6d10c6879f15bced2dc56e29fb1cdfd6a5b7160e..a0445448bce626f0b857175b78f1ab1d8c15dc93 100644
--- a/garnet/bin/traceutil/target_connection.go
+++ b/garnet/bin/traceutil/target_connection.go
@@ -134,11 +134,11 @@ func (c *TargetConnection) SyncClk() (offset time.Duration, delta time.Duration,
 	if err != nil {
 		return offset, delta, err
 	}
-	coutPipe, err := session.StdoutPipe()
+	cerrPipe, err := session.StderrPipe()
 	if err != nil {
 		return offset, delta, err
 	}
-	cout := bufio.NewReader(coutPipe)
+	cerr := bufio.NewReader(cerrPipe)
 
 	err = session.Start("trace time")
 	if err != nil {
@@ -146,7 +146,7 @@ func (c *TargetConnection) SyncClk() (offset time.Duration, delta time.Duration,
 	}
 
 	// Expect one line of "how to use timesync" output.
-	_, err = cout.ReadString('\n')
+	_, err = cerr.ReadString('\n')
 	if err != nil {
 		return offset, delta, err
 	}
@@ -156,7 +156,6 @@ func (c *TargetConnection) SyncClk() (offset time.Duration, delta time.Duration,
 	// Read one line; expect a timestamp.
 	for i := 0; i < 10; i++ {
 		start := time.Now()
-
 		n, err := cin.Write([]byte("t"))
 		if err != nil {
 			return offset, delta, err
@@ -166,13 +165,14 @@ func (c *TargetConnection) SyncClk() (offset time.Duration, delta time.Duration,
 		}
 
 		// Read a line ending with '\n'.
-		usStr, err := cout.ReadString('\n')
+		usBytes, err := cerr.ReadBytes('\n')
+
+		end := time.Now()
+
 		if err != nil {
 			return offset, delta, err
 		}
 
-		end := time.Now()
-
 		// If this wasn't the fastest RTT we've seen so far, then continue.
 		d := end.Sub(start)
 		if d > delta {
@@ -184,13 +184,13 @@ func (c *TargetConnection) SyncClk() (offset time.Duration, delta time.Duration,
 		t1 := start.Add(delta / 2)
 
 		// Parse remote timestamp.
+		usStr := string(usBytes[:len(usBytes)-1])
 		floatUSecs, err := strconv.ParseFloat(usStr, 64)
 		if err != nil {
 			return offset, delta, errors.New("Failed to parse timestamp")
 		}
-		secs := math.Floor(floatUSecs / 1000000)
-		nanoSecs := (floatUSecs - secs) * 1000
-		t2 := time.Unix(int64(secs), int64(nanoSecs))
+		nanoSecs := int64(math.Round(floatUSecs * 1000.0))
+		t2 := time.Unix(nanoSecs/1000000000, nanoSecs%1000000000)
 
 		// Store offset between local and remote timestamps.
 		offset = t1.Sub(t2)