blob: e2d6d30359ab4901fabc5a7e66866698fa97e484 [file] [log] [blame]
package main
import (
"context"
"flag"
"fmt"
"io"
"os"
"path"
"time"
"github.com/google/subcommands"
)
type cmdRecord struct {
flags *flag.FlagSet
targetHostname string
keyFile string
filePrefix string
reportType string
stdout bool
captureConfig *captureTraceConfig
}
type reportGenerator struct {
generatorPath string
outputFileSuffix string
}
var (
builtinReports = map[string]reportGenerator{
"html": {getHtmlGenerator(), "html"},
}
)
func NewCmdRecord() *cmdRecord {
cmd := &cmdRecord{
flags: flag.NewFlagSet("record", flag.ExitOnError),
}
cmd.flags.StringVar(&cmd.keyFile, "key-file", "", "SSH key file to use. The default is $FUCHSIA_DIR/.ssh/pkey.")
cmd.flags.StringVar(&cmd.filePrefix, "file-prefix", "",
"Prefix for trace file names. Defaults to 'trace-<timestamp>'.")
cmd.flags.StringVar(&cmd.targetHostname, "target", "", "Target hostname.")
cmd.flags.StringVar(&cmd.reportType, "report-type", "html", "Report type.")
cmd.flags.BoolVar(&cmd.stdout, "stdout", false,
"Send the report to stdout, in addition to writing to file.")
cmd.captureConfig = newCaptureTraceConfig(cmd.flags)
return cmd
}
func (*cmdRecord) Name() string {
return "record"
}
func (*cmdRecord) Synopsis() string {
return "Record a trace on a target, download, and convert to HTML."
}
func (cmd *cmdRecord) Usage() string {
usage := "traceutil record <options>\n"
cmd.flags.Visit(func(flag *flag.Flag) {
usage += flag.Usage
})
return usage
}
func (cmd *cmdRecord) SetFlags(f *flag.FlagSet) {
*f = *cmd.flags
}
func (cmd *cmdRecord) Execute(_ context.Context, f *flag.FlagSet,
_ ...interface{}) subcommands.ExitStatus {
// Flag errors in report type early.
reportGenerator := builtinReports[cmd.reportType]
generatorPath := ""
outputFileSuffix := ""
if reportGenerator.generatorPath != "" {
generatorPath = reportGenerator.generatorPath
outputFileSuffix = reportGenerator.outputFileSuffix
} else {
generatorPath = getExternalReportGenerator(cmd.reportType)
outputFileSuffix = cmd.reportType
}
fmt.Printf("generator path: %s\n", generatorPath);
if _, err := os.Stat(generatorPath); os.IsNotExist(err) {
fmt.Printf("No generator for report type \"%s\"\n",
cmd.reportType)
return subcommands.ExitFailure
}
conn, err := NewTargetConnection(cmd.targetHostname, cmd.keyFile)
if err != nil {
fmt.Println(err.Error())
return subcommands.ExitFailure
}
defer conn.Close()
prefix := cmd.getFilenamePrefix()
jsonFilename := prefix + ".json"
outputFilename := prefix + "." + outputFileSuffix
// TODO(dje): Should we use prefix on the remote file name as well?
remoteFilename := "/data/trace.json"
var jsonFile *os.File = nil
if cmd.captureConfig.Stream {
jsonFile, err = os.Create(jsonFilename)
if err != nil {
fmt.Printf("Error creating intermediate file %s: %s\n",
jsonFilename, err.Error())
return subcommands.ExitFailure
}
} else if cmd.captureConfig.Compress {
jsonFilename += ".gz"
remoteFilename += ".gz"
}
err = captureTrace(cmd.captureConfig, conn, jsonFile)
if err != nil {
fmt.Println(err.Error())
return subcommands.ExitFailure
}
if !cmd.captureConfig.Stream {
err = cmd.downloadFile(conn, "trace", remoteFilename, jsonFilename)
if err != nil {
fmt.Println(err.Error())
return subcommands.ExitFailure
}
}
if len(cmd.captureConfig.BenchmarkResultsFile) > 0 {
err = cmd.downloadFile(conn, cmd.captureConfig.BenchmarkResultsFile,
cmd.captureConfig.BenchmarkResultsFile,
cmd.captureConfig.BenchmarkResultsFile)
if err != nil {
fmt.Println(err)
return subcommands.ExitFailure
}
fmt.Println("done")
}
// TODO(TO-403): Remove remote file. Add command line option to leave it.
title := cmd.getReportTitle()
err = convertTrace(generatorPath, jsonFilename, outputFilename, title)
if err != nil {
fmt.Println(err.Error())
return subcommands.ExitFailure
}
if cmd.stdout {
report, openErr := os.Open(outputFilename)
if openErr != nil {
fmt.Println(openErr.Error())
return subcommands.ExitFailure
}
_, reportErr := io.Copy(os.Stdout, report)
if reportErr != nil {
fmt.Println(reportErr.Error())
return subcommands.ExitFailure
}
}
return subcommands.ExitSuccess
}
func (cmd *cmdRecord) downloadFile(conn *TargetConnection, name string, remotePath string, localPath string) error {
now := time.Now()
fmt.Printf("Downloading %s ... ", name)
err := conn.GetFile(remotePath, localPath)
if err != nil {
fmt.Println(err.Error())
return err
}
fmt.Println("done")
elapsed := time.Since(now)
fileInfo, err2 := os.Stat(localPath)
fmt.Printf("Downloaded %s in %0.3f seconds",
path.Base(localPath), elapsed.Seconds())
if err2 == nil {
fmt.Printf(" (%0.3f KB/sec)",
float64((fileInfo.Size()+1023)/1024)/elapsed.Seconds())
} else {
fmt.Printf(" (unable to determine download speed: %s)", err2.Error())
}
fmt.Printf("\n")
return nil
}
func (cmd *cmdRecord) getFilenamePrefix() string {
if cmd.filePrefix == "" {
// Use ISO_8601 date time format.
return "trace-" + time.Now().Format("2006-01-02T15:04:05")
} else {
return cmd.filePrefix
}
}
func (cmd *cmdRecord) getReportTitle() string {
conf := cmd.captureConfig
have_categories := conf.Categories != ""
have_duration := conf.Duration != 0
text := ""
if have_categories {
text = text + ", categories " + conf.Categories
}
if have_duration {
text = text + ", duration " + conf.Duration.String()
}
text = text[2:]
if text == "" {
return ""
}
return fmt.Sprintf("Report for %s", text)
}