Skip to content

Commit

Permalink
✨ Command verbosity with task report files. (#50)
Browse files Browse the repository at this point in the history
Add command reporting verbosity. This is intended to better support
troubleshooting.
Mainly, the support for level=_LiveOutput_. This _streams_ command
output to files attached to the task report.

The output reporting is rate-limited using a _backoff_ algorithm. 

Add command `New` _constructor_ and update the repository package to use
it.

**Requires**: konveyor/tackle2-hub#577

Example:
```
 - '[CMD] Running: /usr/bin/konveyor-analyzer --provider-settings /addon/opt/settings.json --output-file /addon/report.yaml --no-dependency-rules --rules /addon/rules/rulesets/1/rules --rules /addon/rules/rulesets/24/rules --rules /addon/rules/rulesets/20/rules --label-selector konveyor.io/target=cloud-readiness --dep-label-selector !konveyor.io/dep-source=open-source'
    - '[CMD] /usr/bin/konveyor-analyzer succeeded.'
    - '[CMD] Running: /usr/bin/konveyor-analyzer-dep --provider-settings /addon/opt/settings.json --output-file /addon/deps.yaml'
    - '[CMD] /usr/bin/konveyor-analyzer-dep succeeded.'
    - 'Analysis reported. duration: 102.5784ms'
    - '[TAG] Tagging Application 4.'
    - Facts updated.
    - Done.
attached:
    - id: 989
      name: konveyor-analyzer.output
      activity: 44
    - id: 991
      name: konveyor-analyzer-dep.output
      activity: 50
```

---------

Signed-off-by: Jeff Ortel <[email protected]>
  • Loading branch information
jortel authored Dec 19, 2023
1 parent 31d1afa commit 1119ca4
Show file tree
Hide file tree
Showing 9 changed files with 287 additions and 76 deletions.
76 changes: 41 additions & 35 deletions command/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,30 @@ import (
"context"
"fmt"
"os/exec"
"strings"

hub "github.com/konveyor/tackle2-hub/addon"
"path"
)

var (
addon = hub.Addon
)

//
// New returns a command.
func New(path string) (cmd *Command) {
cmd = &Command{Path: path}
return
}

//
// Command execution.
type Command struct {
Options Options
Path string
Dir string
Output []byte
Options Options
Path string
Dir string
Reporter Reporter
Writer Writer
}

//
Expand All @@ -40,22 +48,31 @@ func (r *Command) Run() (err error) {
// The command and output are both reported in
// task Report.Activity.
func (r *Command) RunWith(ctx context.Context) (err error) {
addon.Activity(
"[CMD] Running: %s %s",
r.Path,
strings.Join(r.Options, " "))
r.Writer.reporter = &r.Reporter
output := path.Base(r.Path) + ".output"
r.Reporter.file, err = addon.File.Touch(output)
if err != nil {
return
}
r.Reporter.Run(r.Path, r.Options)
addon.Attach(r.Reporter.file)
defer func() {
r.Writer.End()
if err != nil {
r.Reporter.Error(r.Path, err, r.Writer.buffer)
} else {
r.Reporter.Succeeded(r.Path, r.Writer.buffer)
}
}()
cmd := exec.CommandContext(ctx, r.Path, r.Options...)
cmd.Dir = r.Dir
r.Output, err = cmd.CombinedOutput()
cmd.Stdout = &r.Writer
cmd.Stderr = &r.Writer
err = cmd.Start()
if err != nil {
addon.Activity(
"[CMD] %s failed: %s.\n%s",
r.Path,
err.Error(),
string(r.Output))
} else {
addon.Activity("[CMD] succeeded.")
return
}
err = cmd.Wait()
return
}

Expand All @@ -64,41 +81,30 @@ func (r *Command) RunWith(ctx context.Context) (err error) {
// On error: The command (without arguments) and output are
// reported in task Report.Activity
func (r *Command) RunSilent() (err error) {
err = r.RunSilentWith(context.TODO())
r.Reporter.Verbosity = Error
err = r.RunWith(context.TODO())
return
}

//
// RunSilentWith executes the command with context.
// On error: The command (without arguments) and output are
// reported in task Report.Activity
func (r *Command) RunSilentWith(ctx context.Context) (err error) {
cmd := exec.CommandContext(ctx, r.Path, r.Options...)
cmd.Dir = r.Dir
r.Output, err = cmd.CombinedOutput()
if err != nil {
addon.Activity(
"[CMD] %s failed: %s.\n%s",
r.Path,
err.Error(),
string(r.Output))
}
return
// Output returns the command output.
func (r *Command) Output() (b []byte) {
return r.Writer.buffer
}

//
// Options are CLI options.
type Options []string

//
// add
// Add option.
func (a *Options) Add(option string, s ...string) {
*a = append(*a, option)
*a = append(*a, s...)
}

//
// add
// Addf option.
func (a *Options) Addf(option string, x ...interface{}) {
*a = append(*a, fmt.Sprintf(option, x...))
}
116 changes: 116 additions & 0 deletions command/reporter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package command

import (
"strings"
"github.com/konveyor/tackle2-hub/api"
)

//
// Verbosity.
const (
// Disabled reports: NOTHING.
Disabled = -2
// Error reports: error.
Error = -1
// Default reports: error, started, succeeded.
Default = 0
// LiveOutput reports: error, started, succeeded, output (live).
LiveOutput = 1
)

//
// Reporter activity reporter.
type Reporter struct {
Verbosity int
file *api.File
index int
}

//
// Run reports command started in task Report.Activity.
func (r *Reporter) Run(path string, options Options) {
switch r.Verbosity {
case Disabled:
case Error:
case Default,
LiveOutput:
addon.Activity(
"[CMD] Running: %s %s",
path,
strings.Join(options, " "))
}
}

//
// Succeeded reports command succeeded in task Report.Activity.
func (r *Reporter) Succeeded(path string, output []byte) {
switch r.Verbosity {
case Disabled:
case Error:
case Default:
addon.Activity(
"[CMD] %s succeeded.",
path)
r.append(output)
case LiveOutput:
addon.Activity(
"[CMD] %s succeeded.",
path)
}
}

//
// Error reports command failed in task Report.Activity.
func (r *Reporter) Error(path string, err error, output []byte) {
if len(output) == 0 {
return
}
switch r.Verbosity {
case Disabled:
case Error,
Default:
addon.Activity(
"[CMD] %s failed: %s",
path,
err.Error())
r.append(output)
case LiveOutput:
addon.Activity(
"[CMD] %s failed: %s.",
path,
err.Error())
}
}

//
// Output reports command output.
func (r *Reporter) Output(buffer []byte) (reported int) {
switch r.Verbosity {
case Disabled:
case Error:
case Default:
case LiveOutput:
if r.index >= len(buffer) {
return
}
batch := buffer[r.index:]
reported = len(batch)
if reported > 0 {
r.index += reported
r.append(batch)
}
}
return
}

//
// append output.
func (r *Reporter) append(batch []byte) {
if r.file == nil {
return
}
err := addon.File.Patch(r.file.ID, batch)
if err != nil {
panic(err)
}
}
89 changes: 89 additions & 0 deletions command/writer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package command

import (
"time"
)

const (
// Backoff rate increment.
Backoff = time.Millisecond * 100
// MaxBackoff max backoff.
MaxBackoff = 10 * Backoff
// MinBackoff minimum backoff.
MinBackoff = Backoff
)

//
// Writer records command output.
type Writer struct {
reporter *Reporter
buffer []byte
backoff time.Duration
end chan any
ended chan any
}

//
// Write command output.
func (w *Writer) Write(p []byte) (n int, err error) {
n = len(p)
w.buffer = append(w.buffer, p...)
switch w.reporter.Verbosity {
case LiveOutput:
if w.ended == nil {
w.end = make(chan any)
w.ended = make(chan any)
go w.report()
}
}
return
}

//
// End of writing.
func (w *Writer) End() {
if w.end == nil {
return
}
close(w.end)
<-w.ended
close(w.ended)
w.end = nil
}

//
// report in task Report.Activity.
// Rate limited.
func (w *Writer) report() {
w.backoff = MinBackoff
ended := false
for {
select {
case <-w.end:
ended = true
case <-time.After(w.backoff):
}
n := w.reporter.Output(w.buffer)
w.adjustBackoff(n)
if ended && n == 0 {
break
}
}
w.ended <- true
}

//
// adjustBackoff adjust the backoff as needed.
// incremented when output reported.
// decremented when no outstanding output reported.
func (w *Writer) adjustBackoff(reported int) {
if reported > 0 {
if w.backoff < MaxBackoff {
w.backoff += Backoff
}
} else {
if w.backoff > MinBackoff {
w.backoff -= Backoff
}
}
}
16 changes: 8 additions & 8 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ go 1.18

require (
github.com/clbanning/mxj v1.8.4
github.com/jortel/go-utils v0.1.1
github.com/konveyor/tackle2-hub v0.2.2-0.20230731153407-22bf2d68128a
github.com/jortel/go-utils v0.1.2
github.com/konveyor/tackle2-hub v0.3.0-rc.2.0.20231219211826-f09d0b24c0e6
)

require (
Expand Down Expand Up @@ -43,7 +43,7 @@ require (
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
github.com/leodido/go-urn v1.2.3 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mattn/go-sqlite3 v1.14.17 // indirect
Expand All @@ -64,12 +64,12 @@ require (
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.9 // indirect
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
golang.org/x/crypto v0.7.0 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/oauth2 v0.5.0 // indirect
golang.org/x/sys v0.7.0 // indirect
golang.org/x/term v0.6.0 // indirect
golang.org/x/text v0.8.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/term v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/time v0.3.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.30.0 // indirect
Expand Down
Loading

0 comments on commit 1119ca4

Please sign in to comment.