Add output you can stream matches into, so we don't spwan a process at each match
This commit is contained in:
		@ -2,6 +2,8 @@ package app
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"os/signal"
 | 
			
		||||
@ -23,6 +25,61 @@ func IsStringArrayEqual(one, two []string) bool {
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// Executes a command and write to its stdin via input channel until command, or reaction, dies
 | 
			
		||||
func cmdStdin(commandline []string, input <-chan string) {
 | 
			
		||||
	cmd := exec.Command(commandline[0], commandline[1:]...)
 | 
			
		||||
	stdin, err := cmd.StdinPipe()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.Fatalln("couldn't open stdin on command:", err)
 | 
			
		||||
	}
 | 
			
		||||
	stdout, err := cmd.StdoutPipe()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.Fatalln("couldn't open stdout on command:", err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := cmd.Start(); err != nil {
 | 
			
		||||
		logger.Fatalln("couldn't start command:", err)
 | 
			
		||||
	}
 | 
			
		||||
	defer stdin.Close()
 | 
			
		||||
 | 
			
		||||
	logger.Printf(logger.INFO, fmt.Sprintf("Output started with %v\n", commandline))
 | 
			
		||||
 | 
			
		||||
	// stdout displaying thread
 | 
			
		||||
	go func() {
 | 
			
		||||
		for {
 | 
			
		||||
			// FIXME
 | 
			
		||||
			tmp := make([]byte, 1024)
 | 
			
		||||
			_, err := stdout.Read(tmp)
 | 
			
		||||
			if len(bytes.Trim(tmp, "\x00")) > 0 {
 | 
			
		||||
				for _, line := range strings.Split(strings.ReplaceAll(string(bytes.Trim(tmp, "\x00")), "\r\n", "\n"), "\n") {
 | 
			
		||||
					if len(line) > 0 {
 | 
			
		||||
						logger.Printf(logger.INFO, fmt.Sprintf("Output returned %s", line))
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logger.Printf(logger.ERROR, fmt.Sprintf("Reading output error: %v\n", err))
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	// Stdin writing thread
 | 
			
		||||
	go func() {
 | 
			
		||||
		for {
 | 
			
		||||
			in := <-input
 | 
			
		||||
			_, err := stdin.Write([]byte(in))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logger.Printf(logger.ERROR, fmt.Sprintf("Writing to output error: %v\n", err))
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	err = cmd.Wait()
 | 
			
		||||
	logger.Fatalln("command %v stopped: %v", cmd, err)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Executes a command and channel-send its stdout
 | 
			
		||||
func cmdStdout(commandline []string) chan *string {
 | 
			
		||||
	lines := make(chan *string)
 | 
			
		||||
@ -127,6 +184,16 @@ func (f *Filter) sendActions(match []string, at time.Time) {
 | 
			
		||||
func (a *Action) exec(match []string) {
 | 
			
		||||
	defer wgActions.Done()
 | 
			
		||||
 | 
			
		||||
	if len(a.Cmd) > 0 {
 | 
			
		||||
		a.execCmd(match)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if a.Write != nil {
 | 
			
		||||
		a.execWrite(match)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *Action) execCmd(match []string) {
 | 
			
		||||
	var computedCommand []string
 | 
			
		||||
	var cmdItem string
 | 
			
		||||
 | 
			
		||||
@ -153,6 +220,29 @@ func (a *Action) exec(match []string) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *Action) execWrite(match []string) {
 | 
			
		||||
	var computedWrite string
 | 
			
		||||
	var writeItem string
 | 
			
		||||
 | 
			
		||||
	if a.filter.pattern != nil {
 | 
			
		||||
		for _, item := range a.Write.Text {
 | 
			
		||||
			writeItem = strings.Clone(item)
 | 
			
		||||
			for i, p := range a.filter.pattern {
 | 
			
		||||
				writeItem = strings.ReplaceAll(writeItem, p.nameWithBraces, match[i])
 | 
			
		||||
			}
 | 
			
		||||
			if len(computedWrite) > 0 {
 | 
			
		||||
				computedWrite = computedWrite + " " + writeItem
 | 
			
		||||
			} else {
 | 
			
		||||
				computedWrite = writeItem
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		computedWrite = strings.Join(a.Write.Text, " ")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	a.Write.Output.Stdin <- fmt.Sprintf("%s\n", computedWrite)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ActionsManager(concurrency int) {
 | 
			
		||||
	// concurrency init
 | 
			
		||||
	execActionsC := make(chan PA)
 | 
			
		||||
@ -353,6 +443,14 @@ func StreamManager(s *Stream, endedSignal chan *Stream) {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func OutputsManager(c *Conf) {
 | 
			
		||||
	for outputName := range c.Outputs {
 | 
			
		||||
		output := c.Outputs[outputName]
 | 
			
		||||
		output.Stdin = make(chan string)
 | 
			
		||||
		cmdStdin(output.Start, output.Stdin)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var actions ActionsMap
 | 
			
		||||
var matches MatchesMap
 | 
			
		||||
var actionsLock sync.Mutex
 | 
			
		||||
@ -416,6 +514,7 @@ func Daemon(confFilename string) {
 | 
			
		||||
	_ = runCommands(conf.Start, "start")
 | 
			
		||||
 | 
			
		||||
	go DatabaseManager(conf)
 | 
			
		||||
	go OutputsManager(conf)
 | 
			
		||||
	go MatchesManager()
 | 
			
		||||
	go ActionsManager(conf.Concurrency)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -21,6 +21,15 @@ func (c *Conf) setup() {
 | 
			
		||||
		c.Concurrency = runtime.NumCPU()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for outputName := range c.Outputs {
 | 
			
		||||
		output := c.Outputs[outputName]
 | 
			
		||||
		output.name = outputName
 | 
			
		||||
 | 
			
		||||
		if len(output.Start) == 0 {
 | 
			
		||||
			logger.Fatalf("Bad configuration: output's start %v is empty!", outputName)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for patternName := range c.Patterns {
 | 
			
		||||
		pattern := c.Patterns[patternName]
 | 
			
		||||
		pattern.name = patternName
 | 
			
		||||
@ -136,6 +145,20 @@ func (c *Conf) setup() {
 | 
			
		||||
				if filter.longuestActionDuration == nil || filter.longuestActionDuration.Milliseconds() < action.afterDuration.Milliseconds() {
 | 
			
		||||
					filter.longuestActionDuration = &action.afterDuration
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if action.Write != nil {
 | 
			
		||||
					found := false
 | 
			
		||||
					for oname := range c.Outputs {
 | 
			
		||||
						if strings.EqualFold(oname, action.Write.OutputName) {
 | 
			
		||||
							action.Write.Output = c.Outputs[oname]
 | 
			
		||||
							found = true
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
					if !found {
 | 
			
		||||
						logger.Fatalln(fmt.Sprintf("Bad configuration: action %s.%s.%s refers to undeclared output %s",
 | 
			
		||||
									   stream.name, filter.name, action.name, action.Write.OutputName))
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										22
									
								
								app/types.go
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								app/types.go
									
									
									
									
									
								
							@ -9,12 +9,24 @@ import (
 | 
			
		||||
 | 
			
		||||
type Conf struct {
 | 
			
		||||
	Concurrency int                 `json:"concurrency"`
 | 
			
		||||
	Outputs     map[string]*Output  `json:"outputs"`
 | 
			
		||||
	Patterns    map[string]*Pattern `json:"patterns"`
 | 
			
		||||
	Streams     map[string]*Stream  `json:"streams"`
 | 
			
		||||
	Start       [][]string          `json:"start"`
 | 
			
		||||
	Stop        [][]string          `json:"stop"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Output struct {
 | 
			
		||||
	Start   []string `json:"start"`
 | 
			
		||||
	Stop    []string `json:"stop"`
 | 
			
		||||
	// TODO: Restart when lost communication with output
 | 
			
		||||
	//Restart string   `json:"restart"`
 | 
			
		||||
 | 
			
		||||
	name   string   `json:"-"`
 | 
			
		||||
 | 
			
		||||
	Stdin  chan string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Pattern struct {
 | 
			
		||||
	Regex  string   `json:"regex"`
 | 
			
		||||
	Ignore []string `json:"ignore"`
 | 
			
		||||
@ -52,11 +64,19 @@ type Filter struct {
 | 
			
		||||
	longuestActionDuration *time.Duration
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type OutputWrite struct {
 | 
			
		||||
	OutputName string   `json:"output"`
 | 
			
		||||
	Text       []string `json:"text"`
 | 
			
		||||
 | 
			
		||||
	Output *Output
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Action struct {
 | 
			
		||||
	filter *Filter `json:"-"`
 | 
			
		||||
	name   string  `json:"-"`
 | 
			
		||||
 | 
			
		||||
	Cmd []string `json:"cmd"`
 | 
			
		||||
	Cmd         []string     `json:"cmd"`
 | 
			
		||||
	Write       *OutputWrite `json:"write"`
 | 
			
		||||
 | 
			
		||||
	After         string        `json:"after"`
 | 
			
		||||
	afterDuration time.Duration `json:"-"`
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user