blob: 96cb9336e7ae9ea8200caf813d176057ce8b1e48 [file] [log] [blame]
// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package main
import (
"flag"
"fmt"
"os"
"trace_profiling/cmd/profile/profile"
"trace_profiling/cmd/profile/remote"
)
func runProfiling(prof *profile.Profiler, target *remote.SSHTarget, tunnel *remote.Tunnel) {
tunnelReady := make(chan bool, 1)
tunnelError := make(chan error, 1)
if tunnel != nil {
go func(errChan chan error) {
errChan <- tunnel.BeginTunneling(tunnelReady)
}(tunnelError)
} else {
tunnelReady <- true // No tunnel implies it's ready to go as is.
}
defer func() {
if tunnel != nil {
tunnel.Close()
<-tunnelReady // Wait for tunnel to be closed.
}
}()
// Wait until tunneling is ready to go or failed.
select {
case err := <-tunnelError:
fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error())
return
case <-tunnelReady:
{
err := target.Connect()
if err != nil {
fmt.Fprintf(os.Stderr, "SSH error: %s\n", err.Error())
return
}
defer target.Disconnect()
err = prof.GatherProfiles()
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
}
}
}
}
// Retrieve the configuration parameters from a single Bundle JSON file.
func getParamsFromBundle(bundleFilePath string) (
*remote.SSHParams, *remote.TunnelParams, *profile.ProfileParams, error) {
sshParams, tunnelParams, profileParams, err := profile.ReadConfigBundle(bundleFilePath)
return sshParams, tunnelParams, profileParams, err
}
// Retrieve the configuration parameters from individual JSON files for
// ssh, tunnel and profile configurations.
func getParamsFromFiles(sshConfigFile, tunnelConfigFile, profileConfigFile string) (
*remote.SSHParams, *remote.TunnelParams, *profile.ProfileParams, error) {
var tunnelConfig *remote.TunnelParams
var err error
if tunnelConfigFile != "" && tunnelConfigFile != "/no-tunnel/" {
tunnelConfig, err = remote.ReadTunnelParamsFromJSON(tunnelConfigFile)
if err != nil {
return nil, nil, nil, err
}
}
sshConfig, err := remote.CreateSSHParamsFromJSON(sshConfigFile)
if err != nil {
return nil, nil, nil, err
}
profConfig, err := profile.CreateProfileParamsFromJSON(profileConfigFile)
if err != nil {
return nil, nil, nil, err
}
return sshConfig, tunnelConfig, profConfig, nil
}
func main() {
var argUnifiedConfigFilePath string
var argTunnelConfigFilepath string
var argSSHConfigFilepath string
var argBundleConfigFilePath string
var argProfileConfigFilepath string
var argForceInstallTools bool
var argAlwaysCopyTraces bool
var argEnableVerbose bool
flag.StringVar(&argUnifiedConfigFilePath, "config", "",
"A single, unified configuration file")
flag.StringVar(&argTunnelConfigFilepath, "tunnel-config", "/no-tunnel/",
"Optional tunnel (port-forwarding) configuration file")
flag.StringVar(&argSSHConfigFilepath, "ssh-config", "ssh_config.json",
"SSH configuration file")
flag.StringVar(&argProfileConfigFilepath, "profile-config", "profile_config.json",
"Profile configuration file")
flag.StringVar(&argBundleConfigFilePath, "config-bundle", "",
"Configuration-bundle file (other files ignored when specified)")
flag.BoolVar(&argForceInstallTools, "reinstall-tools", false,
"Re-install the profiling tools on the remote device, even if they are already there")
flag.BoolVar(&argAlwaysCopyTraces, "always-copy-traces", false,
"Always copy the traces to the remote profiling device, even if they are already there")
flag.BoolVar(&argEnableVerbose, "verbose", false,
"Enable verbose mode, to see more info during profiling")
flag.Parse()
// Unidied configuration takes precendence, then a config bundle if available.
// Otherwise, we look for individual SSH, tunnel and profile config files.
var sshParams *remote.SSHParams
var tunnelParams *remote.TunnelParams
var profParams *profile.ProfileParams
var profilerConfig *profile.ProfilerConfig
var err error
if argUnifiedConfigFilePath != "" {
profilerConfig = profile.CreateProfilerConfig()
err = profilerConfig.ParseJSONFile(argUnifiedConfigFilePath)
} else if argBundleConfigFilePath != "" {
sshParams, tunnelParams, profParams, err = getParamsFromBundle(argBundleConfigFilePath)
} else {
sshParams, tunnelParams, profParams, err = getParamsFromFiles(
argSSHConfigFilepath, argTunnelConfigFilepath, argProfileConfigFilepath)
}
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
return
}
if profilerConfig != nil {
sshParams = profilerConfig.GetSSHParams()
tunnelParams = profilerConfig.GetTunnelParams()
profParams = profilerConfig.GetProfilerParams()
if sshParams == nil {
fmt.Fprintf(os.Stderr, "No SSH configuration in %s\n", argUnifiedConfigFilePath)
return
}
if profParams == nil {
fmt.Fprintf(os.Stderr, "No Profiler configuration in %s\n", argUnifiedConfigFilePath)
return
}
}
// Tunneling is optional. If no tunnel parameters are provided, the SHH
// connection to the target device will be direct.
var tunnel *remote.Tunnel
if tunnelParams != nil {
tunnel, err = remote.CreateTunnel(tunnelParams)
if err != nil {
fmt.Fprintf(os.Stderr, "Error creating tunnel %s\n", err.Error())
return
}
}
var target *remote.SSHTarget
target, err = remote.CreateSSHTargetWithParams(sshParams)
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
return
}
var prof = profile.CreateProfiler(profParams, target)
prof.SetEnableVerbose(argEnableVerbose)
prof.SetAlwaysCopyTraces(argAlwaysCopyTraces)
prof.SetForceInstallTools(argForceInstallTools)
runProfiling(prof, target, tunnel)
}