1
0

feat: realtime output

This commit is contained in:
dump_stack() 2024-10-07 22:06:04 +00:00
parent fee3b44c6e
commit 5bb4e3ff45
Signed by: dump_stack
GPG Key ID: C9905BA72B5E02BB
10 changed files with 173 additions and 37 deletions

View File

@ -240,8 +240,9 @@ func (ka Artifact) Supported(ki distro.KernelInfo) (supported bool, err error) {
return return
} }
// TODO too many parameters
func (ka Artifact) Process(slog zerolog.Logger, ki distro.KernelInfo, func (ka Artifact) Process(slog zerolog.Logger, ki distro.KernelInfo,
outputOnSuccess, endless bool, cBinary, outputOnSuccess, realtimeOutput, endless bool, cBinary,
cEndlessStress string, cEndlessTimeout time.Duration, cEndlessStress string, cEndlessTimeout time.Duration,
dump func(q *qemu.System, ka Artifact, ki distro.KernelInfo, dump func(q *qemu.System, ka Artifact, ki distro.KernelInfo,
result *Result)) { result *Result)) {
@ -333,14 +334,14 @@ func (ka Artifact) Process(slog zerolog.Logger, ki distro.KernelInfo,
// TODO: build should return structure // TODO: build should return structure
start := time.Now() start := time.Now()
result.BuildDir, result.BuildArtifact, result.Build.Output, err = result.BuildDir, result.BuildArtifact, result.Build.Output, err =
Build(slog, tmp, ka, ki, ka.Docker.Timeout.Duration) Build(slog, tmp, ka, ki, ka.Docker.Timeout.Duration, realtimeOutput)
slog.Debug().Str("duration", time.Since(start).String()). slog.Debug().Str("duration", time.Since(start).String()).
Msg("build done") Msg("build done")
if err != nil { if err != nil {
slog.Error().Err(err).Msgf("build failure\n%v\n", result.Build.Output) slog.Error().Err(err).Msgf("build failure\n%v\n", result.Build.Output)
return return
} else { } else {
if outputOnSuccess { if outputOnSuccess && !realtimeOutput {
slog.Info().Msgf("build success\n%v\n", result.Build.Output) slog.Info().Msgf("build success\n%v\n", result.Build.Output)
} else { } else {
slog.Info().Msg("build success") slog.Info().Msg("build success")
@ -403,24 +404,37 @@ func (ka Artifact) Process(slog zerolog.Logger, ki distro.KernelInfo,
} }
var qemuTestOutput string var qemuTestOutput string
q.SetOutputHandler(func(s string) { q.SetQemuOutputHandler(func(s string) {
qemuTestOutput += s + "\n" if realtimeOutput {
fmt.Printf("kmsg: %s\n", s)
} else {
qemuTestOutput += s + "\n"
}
}) })
if realtimeOutput {
q.SetCommandsOutputHandler(func(s string) {
fmt.Printf("test: %s\n", s)
})
}
start := time.Now() start := time.Now()
copyArtifactAndTest(slog, q, ka, &result, remoteTest, outputOnSuccess) copyArtifactAndTest(slog, q, ka, &result, remoteTest, outputOnSuccess, realtimeOutput)
slog.Debug().Str("duration", time.Since(start).String()). slog.Debug().Str("duration", time.Since(start).String()).
Msgf("test completed (success: %v)", result.Test.Ok) Msgf("test completed (success: %v)", result.Test.Ok)
if result.Build.Ok { if result.Build.Ok {
if !result.Run.Ok || !result.Test.Ok { if !result.Run.Ok || !result.Test.Ok {
slog.Error().Msgf("qemu output\n%v\n", qemuTestOutput) slog.Error().Msgf("qemu output\n%v\n", qemuTestOutput)
} else if outputOnSuccess { } else if outputOnSuccess && !realtimeOutput {
slog.Info().Msgf("qemu output\n%v\n", qemuTestOutput) slog.Info().Msgf("qemu output\n%v\n", qemuTestOutput)
} }
} }
q.CloseOutputHandler() if realtimeOutput {
q.CloseCommandsOutputHandler()
}
q.CloseQemuOutputHandler()
if !endless { if !endless {
return return

View File

@ -111,7 +111,7 @@ func buildPreload(workPath, tmp string, ki distro.KernelInfo,
dockerTimeout = ka.Docker.Timeout.Duration dockerTimeout = ka.Docker.Timeout.Duration
} }
_, af, _, err = Build(log.Logger, tmp, ka, ki, dockerTimeout) _, af, _, err = Build(log.Logger, tmp, ka, ki, dockerTimeout, false)
return return
} }

View File

@ -103,7 +103,7 @@ func applyPatches(src string, ka Artifact) (err error) {
} }
func Build(flog zerolog.Logger, tmp string, ka Artifact, func Build(flog zerolog.Logger, tmp string, ka Artifact,
ki distro.KernelInfo, dockerTimeout time.Duration) ( ki distro.KernelInfo, dockerTimeout time.Duration, realtimeOutput bool) (
outdir, outpath, output string, err error) { outdir, outpath, output string, err error) {
target := strings.Replace(ka.Name, " ", "_", -1) target := strings.Replace(ka.Name, " ", "_", -1)
@ -157,9 +157,19 @@ func Build(flog zerolog.Logger, tmp string, ka Artifact,
c.Args = append(c.Args, "--network", "none") c.Args = append(c.Args, "--network", "none")
if realtimeOutput {
c.SetCommandsOutputHandler(func(s string) {
fmt.Printf("%s\n", s)
})
}
output, err = c.Run(outdir, []string{ output, err = c.Run(outdir, []string{
buildCommand + " && chmod -R 777 /work", buildCommand + " && chmod -R 777 /work",
}) })
if realtimeOutput {
c.CloseCommandsOutputHandler()
}
} else { } else {
cmd := exec.Command("bash", "-c", "cd "+outdir+" && "+ cmd := exec.Command("bash", "-c", "cd "+outdir+" && "+
buildCommand) buildCommand)
@ -281,7 +291,7 @@ func CopyFile(sourcePath, destinationPath string) (err error) {
} }
func copyArtifactAndTest(slog zerolog.Logger, q *qemu.System, ka Artifact, func copyArtifactAndTest(slog zerolog.Logger, q *qemu.System, ka Artifact,
res *Result, remoteTest string, outputOnSuccess bool) (err error) { res *Result, remoteTest string, outputOnSuccess, realtimeOutput bool) (err error) {
// Copy all test files to the remote machine // Copy all test files to the remote machine
for _, f := range ka.TestFiles { for _, f := range ka.TestFiles {
@ -346,7 +356,7 @@ func copyArtifactAndTest(slog zerolog.Logger, q *qemu.System, ka Artifact,
return return
} }
if outputOnSuccess { if outputOnSuccess && !realtimeOutput {
slog.Info().Msgf("test success\n%v\n", res.Test.Output) slog.Info().Msgf("test success\n%v\n", res.Test.Output)
} else { } else {
slog.Info().Msg("test success") slog.Info().Msg("test success")

View File

@ -24,6 +24,15 @@ type ContainerCmd struct {
Update ContainerUpdateCmd `cmd:"" help:"update containers"` Update ContainerUpdateCmd `cmd:"" help:"update containers"`
Save ContainerSaveCmd `cmd:"" help:"save containers"` Save ContainerSaveCmd `cmd:"" help:"save containers"`
Cleanup ContainerCleanupCmd `cmd:"" help:"cleanup containers"` Cleanup ContainerCleanupCmd `cmd:"" help:"cleanup containers"`
RealtimeOutput RealtimeContainerOutputFlag `help:"show realtime output"`
}
type RealtimeContainerOutputFlag bool
func (f RealtimeContainerOutputFlag) AfterApply() (err error) {
container.Stdout = bool(f)
return
} }
func (cmd ContainerCmd) Containers() (diis []container.Image, err error) { func (cmd ContainerCmd) Containers() (diis []container.Image, err error) {

View File

@ -193,7 +193,8 @@ func (cmd *DebugCmd) Run(g *Globals) (err error) {
return return
} }
} else { } else {
buildDir, outFile, output, err = artifact.Build(log.Logger, tmp, ka, ki, g.Config.Docker.Timeout.Duration) buildDir, outFile, output, err = artifact.Build(
log.Logger, tmp, ka, ki, g.Config.Docker.Timeout.Duration, false)
if err != nil { if err != nil {
log.Error().Err(err).Msg(output) log.Error().Err(err).Msg(output)
return return

View File

@ -39,6 +39,8 @@ type KernelCmd struct {
ContainerTimeout time.Duration `help:"container timeout"` ContainerTimeout time.Duration `help:"container timeout"`
RealtimeOutput RealtimeContainerOutputFlag `help:"show realtime output"`
List KernelListCmd `cmd:"" help:"list kernels"` List KernelListCmd `cmd:"" help:"list kernels"`
ListRemote KernelListRemoteCmd `cmd:"" help:"list remote kernels"` ListRemote KernelListRemoteCmd `cmd:"" help:"list remote kernels"`
Autogen KernelAutogenCmd `cmd:"" help:"generate kernels based on the current config"` Autogen KernelAutogenCmd `cmd:"" help:"generate kernels based on the current config"`

View File

@ -88,6 +88,7 @@ type PewCmd struct {
IncludeInternalErrors bool `help:"count internal errors as part of the success rate"` IncludeInternalErrors bool `help:"count internal errors as part of the success rate"`
OutputOnSuccess bool `help:"show output on success"` OutputOnSuccess bool `help:"show output on success"`
RealtimeOutput bool `help:"show realtime output"`
Endless bool `help:"endless tests"` Endless bool `help:"endless tests"`
EndlessTimeout time.Duration `help:"timeout between tests" default:"1m"` EndlessTimeout time.Duration `help:"timeout between tests" default:"1m"`
@ -447,7 +448,7 @@ func (cmd PewCmd) testArtifact(swg *sizedwaitgroup.SizedWaitGroup,
Str("kernel", ki.KernelRelease). Str("kernel", ki.KernelRelease).
Logger() Logger()
ka.Process(slog, ki, cmd.OutputOnSuccess, ka.Process(slog, ki, cmd.OutputOnSuccess, cmd.RealtimeOutput,
cmd.Endless, cmd.Binary, cmd.EndlessStress, cmd.EndlessTimeout, cmd.Endless, cmd.Binary, cmd.EndlessStress, cmd.EndlessTimeout,
func(q *qemu.System, ka artifact.Artifact, ki distro.KernelInfo, result *artifact.Result) { func(q *qemu.System, ka artifact.Artifact, ki distro.KernelInfo, result *artifact.Result) {
dumpResult(q, ka, ki, result, cmd.Dist, cmd.Tag, cmd.Binary, cmd.DB) dumpResult(q, ka, ki, result, cmd.Dist, cmd.Tag, cmd.Binary, cmd.DB)

View File

@ -14,6 +14,7 @@ import (
"path/filepath" "path/filepath"
"regexp" "regexp"
"strings" "strings"
"sync"
"time" "time"
"github.com/cavaliergopher/grab/v3" "github.com/cavaliergopher/grab/v3"
@ -45,6 +46,8 @@ var UsePrebuilt = true
var Prune = true var Prune = true
var Stdout = false
type Image struct { type Image struct {
Name string Name string
Distro distro.Distro Distro distro.Distro
@ -176,6 +179,11 @@ type Container struct {
Args []string Args []string
Log zerolog.Logger Log zerolog.Logger
commandsOutput struct {
listener chan string
mu sync.Mutex
}
} }
func New(dist distro.Distro) (c Container, err error) { func New(dist distro.Distro) (c Container, err error) {
@ -234,6 +242,43 @@ func NewFromKernelInfo(ki distro.KernelInfo) (
return return
} }
// c.SetCommandsOutputHandler(func(s string) { fmt.Println(s) })
// defer c.CloseCommandsOutputHandler()
func (c *Container) SetCommandsOutputHandler(handler func(s string)) {
c.commandsOutput.mu.Lock()
defer c.commandsOutput.mu.Unlock()
c.commandsOutput.listener = make(chan string)
go func(l chan string) {
for m := range l {
if m != "" {
handler(m)
}
}
}(c.commandsOutput.listener)
}
func (c *Container) CloseCommandsOutputHandler() {
c.commandsOutput.mu.Lock()
defer c.commandsOutput.mu.Unlock()
close(c.commandsOutput.listener)
c.commandsOutput.listener = nil
}
func (c *Container) handleCommandsOutput(m string) {
if c.commandsOutput.listener == nil {
return
}
c.commandsOutput.mu.Lock()
defer c.commandsOutput.mu.Unlock()
if c.commandsOutput.listener != nil {
c.commandsOutput.listener <- m
}
}
func (c Container) Name() string { func (c Container) Name() string {
return c.name return c.name
} }
@ -408,6 +453,10 @@ func (c Container) build(imagePath string) (output string, err error) {
scanner := bufio.NewScanner(stdout) scanner := bufio.NewScanner(stdout)
for scanner.Scan() { for scanner.Scan() {
m := scanner.Text() m := scanner.Text()
if Stdout {
fmt.Println(m)
}
c.handleCommandsOutput(m)
output += m + "\n" output += m + "\n"
flog.Trace().Str("stdout", m).Msg("") flog.Trace().Str("stdout", m).Msg("")
} }
@ -481,6 +530,10 @@ func (c Container) Run(workdir string, cmds []string) (out string, err error) {
scanner := bufio.NewScanner(stdout) scanner := bufio.NewScanner(stdout)
for scanner.Scan() { for scanner.Scan() {
m := scanner.Text() m := scanner.Text()
if Stdout {
fmt.Println(m)
}
c.handleCommandsOutput(m)
out += m + "\n" out += m + "\n"
flog.Trace().Str("stdout", m).Msg("") flog.Trace().Str("stdout", m).Msg("")
} }

View File

@ -131,7 +131,7 @@ func (pj *jobProcessor) Process(res *Resources) (err error) {
var result *artifact.Result var result *artifact.Result
var dq *qemu.System var dq *qemu.System
pj.job.Artifact.Process(pj.log, pj.job.Target, false, false, "", "", 0, pj.job.Artifact.Process(pj.log, pj.job.Target, false, false, false, "", "", 0,
func(q *qemu.System, ka artifact.Artifact, ki distro.KernelInfo, func(q *qemu.System, ka artifact.Artifact, ki distro.KernelInfo,
res *artifact.Result) { res *artifact.Result) {

View File

@ -101,7 +101,12 @@ type System struct {
Stdout, Stderr string Stdout, Stderr string
output struct { qemuOutput struct {
listener chan string
mu sync.Mutex
}
commandsOutput struct {
listener chan string listener chan string
mu sync.Mutex mu sync.Mutex
} }
@ -143,38 +148,77 @@ func NewSystem(arch arch, kernel Kernel, drivePath string) (q *System, err error
return return
} }
// q.SetOutputHandler(func(s string) { fmt.Println(s) }) // q.SetQemuOutputHandler(func(s string) { fmt.Println(s) })
// defer q.CloseOutputHandler() // defer q.CloseQemuOutputHandler()
func (q *System) SetOutputHandler(handler func(s string)) { func (q *System) SetQemuOutputHandler(handler func(s string)) {
q.output.mu.Lock() q.qemuOutput.mu.Lock()
defer q.output.mu.Unlock() defer q.qemuOutput.mu.Unlock()
q.output.listener = make(chan string) q.qemuOutput.listener = make(chan string)
go func(l chan string) { go func(l chan string) {
for m := range l { for m := range l {
handler(m) if m != "" {
handler(m)
}
} }
}(q.output.listener) }(q.qemuOutput.listener)
} }
func (q *System) CloseOutputHandler() { func (q *System) CloseQemuOutputHandler() {
q.output.mu.Lock() q.qemuOutput.mu.Lock()
defer q.output.mu.Unlock() defer q.qemuOutput.mu.Unlock()
close(q.output.listener) close(q.qemuOutput.listener)
q.output.listener = nil q.qemuOutput.listener = nil
} }
func (q *System) handleOutput(m string) { func (q *System) handleQemuOutput(m string) {
if q.output.listener == nil { if q.qemuOutput.listener == nil {
return return
} }
q.output.mu.Lock() q.qemuOutput.mu.Lock()
defer q.output.mu.Unlock() defer q.qemuOutput.mu.Unlock()
if q.output.listener != nil { if q.qemuOutput.listener != nil {
q.output.listener <- m q.qemuOutput.listener <- m
}
}
// q.SetCommandsOutputHandler(func(s string) { fmt.Println(s) })
// defer q.CloseCommandsOutputHandler()
func (q *System) SetCommandsOutputHandler(handler func(s string)) {
q.commandsOutput.mu.Lock()
defer q.commandsOutput.mu.Unlock()
q.commandsOutput.listener = make(chan string)
go func(l chan string) {
for m := range l {
if m != "" {
handler(m)
}
}
}(q.commandsOutput.listener)
}
func (q *System) CloseCommandsOutputHandler() {
q.commandsOutput.mu.Lock()
defer q.commandsOutput.mu.Unlock()
close(q.commandsOutput.listener)
q.commandsOutput.listener = nil
}
func (q *System) handleCommandsOutput(m string) {
if q.commandsOutput.listener == nil {
return
}
q.commandsOutput.mu.Lock()
defer q.commandsOutput.mu.Unlock()
if q.commandsOutput.listener != nil {
q.commandsOutput.listener <- m
} }
} }
@ -353,7 +397,7 @@ func (q *System) Start() (err error) {
scanner := bufio.NewScanner(q.pipe.stdout) scanner := bufio.NewScanner(q.pipe.stdout)
for scanner.Scan() { for scanner.Scan() {
m := scanner.Text() m := scanner.Text()
q.handleOutput(m) q.handleQemuOutput(m)
q.Stdout += m + "\n" q.Stdout += m + "\n"
q.Log.Trace().Str("stdout", m).Msg("qemu") q.Log.Trace().Str("stdout", m).Msg("qemu")
go q.checkOopsPanic(m) go q.checkOopsPanic(m)
@ -364,7 +408,7 @@ func (q *System) Start() (err error) {
scanner := bufio.NewScanner(q.pipe.stderr) scanner := bufio.NewScanner(q.pipe.stderr)
for scanner.Scan() { for scanner.Scan() {
m := scanner.Text() m := scanner.Text()
q.handleOutput(m) q.handleQemuOutput(m)
q.Stderr += m + "\n" q.Stderr += m + "\n"
q.Log.Trace().Str("stderr", m).Msg("qemu") q.Log.Trace().Str("stderr", m).Msg("qemu")
} }
@ -517,6 +561,7 @@ func (q System) Command(user, cmd string) (output string, err error) {
scanner := bufio.NewScanner(stdout) scanner := bufio.NewScanner(stdout)
for scanner.Scan() { for scanner.Scan() {
m := scanner.Text() m := scanner.Text()
q.handleCommandsOutput(m)
output += m + "\n" output += m + "\n"
flog.Trace().Str("stdout", m).Msg("qemu command") flog.Trace().Str("stdout", m).Msg("qemu command")
} }
@ -530,6 +575,7 @@ func (q System) Command(user, cmd string) (output string, err error) {
scanner := bufio.NewScanner(stderr) scanner := bufio.NewScanner(stderr)
for scanner.Scan() { for scanner.Scan() {
m := scanner.Text() m := scanner.Text()
q.handleCommandsOutput(m)
output += m + "\n" output += m + "\n"
// Note: it prints stderr as stdout // Note: it prints stderr as stdout
flog.Trace().Str("stdout", m).Msg("qemu command") flog.Trace().Str("stdout", m).Msg("qemu command")