vm: allow Diagnose to directly return diagnosis

Rather than writing the diagnosis to the kernel console, Diagnose can
now directly return the extra debugging info, which will be appended ot
the kernel console log.
This commit is contained in:
Michael Pratt 2018-12-21 07:58:27 -08:00 committed by Dmitry Vyukov
parent 588075e659
commit 2fc01104d0
10 changed files with 79 additions and 36 deletions

View File

@ -405,6 +405,6 @@ func (inst *instance) Run(timeout time.Duration, stop <-chan bool, command strin
return vmimpl.Multiplex(adb, merger, tty, timeout, stop, inst.closed, inst.debug) return vmimpl.Multiplex(adb, merger, tty, timeout, stop, inst.closed, inst.debug)
} }
func (inst *instance) Diagnose() bool { func (inst *instance) Diagnose() ([]byte, bool) {
return false return nil, false
} }

View File

@ -366,11 +366,11 @@ func waitForConsoleConnect(merger *vmimpl.OutputMerger) error {
} }
} }
func (inst *instance) Diagnose() bool { func (inst *instance) Diagnose() ([]byte, bool) {
if inst.env.OS == "openbsd" { if inst.env.OS == "openbsd" {
return vmimpl.DiagnoseOpenBSD(inst.consolew) return nil, vmimpl.DiagnoseOpenBSD(inst.consolew)
} }
return false return nil, false
} }
func (pool *Pool) getSerialPortOutput(name, gceKey string) ([]byte, error) { func (pool *Pool) getSerialPortOutput(name, gceKey string) ([]byte, error) {

View File

@ -327,9 +327,9 @@ func (inst *instance) guestProxy() (*os.File, error) {
return guestSock, nil return guestSock, nil
} }
func (inst *instance) Diagnose() bool { func (inst *instance) Diagnose() ([]byte, bool) {
osutil.Run(time.Minute, inst.runscCmd("debug", "-signal=12", inst.name)) osutil.Run(time.Minute, inst.runscCmd("debug", "-signal=12", inst.name))
return true return nil, true
} }
func init() { func init() {

View File

@ -303,8 +303,8 @@ func (inst *instance) Run(timeout time.Duration, stop <-chan bool, command strin
return vmimpl.Multiplex(cmd, merger, dmesg, timeout, stop, inst.closed, inst.debug) return vmimpl.Multiplex(cmd, merger, dmesg, timeout, stop, inst.closed, inst.debug)
} }
func (inst *instance) Diagnose() bool { func (inst *instance) Diagnose() ([]byte, bool) {
return false return nil, false
} }
func splitTargetPort(addr string) (string, int, error) { func splitTargetPort(addr string) (string, int, error) {

View File

@ -287,8 +287,8 @@ func (inst *instance) Run(timeout time.Duration, stop <-chan bool, command strin
return outputC, errorC, nil return outputC, errorC, nil
} }
func (inst *instance) Diagnose() bool { func (inst *instance) Diagnose() ([]byte, bool) {
return false return nil, false
} }
const script = `#! /bin/bash const script = `#! /bin/bash

View File

@ -512,12 +512,12 @@ func (inst *instance) Run(timeout time.Duration, stop <-chan bool, command strin
return inst.merger.Output, errc, nil return inst.merger.Output, errc, nil
} }
func (inst *instance) Diagnose() bool { func (inst *instance) Diagnose() ([]byte, bool) {
select { select {
case inst.diagnose <- true: case inst.diagnose <- true:
default: default:
} }
return false return nil, false
} }
// nolint: lll // nolint: lll

View File

@ -126,7 +126,7 @@ func (inst *Instance) Run(timeout time.Duration, stop <-chan bool, command strin
return inst.impl.Run(timeout, stop, command) return inst.impl.Run(timeout, stop, command)
} }
func (inst *Instance) Diagnose() bool { func (inst *Instance) Diagnose() ([]byte, bool) {
return inst.impl.Diagnose() return inst.impl.Diagnose()
} }
@ -204,7 +204,12 @@ func (inst *Instance) MonitorExecution(outc <-chan []byte, errc <-chan error,
if time.Since(lastExecuteTime) < noOutputTimeout { if time.Since(lastExecuteTime) < noOutputTimeout {
break break
} }
if inst.Diagnose() { diag, wait := inst.Diagnose()
if len(diag) > 0 {
mon.output = append(mon.output, "DIAGNOSIS:\n"...)
mon.output = append(mon.output, diag...)
}
if wait {
mon.waitForOutput() mon.waitForOutput()
} }
rep := &report.Report{ rep := &report.Report{
@ -232,7 +237,12 @@ type monitor struct {
func (mon *monitor) extractError(defaultError string) *report.Report { func (mon *monitor) extractError(defaultError string) *report.Report {
crashed := defaultError != "" || !mon.canExit crashed := defaultError != "" || !mon.canExit
if crashed { if crashed {
mon.inst.Diagnose() // N.B. we always wait below for other errors.
diag, _ := mon.inst.Diagnose()
if len(diag) > 0 {
mon.output = append(mon.output, "DIAGNOSIS:\n"...)
mon.output = append(mon.output, diag...)
}
} }
// Give it some time to finish writing the error message. // Give it some time to finish writing the error message.
mon.waitForOutput() mon.waitForOutput()
@ -253,9 +263,16 @@ func (mon *monitor) extractError(defaultError string) *report.Report {
} }
return rep return rep
} }
if !crashed && mon.inst.Diagnose() { if !crashed {
diag, wait := mon.inst.Diagnose()
if len(diag) > 0 {
mon.output = append(mon.output, "DIAGNOSIS:\n"...)
mon.output = append(mon.output, diag...)
}
if wait {
mon.waitForOutput() mon.waitForOutput()
} }
}
rep := mon.reporter.Parse(mon.output[mon.matchPos:]) rep := mon.reporter.Parse(mon.output[mon.matchPos:])
if rep == nil { if rep == nil {
panic(fmt.Sprintf("reporter.ContainsCrash/Parse disagree:\n%s", mon.output[mon.matchPos:])) panic(fmt.Sprintf("reporter.ContainsCrash/Parse disagree:\n%s", mon.output[mon.matchPos:]))

View File

@ -34,6 +34,7 @@ type testInstance struct {
outc chan []byte outc chan []byte
errc chan error errc chan error
diagnoseBug bool diagnoseBug bool
diagnoseNoWait bool
} }
func (inst *testInstance) Copy(hostSrc string) (string, error) { func (inst *testInstance) Copy(hostSrc string) (string, error) {
@ -49,13 +50,20 @@ func (inst *testInstance) Run(timeout time.Duration, stop <-chan bool, command s
return inst.outc, inst.errc, nil return inst.outc, inst.errc, nil
} }
func (inst *testInstance) Diagnose() bool { func (inst *testInstance) Diagnose() ([]byte, bool) {
var diag []byte
if inst.diagnoseBug { if inst.diagnoseBug {
inst.outc <- []byte("BUG: DIAGNOSE\n") diag = []byte("BUG: DIAGNOSE\n")
} else { } else {
inst.outc <- []byte("DIAGNOSE\n") diag = []byte("DIAGNOSE\n")
} }
return true
if inst.diagnoseNoWait {
return diag, false
}
inst.outc <- diag
return nil, true
} }
func (inst *testInstance) Close() { func (inst *testInstance) Close() {
@ -77,6 +85,7 @@ type Test struct {
Name string Name string
CanExit bool // if the program is allowed to exit normally CanExit bool // if the program is allowed to exit normally
DiagnoseBug bool // Diagnose produces output that is detected as kernel crash DiagnoseBug bool // Diagnose produces output that is detected as kernel crash
DiagnoseNoWait bool // Diagnose returns output directly rather than to console
Body func(outc chan []byte, errc chan error) Body func(outc chan []byte, errc chan error)
Report *report.Report Report *report.Report
} }
@ -120,6 +129,19 @@ var tests = []*Test{
), ),
}, },
}, },
{
Name: "diagnose-no-wait",
Body: func(outc chan []byte, errc chan error) {
errc <- nil
},
DiagnoseNoWait: true,
Report: &report.Report{
Title: lostConnectionCrash,
Output: []byte(
"DIAGNOSIS:\nDIAGNOSE\n",
),
},
},
{ {
Name: "kernel-crashes", Name: "kernel-crashes",
Body: func(outc chan []byte, errc chan error) { Body: func(outc chan []byte, errc chan error) {
@ -280,6 +302,7 @@ func testMonitorExecution(t *testing.T, test *Test) {
} }
testInst := inst.impl.(*testInstance) testInst := inst.impl.(*testInstance)
testInst.diagnoseBug = test.DiagnoseBug testInst.diagnoseBug = test.DiagnoseBug
testInst.diagnoseNoWait = test.DiagnoseNoWait
done := make(chan bool) done := make(chan bool)
go func() { go func() {
test.Body(testInst.outc, testInst.errc) test.Body(testInst.outc, testInst.errc)

View File

@ -43,10 +43,13 @@ type Instance interface {
// Command is terminated after timeout. Send on the stop chan can be used to terminate it earlier. // Command is terminated after timeout. Send on the stop chan can be used to terminate it earlier.
Run(timeout time.Duration, stop <-chan bool, command string) (outc <-chan []byte, errc <-chan error, err error) Run(timeout time.Duration, stop <-chan bool, command string) (outc <-chan []byte, errc <-chan error, err error)
// Diagnose forces VM to dump additional debugging info // Diagnose retrieves additional debugging info from the VM (e.g. by
// (e.g. sending some sys-rq's or SIGABORT'ing a Go program). // sending some sys-rq's or SIGABORT'ing a Go program).
// Returns true if it did anything. //
Diagnose() bool // Optionally returns (some or all) of the info directly. If wait ==
// true, the caller must wait for the VM to output info directly to its
// log.
Diagnose() (diagnosis []byte, wait bool)
// Close stops and destroys the VM. // Close stops and destroys the VM.
Close() Close()

View File

@ -310,8 +310,8 @@ func (inst *instance) Run(timeout time.Duration, stop <-chan bool, command strin
return inst.merger.Output, errc, nil return inst.merger.Output, errc, nil
} }
func (inst *instance) Diagnose() bool { func (inst *instance) Diagnose() ([]byte, bool) {
return vmimpl.DiagnoseOpenBSD(inst.consolew) return nil, vmimpl.DiagnoseOpenBSD(inst.consolew)
} }
// Run the given vmctl(8) command and wait for it to finish. // Run the given vmctl(8) command and wait for it to finish.