Skip to content

Commit de2ff60

Browse files
authored
Merge pull request #145 from intel/ctrlc
Enable interruption of PerfSpect with SIGINT (ctrl-c) when collecting data over SSH
2 parents b1af182 + d4bf90d commit de2ff60

File tree

5 files changed

+51
-39
lines changed

5 files changed

+51
-39
lines changed

cmd/metrics/metadata.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,7 @@ func getUncoreDeviceIDs(myTarget target.Target, localTempDir string) (IDs map[st
343343
// getCPUInfo - reads and returns all data from /proc/cpuinfo
344344
func getCPUInfo(myTarget target.Target) (cpuInfo []map[string]string, err error) {
345345
cmd := exec.Command("cat", "/proc/cpuinfo")
346-
stdout, stderr, exitcode, err := myTarget.RunCommand(cmd, 0)
346+
stdout, stderr, exitcode, err := myTarget.RunCommand(cmd, 0, true)
347347
if err != nil {
348348
err = fmt.Errorf("failed to get cpuinfo: %s, %d, %v", stderr, exitcode, err)
349349
return
@@ -369,7 +369,7 @@ func getCPUInfo(myTarget target.Target) (cpuInfo []map[string]string, err error)
369369
// 'perf list'
370370
func getPerfSupportedEvents(myTarget target.Target, perfPath string) (supportedEvents string, err error) {
371371
cmd := exec.Command(perfPath, "list")
372-
stdout, stderr, exitcode, err := myTarget.RunCommand(cmd, 0)
372+
stdout, stderr, exitcode, err := myTarget.RunCommand(cmd, 0, true)
373373
if err != nil {
374374
err = fmt.Errorf("failed to get perf list: %s, %d, %v", stderr, exitcode, err)
375375
return

cmd/metrics/nmi_watchdog.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func getNMIWatchdog(myTarget target.Target) (setting string, err error) {
4848
return
4949
}
5050
cmd := exec.Command(sysctl, "kernel.nmi_watchdog")
51-
stdout, _, _, err := myTarget.RunCommand(cmd, 0)
51+
stdout, _, _, err := myTarget.RunCommand(cmd, 0, true)
5252
if err != nil {
5353
return
5454
}
@@ -86,7 +86,7 @@ func setNMIWatchdog(myTarget target.Target, setting string, localTempDir string)
8686
// findSysctl - gets a useable path to sysctl or error
8787
func findSysctl(myTarget target.Target) (path string, err error) {
8888
cmd := exec.Command("which", "sysctl")
89-
stdout, _, _, err := myTarget.RunCommand(cmd, 0)
89+
stdout, _, _, err := myTarget.RunCommand(cmd, 0, true)
9090
if err == nil {
9191
//found it
9292
path = strings.TrimSpace(stdout)
@@ -95,7 +95,7 @@ func findSysctl(myTarget target.Target) (path string, err error) {
9595
// didn't find it on the path, try being specific
9696
sbinPath := "/usr/sbin/sysctl"
9797
cmd = exec.Command("which", sbinPath)
98-
_, _, _, err = myTarget.RunCommand(cmd, 0)
98+
_, _, _, err = myTarget.RunCommand(cmd, 0, true)
9999
if err == nil {
100100
// found it
101101
path = sbinPath

cmd/metrics/process.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ func GetCgroups(myTarget target.Target, cids []string, localTempDir string) (cgr
6363
func GetHotProcesses(myTarget target.Target, maxProcesses int, filter string) (processes []Process, err error) {
6464
// run ps to get list of processes sorted by cpu utilization (descending)
6565
cmd := exec.Command("ps", "-a", "-x", "-h", "-o", "pid,ppid,comm,cmd", "--sort=-%cpu")
66-
stdout, stderr, exitcode, err := myTarget.RunCommand(cmd, 0)
66+
stdout, stderr, exitcode, err := myTarget.RunCommand(cmd, 0, true)
6767
if err != nil {
6868
err = fmt.Errorf("failed to get hot processes: %s, %d, %v", stderr, exitcode, err)
6969
return
@@ -173,7 +173,7 @@ done | sort -nr | head -n %d
173173

174174
func processExists(myTarget target.Target, pid string) (exists bool) {
175175
cmd := exec.Command("ps", "-p", pid)
176-
_, _, _, err := myTarget.RunCommand(cmd, 0)
176+
_, _, _, err := myTarget.RunCommand(cmd, 0, true)
177177
if err != nil {
178178
exists = false
179179
return
@@ -184,7 +184,7 @@ func processExists(myTarget target.Target, pid string) (exists bool) {
184184

185185
func getProcess(myTarget target.Target, pid string) (process Process, err error) {
186186
cmd := exec.Command("ps", "-q", pid, "h", "-o", "pid,ppid,comm,cmd", "ww")
187-
stdout, stderr, exitcode, err := myTarget.RunCommand(cmd, 0)
187+
stdout, stderr, exitcode, err := myTarget.RunCommand(cmd, 0, true)
188188
if err != nil {
189189
err = fmt.Errorf("failed to get process: %s, %d, %v", stderr, exitcode, err)
190190
return

internal/script/script.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ func RunScripts(myTarget target.Target, scripts []ScriptDefinition, ignoreScript
160160
} else {
161161
cmd = exec.Command("bash", path.Join(myTarget.GetTempDirectory(), masterScriptName))
162162
}
163-
stdout, stderr, exitcode, err := myTarget.RunCommand(cmd, 0)
163+
stdout, stderr, exitcode, err := myTarget.RunCommand(cmd, 0, false) // don't reuse ssh connection on long-running commands, makes it difficult to kill the command
164164
if err != nil {
165165
slog.Error("error running master script on target", slog.String("stdout", stdout), slog.String("stderr", stderr), slog.Int("exitcode", exitcode), slog.String("error", err.Error()))
166166
return nil, err
@@ -183,7 +183,7 @@ func RunScripts(myTarget target.Target, scripts []ScriptDefinition, ignoreScript
183183
// run sequential scripts
184184
for _, script := range sequentialScripts {
185185
cmd := prepareCommand(script, myTarget.GetTempDirectory())
186-
stdout, stderr, exitcode, err := myTarget.RunCommand(cmd, 0)
186+
stdout, stderr, exitcode, err := myTarget.RunCommand(cmd, 0, false)
187187
if err != nil {
188188
slog.Error("error running script on target", slog.String("script", script.Script), slog.String("stdout", stdout), slog.String("stderr", stderr), slog.Int("exitcode", exitcode), slog.String("error", err.Error()))
189189
}
@@ -241,7 +241,7 @@ func RunScriptAsync(myTarget target.Target, script ScriptDefinition, localTempDi
241241
}()
242242
}
243243
cmd := prepareCommand(script, myTarget.GetTempDirectory())
244-
err = myTarget.RunCommandAsync(cmd, stdoutChannel, stderrChannel, exitcodeChannel, 0, cmdChannel)
244+
err = myTarget.RunCommandAsync(cmd, 0, false, stdoutChannel, stderrChannel, exitcodeChannel, cmdChannel)
245245
errorChannel <- err
246246
}
247247

internal/target/target.go

Lines changed: 40 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,24 @@ type Target interface {
6060
GetUserPath() (path string, err error)
6161

6262
// RunCommand runs the specified command on the target.
63+
// Arguments:
64+
// - cmd: the command to run
65+
// - timeout: the maximum time allowed for the command to run (zero means no timeout)
66+
// - reuseSSHConnection: whether to reuse the SSH connection for the command (only relevant for RemoteTarget)
6367
// It returns the standard output, standard error, exit code, and any error that occurred.
64-
RunCommand(cmd *exec.Cmd, timeout int) (stdout string, stderr string, exitCode int, err error)
68+
RunCommand(cmd *exec.Cmd, timeout int, reuseSSHConnection bool) (stdout string, stderr string, exitCode int, err error)
6569

6670
// RunCommandAsync runs the specified command on the target in an asynchronous manner.
71+
// Arguments:
72+
// - cmd: the command to run
73+
// - timeout: the maximum time allowed for the command to run (zero means no timeout)
74+
// - reuseSSHConnection: whether to reuse the SSH connection for the command (only relevant for RemoteTarget)
75+
// - stdoutChannel: a channel to send the standard output of the command
76+
// - stderrChannel: a channel to send the standard error of the command
77+
// - exitcodeChannel: a channel to send the exit code of the command
78+
// - cmdChannel: a channel to send the command that was run
6779
// It returns any error that occurred.
68-
RunCommandAsync(cmd *exec.Cmd, stdoutChannel chan string, stderrChannel chan string, exitcodeChannel chan int, timeout int, cmdChannel chan *exec.Cmd) error
80+
RunCommandAsync(cmd *exec.Cmd, timeout int, reuseSSHConnection bool, stdoutChannel chan string, stderrChannel chan string, exitcodeChannel chan int, cmdChannel chan *exec.Cmd) error
6981

7082
// PushFile transfers a file from the local system to the target.
7183
// It returns any error that occurred.
@@ -171,16 +183,16 @@ func (t *RemoteTarget) SetSshPass(sshPass string) {
171183

172184
// RunCommand executes the given command with a timeout and returns the standard output,
173185
// standard error, exit code, and any error that occurred.
174-
func (t *LocalTarget) RunCommand(cmd *exec.Cmd, timeout int) (stdout string, stderr string, exitCode int, err error) {
186+
func (t *LocalTarget) RunCommand(cmd *exec.Cmd, timeout int, argNotUsed bool) (stdout string, stderr string, exitCode int, err error) {
175187
input := ""
176188
if t.sudo != "" && len(cmd.Args) > 2 && cmd.Args[0] == "sudo" && strings.HasPrefix(cmd.Args[1], "-") && strings.Contains(cmd.Args[1], "S") { // 'sudo -S' gets password from stdin
177189
input = t.sudo + "\n"
178190
}
179191
return runLocalCommandWithInputWithTimeout(cmd, input, timeout)
180192
}
181193

182-
func (t *RemoteTarget) RunCommand(cmd *exec.Cmd, timeout int) (stdout string, stderr string, exitCode int, err error) {
183-
localCommand := t.prepareLocalCommand(cmd, false)
194+
func (t *RemoteTarget) RunCommand(cmd *exec.Cmd, timeout int, reuseSSHConnection bool) (stdout string, stderr string, exitCode int, err error) {
195+
localCommand := t.prepareLocalCommand(cmd, reuseSSHConnection)
184196
return runLocalCommandWithInputWithTimeout(localCommand, "", timeout)
185197
}
186198

@@ -190,15 +202,15 @@ func (t *RemoteTarget) RunCommand(cmd *exec.Cmd, timeout int) (stdout string, st
190202
// and the exit code is sent to the exitcodeChannel.
191203
// The timeout parameter specifies the maximum time allowed for the command to run.
192204
// Returns an error if there was a problem running the command.
193-
func (t *LocalTarget) RunCommandAsync(cmd *exec.Cmd, stdoutChannel chan string, stderrChannel chan string, exitcodeChannel chan int, timeout int, cmdChannel chan *exec.Cmd) (err error) {
205+
func (t *LocalTarget) RunCommandAsync(cmd *exec.Cmd, timeout int, argNotUsed bool, stdoutChannel chan string, stderrChannel chan string, exitcodeChannel chan int, cmdChannel chan *exec.Cmd) (err error) {
194206
localCommand := cmd
195207
cmdChannel <- localCommand
196208
err = runLocalCommandWithInputWithTimeoutAsync(localCommand, stdoutChannel, stderrChannel, exitcodeChannel, "", timeout)
197209
return
198210
}
199211

200-
func (t *RemoteTarget) RunCommandAsync(cmd *exec.Cmd, stdoutChannel chan string, stderrChannel chan string, exitcodeChannel chan int, timeout int, cmdChannel chan *exec.Cmd) (err error) {
201-
localCommand := t.prepareLocalCommand(cmd, true)
212+
func (t *RemoteTarget) RunCommandAsync(cmd *exec.Cmd, timeout int, reuseSSHConnection bool, stdoutChannel chan string, stderrChannel chan string, exitcodeChannel chan int, cmdChannel chan *exec.Cmd) (err error) {
213+
localCommand := t.prepareLocalCommand(cmd, reuseSSHConnection)
202214
cmdChannel <- localCommand
203215
err = runLocalCommandWithInputWithTimeoutAsync(localCommand, stdoutChannel, stderrChannel, exitcodeChannel, "", timeout)
204216
return
@@ -266,7 +278,7 @@ func (t *RemoteTarget) CreateTempDirectory(rootDir string) (tempDir string, err
266278
root = fmt.Sprintf("--tmpdir=%s", rootDir)
267279
}
268280
cmd := exec.Command("mktemp", "-d", "-t", root, "perfspect.tmp.XXXXXXXXXX", "|", "xargs", "realpath")
269-
tempDir, _, _, err = t.RunCommand(cmd, 0)
281+
tempDir, _, _, err = t.RunCommand(cmd, 0, true)
270282
tempDir = strings.TrimSpace(tempDir)
271283
t.tempDir = tempDir
272284
return
@@ -331,7 +343,7 @@ func (t *LocalTarget) CreateDirectory(baseDir string, targetDir string) (dir str
331343
func (t *RemoteTarget) CreateDirectory(baseDir string, targetDir string) (dir string, err error) {
332344
dir = filepath.Join(baseDir, targetDir)
333345
cmd := exec.Command("mkdir", dir)
334-
_, _, _, err = t.RunCommand(cmd, 0)
346+
_, _, _, err = t.RunCommand(cmd, 0, true)
335347
return
336348
}
337349

@@ -348,7 +360,7 @@ func (t *LocalTarget) RemoveDirectory(targetDir string) (err error) {
348360
func (t *RemoteTarget) RemoveDirectory(targetDir string) (err error) {
349361
if targetDir != "" {
350362
cmd := exec.Command("rm", "-rf", targetDir)
351-
_, _, _, err = t.RunCommand(cmd, 0)
363+
_, _, _, err = t.RunCommand(cmd, 0, true)
352364
}
353365
return
354366
}
@@ -360,7 +372,7 @@ func (t *LocalTarget) CanConnect() bool {
360372

361373
func (t *RemoteTarget) CanConnect() bool {
362374
cmd := exec.Command("exit", "0")
363-
_, _, _, err := t.RunCommand(cmd, 5)
375+
_, _, _, err := t.RunCommand(cmd, 5, true)
364376
return err == nil
365377
}
366378

@@ -388,14 +400,14 @@ func (t *LocalTarget) CanElevatePrivileges() bool {
388400
slog.Error("error writing sudo password", slog.String("error", err.Error()))
389401
}
390402
}()
391-
_, _, _, err := t.RunCommand(cmd, 0)
403+
_, _, _, err := t.RunCommand(cmd, 0, true)
392404
if err == nil {
393405
t.canElevate = 1
394406
return true // sudo password works
395407
}
396408
}
397409
cmd := exec.Command("sudo", "-kS", "ls")
398-
_, _, _, err := t.RunCommand(cmd, 0)
410+
_, _, _, err := t.RunCommand(cmd, 0, true)
399411
if err == nil { // true - passwordless sudo works
400412
t.canElevate = 1
401413
return true
@@ -415,7 +427,7 @@ func (t *RemoteTarget) CanElevatePrivileges() bool {
415427
return true
416428
}
417429
cmd := exec.Command("sudo", "-kS", "ls")
418-
_, _, _, err := t.RunCommand(cmd, 0)
430+
_, _, _, err := t.RunCommand(cmd, 0, true)
419431
if err == nil { // true - passwordless sudo works
420432
t.canElevate = 1
421433
return true
@@ -492,7 +504,7 @@ func (t *LocalTarget) GetUserPath() (string, error) {
492504
func (t *RemoteTarget) GetUserPath() (string, error) {
493505
if t.userPath == "" {
494506
cmd := exec.Command("echo", "$PATH")
495-
stdout, _, _, err := t.RunCommand(cmd, 0)
507+
stdout, _, _, err := t.RunCommand(cmd, 0, true)
496508
if err != nil {
497509
return "", err
498510
}
@@ -594,7 +606,7 @@ func runLocalCommandWithInputWithTimeoutAsync(cmd *exec.Cmd, stdoutChannel chan
594606
return nil
595607
}
596608

597-
func (t *RemoteTarget) prepareSSHFlags(scp bool, async bool, prompt bool) (flags []string) {
609+
func (t *RemoteTarget) prepareSSHFlags(scp bool, useControlMaster bool, prompt bool) (flags []string) {
598610
flags = []string{
599611
"-2",
600612
"-o",
@@ -621,7 +633,7 @@ func (t *RemoteTarget) prepareSSHFlags(scp bool, async bool, prompt bool) (flags
621633
flags = append(flags, promptFlags...)
622634
}
623635
// when using a control master, a long-running remote program doesn't get terminated when the local ssh client is terminated
624-
if !async {
636+
if useControlMaster {
625637
controlPathFlags := []string{
626638
"-o",
627639
"ControlPath=" + filepath.Join(os.TempDir(), `control-%h-%p-%r`),
@@ -654,10 +666,10 @@ func (t *RemoteTarget) prepareSSHFlags(scp bool, async bool, prompt bool) (flags
654666
return
655667
}
656668

657-
func (t *RemoteTarget) prepareSSHCommand(command []string, async bool, prompt bool) []string {
669+
func (t *RemoteTarget) prepareSSHCommand(command []string, useControlMaster bool, prompt bool) []string {
658670
var cmd []string
659671
cmd = append(cmd, "ssh")
660-
cmd = append(cmd, t.prepareSSHFlags(false, async, prompt)...)
672+
cmd = append(cmd, t.prepareSSHFlags(false, useControlMaster, prompt)...)
661673
if t.user != "" {
662674
cmd = append(cmd, t.user+"@"+t.host)
663675
} else {
@@ -671,7 +683,7 @@ func (t *RemoteTarget) prepareSSHCommand(command []string, async bool, prompt bo
671683
func (t *RemoteTarget) prepareSCPCommand(src string, dstDir string, push bool) []string {
672684
var cmd []string
673685
cmd = append(cmd, "scp")
674-
cmd = append(cmd, t.prepareSSHFlags(true, false, false)...)
686+
cmd = append(cmd, t.prepareSSHFlags(true, true, false)...)
675687
if push {
676688
fileInfo, err := os.Stat(src)
677689
if err != nil {
@@ -698,11 +710,11 @@ func (t *RemoteTarget) prepareSCPCommand(src string, dstDir string, push bool) [
698710
return cmd
699711
}
700712

701-
func (t *RemoteTarget) prepareLocalCommand(cmd *exec.Cmd, async bool) *exec.Cmd {
713+
func (t *RemoteTarget) prepareLocalCommand(cmd *exec.Cmd, useControlMaster bool) *exec.Cmd {
702714
var name string
703715
var args []string
704716
usePass := t.key == "" && t.sshPass != ""
705-
sshCommand := t.prepareSSHCommand(cmd.Args, async, usePass)
717+
sshCommand := t.prepareSSHCommand(cmd.Args, useControlMaster, usePass)
706718
if usePass {
707719
name = t.sshpassPath
708720
args = []string{"-e", "--"}
@@ -741,7 +753,7 @@ func (t *RemoteTarget) prepareAndRunSCPCommand(srcPath string, dstDir string, is
741753

742754
func getArchitecture(t Target) (arch string, err error) {
743755
cmd := exec.Command("uname", "-m")
744-
arch, _, _, err = t.RunCommand(cmd, 0)
756+
arch, _, _, err = t.RunCommand(cmd, 0, true)
745757
if err != nil {
746758
return
747759
}
@@ -751,7 +763,7 @@ func getArchitecture(t Target) (arch string, err error) {
751763

752764
func getFamily(t Target) (family string, err error) {
753765
cmd := exec.Command("bash", "-c", "lscpu | grep -i \"^CPU family:\" | awk '{print $NF}'")
754-
family, _, _, err = t.RunCommand(cmd, 0)
766+
family, _, _, err = t.RunCommand(cmd, 0, true)
755767
if err != nil {
756768
return
757769
}
@@ -761,7 +773,7 @@ func getFamily(t Target) (family string, err error) {
761773

762774
func getModel(t Target) (model string, err error) {
763775
cmd := exec.Command("bash", "-c", "lscpu | grep -i model: | awk '{print $NF}'")
764-
model, _, _, err = t.RunCommand(cmd, 0)
776+
model, _, _, err = t.RunCommand(cmd, 0, true)
765777
if err != nil {
766778
return
767779
}
@@ -776,7 +788,7 @@ func installLkms(t Target, lkms []string) (installedLkms []string, err error) {
776788
}
777789
for _, lkm := range lkms {
778790
slog.Debug("attempting to install kernel module", slog.String("lkm", lkm))
779-
_, _, _, err := t.RunCommand(exec.Command("modprobe", "--first-time", lkm), 10)
791+
_, _, _, err := t.RunCommand(exec.Command("modprobe", "--first-time", lkm), 10, true)
780792
if err != nil {
781793
slog.Debug("kernel module already installed or problem installing", slog.String("lkm", lkm), slog.String("error", err.Error()))
782794
continue
@@ -794,7 +806,7 @@ func uninstallLkms(t Target, lkms []string) (err error) {
794806
}
795807
for _, lkm := range lkms {
796808
slog.Debug("attempting to uninstall kernel module", slog.String("lkm", lkm))
797-
_, _, _, err := t.RunCommand(exec.Command("modprobe", "-r", lkm), 10)
809+
_, _, _, err := t.RunCommand(exec.Command("modprobe", "-r", lkm), 10, true)
798810
if err != nil {
799811
slog.Error("error uninstalling kernel module", slog.String("lkm", lkm), slog.String("error", err.Error()))
800812
continue

0 commit comments

Comments
 (0)