sampler-fork/data/item.go

214 lines
4.3 KiB
Go
Raw Normal View History

2019-01-31 23:40:05 +00:00
package data
import (
2019-04-07 15:09:24 +00:00
"bufio"
"errors"
"fmt"
2019-03-14 03:01:44 +00:00
ui "github.com/gizak/termui/v3"
"github.com/kr/pty"
"github.com/sqshq/sampler/config"
2019-04-07 15:09:24 +00:00
"io"
2019-03-21 02:23:08 +00:00
"os"
2019-01-31 23:40:05 +00:00
"os/exec"
"strings"
2019-04-07 15:09:24 +00:00
"time"
2019-01-31 23:40:05 +00:00
)
const (
interactiveShellStartupTimeout = 100 * time.Millisecond
interactiveShellMinAwaitTimeout = 100 * time.Millisecond
interactiveShellMaxAwaitTimeout = 1 * time.Second
interactiveShellErrorThreshold = 10
)
2019-01-31 23:40:05 +00:00
type Item struct {
label string
sampleScript string
initScript *string
transformScript *string
color *ui.Color
rateMs int
errorsCount int
interactiveShell *InteractiveShell
}
2019-04-07 15:09:24 +00:00
type InteractiveShell struct {
Channel chan string
File io.WriteCloser
Cmd *exec.Cmd
2019-04-07 15:09:24 +00:00
}
2019-04-07 15:17:28 +00:00
func NewItems(cfgs []config.Item, rateMs int) []*Item {
2019-04-07 15:09:24 +00:00
items := make([]*Item, 0)
for _, i := range cfgs {
2019-04-07 15:09:24 +00:00
item := &Item{
label: *i.Label,
sampleScript: *i.SampleScript,
initScript: i.InitScript,
transformScript: i.TransformScript,
color: i.Color,
rateMs: rateMs,
2019-04-07 15:09:24 +00:00
}
items = append(items, item)
}
return items
2019-01-31 23:40:05 +00:00
}
2019-04-07 15:09:24 +00:00
func (i *Item) nextValue(variables []string) (string, error) {
2019-01-31 23:40:05 +00:00
if i.initScript != nil && i.interactiveShell == nil {
2019-04-07 15:09:24 +00:00
err := i.initInteractiveShell(variables)
if err != nil {
return "", errors.New(fmt.Sprintf("Failed to init interactive shell: %s", err))
2019-04-07 15:09:24 +00:00
}
}
2019-03-21 02:23:08 +00:00
if i.initScript != nil {
2019-04-07 15:09:24 +00:00
return i.executeInteractiveShellCmd(variables)
} else {
return i.executeCmd(variables, i.sampleScript)
2019-03-21 02:23:08 +00:00
}
2019-04-07 15:09:24 +00:00
}
func (i *Item) executeCmd(variables []string, script string) (string, error) {
2019-04-07 15:09:24 +00:00
cmd := exec.Command("sh", "-c", script)
2019-04-07 15:09:24 +00:00
enrichEnvVariables(cmd, variables)
2019-03-21 02:23:08 +00:00
output, err := cmd.Output()
2019-01-31 23:40:05 +00:00
if err != nil {
return "", err
}
return string(output), nil
2019-01-31 23:40:05 +00:00
}
2019-04-07 15:09:24 +00:00
func (i *Item) initInteractiveShell(variables []string) error {
cmd := exec.Command("sh", "-c", *i.initScript)
2019-04-07 15:09:24 +00:00
enrichEnvVariables(cmd, variables)
file, err := pty.Start(cmd)
2019-04-07 15:09:24 +00:00
if err != nil {
return err
}
scanner := bufio.NewScanner(file)
channel := make(chan string)
2019-04-07 15:09:24 +00:00
go func() {
for scanner.Scan() {
channel <- scanner.Text()
2019-04-07 15:09:24 +00:00
}
}()
i.interactiveShell = &InteractiveShell{
Channel: channel,
File: file,
Cmd: cmd,
2019-04-07 15:09:24 +00:00
}
_, err = file.Read(make([]byte, 4096))
2019-04-07 15:09:24 +00:00
if err != nil {
return err
}
time.Sleep(interactiveShellStartupTimeout)
2019-04-07 15:09:24 +00:00
return nil
}
func (i *Item) executeInteractiveShellCmd(variables []string) (string, error) {
_, err := io.WriteString(i.interactiveShell.File, fmt.Sprintf(" %s\n", i.sampleScript))
2019-04-07 15:09:24 +00:00
if err != nil {
i.errorsCount++
if i.errorsCount > interactiveShellErrorThreshold {
i.interactiveShell = nil // restart session
i.errorsCount = 0
}
return "", errors.New(fmt.Sprintf("Failed to execute interactive shell cmd: %s", err))
2019-04-07 15:09:24 +00:00
}
softTimeout := make(chan bool, 1)
hardTimeout := make(chan bool, 1)
2019-04-07 15:09:24 +00:00
go func() {
time.Sleep(i.getAwaitTimeout() / 4)
softTimeout <- true
time.Sleep(i.getAwaitTimeout() * 100)
hardTimeout <- true
2019-04-07 15:09:24 +00:00
}()
var builder strings.Builder
softTimeoutElapsed := false
2019-04-07 15:09:24 +00:00
await:
2019-04-07 15:09:24 +00:00
for {
select {
case output := <-i.interactiveShell.Channel:
o := cleanupOutput(output)
if len(o) > 0 && !strings.Contains(o, i.sampleScript) {
builder.WriteString(o)
builder.WriteString("\n")
if softTimeoutElapsed {
break await
}
}
case <-softTimeout:
if builder.Len() > 0 {
break await
} else {
softTimeoutElapsed = true
2019-04-07 15:09:24 +00:00
}
case <-hardTimeout:
break await
2019-04-07 15:09:24 +00:00
}
}
sample := strings.TrimSpace(builder.String())
return i.transformInteractiveShellCmd(sample)
2019-04-07 15:09:24 +00:00
}
func (i *Item) transformInteractiveShellCmd(sample string) (string, error) {
if i.transformScript != nil && len(sample) > 0 {
return i.executeCmd([]string{"sample=" + sample}, *i.transformScript)
}
return sample, nil
2019-04-07 15:09:24 +00:00
}
func (i *Item) getAwaitTimeout() time.Duration {
timeout := time.Duration(i.rateMs) * time.Millisecond
if timeout > interactiveShellMaxAwaitTimeout {
return interactiveShellMaxAwaitTimeout
} else if timeout < interactiveShellMinAwaitTimeout {
return interactiveShellMinAwaitTimeout
}
return timeout
}
2019-04-07 15:09:24 +00:00
func enrichEnvVariables(cmd *exec.Cmd, variables []string) {
cmd.Env = os.Environ()
for _, variable := range variables {
cmd.Env = append(cmd.Env, variable)
}
}
func cleanupOutput(output string) string {
s := strings.TrimSpace(output)
if idx := strings.Index(s, "\r"); idx != -1 {
return s[idx+1:]
}
return s
}