]> Cypherpunks.ru repositories - goredo.git/commitdiff
Simple real-time status messages
authorSergey Matveev <stargrave@stargrave.org>
Sun, 22 Nov 2020 18:36:52 +0000 (21:36 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Sun, 22 Nov 2020 18:58:21 +0000 (21:58 +0300)
README
js.go
log.go
main.go
run.go
status.go [new file with mode: 0644]

diff --git a/README b/README
index edb5b765d22067201af5746fd5155bf049fe4bbc..f8b287334aade269273a437ea869fc37c390c0ce 100644 (file)
--- a/README
+++ b/README
@@ -52,6 +52,7 @@ FEATURES                                               *goredo-features*
   lock and jobserver acquirings/releases
 * displaying of each target's execution time
 * each target's stderr can be displayed with the PID
+* optional statusline with currently running/waiting/done jobs
 * target's stderr can be stored on the disk with TAI64N timestamp
   prefixes for each line. To convert them to localtime you can use either
   tai64nlocal utility from daemontools (http://cr.yp.to/daemontools.html)
diff --git a/js.go b/js.go
index ccc9fe1d1ab1a9ecaa4f4a202804af09e3dbbde6..d160c1091a28995a471c2e6ae2b30de83276daf1 100644 (file)
--- a/js.go
+++ b/js.go
@@ -21,6 +21,7 @@ package main
 
 import (
        "flag"
+       "fmt"
        "log"
        "os"
        "os/signal"
@@ -41,7 +42,7 @@ var (
        jsTokens  int
        jsTokensM sync.Mutex
 
-       flagJobs = flag.Uint64("j", 1, "number of parallel jobs (0=inf) (REDO_JOBS)")
+       flagJobs = flag.Uint64("j", 1, fmt.Sprintf("number of parallel jobs (0=inf) (%s)", EnvJobs))
 )
 
 func jsInit() {
diff --git a/log.go b/log.go
index f4fa2ca9c65f647b8aa1d48d71232984b76bed90..ebfb39493043b630f6b839ff75d6cecd54c0a91c 100644 (file)
--- a/log.go
+++ b/log.go
@@ -24,6 +24,7 @@ import (
        "fmt"
        "os"
        "strings"
+       "sync"
 )
 
 const (
@@ -65,11 +66,13 @@ var (
        LogJS   bool
        MyPid   int
 
-       flagDebug   = flag.Bool("debug", false, "enable debug logging (REDO_DEBUG=1)")
-       flagLogWait = flag.Bool("log-wait", false, "enable wait messages logging (REDO_LOG_WAIT=1)")
-       flagLogLock = flag.Bool("log-lock", false, "enable lock messages logging (REDO_LOG_LOCK=1)")
-       flagLogPid  = flag.Bool("log-pid", false, "append PIDs (REDO_LOG_PID=1)")
-       flagLogJS   = flag.Bool("log-js", false, "enable jobserver messages logging (REDO_LOG_JS=1)")
+       flagDebug   = flag.Bool("debug", false, fmt.Sprintf("enable debug logging (%s=1)", EnvDebug))
+       flagLogWait = flag.Bool("log-wait", false, fmt.Sprintf("enable wait messages logging (%s=1)", EnvLogWait))
+       flagLogLock = flag.Bool("log-lock", false, fmt.Sprintf("enable lock messages logging (%s=1)", EnvLogLock))
+       flagLogPid  = flag.Bool("log-pid", false, fmt.Sprintf("append PIDs (%s=1)", EnvLogPid))
+       flagLogJS   = flag.Bool("log-js", false, fmt.Sprintf("enable jobserver messages logging (%s=1)", EnvLogJS))
+
+       LogMutex sync.Mutex
 )
 
 func trace(level, format string, args ...interface{}) {
@@ -79,7 +82,9 @@ func trace(level, format string, args ...interface{}) {
        }
        switch level {
        case CNone:
+               LogMutex.Lock()
                os.Stderr.WriteString(StderrPrefix + p + fmt.Sprintf(format+"\n", args...))
+               LogMutex.Unlock()
                return
        case CDebug:
                if !Debug {
@@ -110,7 +115,9 @@ func trace(level, format string, args ...interface{}) {
        }
        msg := fmt.Sprintf(format, args...)
        msg = StderrPrefix + colourize(level, p+strings.Repeat(". ", Level)+msg)
+       LogMutex.Lock()
        os.Stderr.WriteString(msg + "\n")
+       LogMutex.Unlock()
 }
 
 func colourize(colour, s string) string {
diff --git a/main.go b/main.go
index afeb353f43d002c727f6596d1786052c6ee7f47c..63f580a5319cb17e00c040df0b89d92615831912 100644 (file)
--- a/main.go
+++ b/main.go
@@ -177,6 +177,7 @@ func main() {
                        raw[0:4], raw[4:6], raw[6:8], raw[8:10], raw[10:],
                )
        }
+       statusInit()
 
        tgts := flag.Args()
        if len(tgts) == 0 {
diff --git a/run.go b/run.go
index bb455e845df4b20abc91be0a10c584367b43a301..93c837d9fb458dca6c4bffbe62c120da60aa387f 100644 (file)
--- a/run.go
+++ b/run.go
@@ -63,9 +63,9 @@ var (
        StderrPrefix string
        Jobs         sync.WaitGroup
 
-       flagTrace        = flag.Bool("x", false, "trace current target (sh -x) (set REDO_TRACE=1 for others too)")
-       flagStderrKeep   = flag.Bool("logs", false, "keep job's stderr (REDO_LOGS=1)")
-       flagStderrSilent = flag.Bool("silent", false, "do not print job's stderr (REDO_SILENT=1)")
+       flagTrace        = flag.Bool("x", false, fmt.Sprintf("trace current target (sh -x) (set %s=1 for others too)", EnvTrace))
+       flagStderrKeep   = flag.Bool("logs", false, fmt.Sprintf("keep job's stderr (%s=1)", EnvStderrKeep))
+       flagStderrSilent = flag.Bool("silent", false, fmt.Sprintf("do not print job's stderr (%s=1)", EnvStderrSilent))
 )
 
 type RunErr struct {
@@ -193,14 +193,20 @@ func runScript(tgtOrig string, errs chan error, traced bool) error {
                        fdLock.Close()
                        return TgtErr{tgtOrig, err}
                }
-               trace(CDebug, "waiting: %s", tgtOrig)
                Jobs.Add(1)
+               trace(CDebug, "waiting: %s", tgtOrig)
+               if FdStatus != nil {
+                       FdStatus.Write([]byte{StatusWait})
+               }
                go func() {
                        defer Jobs.Done()
                        trace(CLock, "LOCK_EX: %s", fdLock.Name())
                        unix.Flock(int(fdLock.Fd()), unix.LOCK_EX)
                        lockRelease()
                        trace(CDebug, "waiting done: %s", tgtOrig)
+                       if FdStatus != nil {
+                               FdStatus.Write([]byte{StatusWaited})
+                       }
                        var depInfo *DepInfo
                        fdDep, err := os.Open(path.Join(redoDir, tgt+DepSuffix))
                        if err != nil {
@@ -354,10 +360,19 @@ func runScript(tgtOrig string, errs chan error, traced bool) error {
                "%s=%s", EnvStderrPrefix, childStderrPrefix,
        ))
 
-       cmd.ExtraFiles = append(cmd.ExtraFiles, fdDep)
        fdNum := 0
+       cmd.ExtraFiles = append(cmd.ExtraFiles, fdDep)
        cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%d", EnvDepFd, 3+fdNum))
        fdNum++
+
+       if FdStatus == nil {
+               cmd.Env = append(cmd.Env, fmt.Sprintf("%s=NO", EnvStatusFd))
+       } else {
+               cmd.ExtraFiles = append(cmd.ExtraFiles, FdStatus)
+               cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%d", EnvStatusFd, 3+fdNum))
+               fdNum++
+       }
+
        if JSR == nil {
                // infinite jobs
                cmd.Env = append(cmd.Env, fmt.Sprintf("%s=NO", EnvJSFd))
@@ -394,6 +409,9 @@ func runScript(tgtOrig string, errs chan error, traced bool) error {
        Jobs.Add(1)
        go func() {
                jsAcquire(shCtx)
+               if FdStatus != nil {
+                       FdStatus.Write([]byte{StatusRun})
+               }
                defer func() {
                        jsRelease(shCtx)
                        lockRelease()
@@ -406,6 +424,9 @@ func runScript(tgtOrig string, errs chan error, traced bool) error {
                        os.Remove(fdStdout.Name())
                        os.Remove(tmpPath)
                        os.Remove(fdLock.Name())
+                       if FdStatus != nil {
+                               FdStatus.Write([]byte{StatusDone})
+                       }
                        Jobs.Done()
                }()
                started := time.Now()
@@ -433,7 +454,9 @@ func runScript(tgtOrig string, errs chan error, traced bool) error {
                                }
                                if fdStderr != nil {
                                        tai64nNow(ts)
+                                       LogMutex.Lock()
                                        fmt.Fprintf(fdStderr, "@%s %s\n", hex.EncodeToString(ts[:]), line)
+                                       LogMutex.Unlock()
                                }
                                if StderrSilent {
                                        continue
diff --git a/status.go b/status.go
new file mode 100644 (file)
index 0000000..f203b74
--- /dev/null
+++ b/status.go
@@ -0,0 +1,111 @@
+/*
+goredo -- redo implementation on pure Go
+Copyright (C) 2020 Sergey Matveev <stargrave@stargrave.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 3 of the License.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package main
+
+import (
+       "flag"
+       "fmt"
+       "os"
+       "strings"
+)
+
+const (
+       EnvStatusFd = "REDO_STATUS_FD"
+       EnvNoStatus = "REDO_NO_STATUS"
+
+       StatusRun = iota
+       StatusDone
+       StatusWait
+       StatusWaited
+)
+
+var (
+       FdStatus *os.File
+
+       flagNoStatus = flag.Bool("no-status", false, "disable statusline (REDO_NO_STATUS=1)")
+)
+
+func statusInit() {
+       if *flagNoStatus {
+               return
+       }
+       if v := os.Getenv(EnvNoStatus); v == "1" {
+               return
+       }
+       if v := os.Getenv(EnvStatusFd); v != "" {
+               if v == "NO" {
+                       return
+               }
+               FdStatus = mustParseFd(v, EnvStatusFd)
+               return
+       }
+       var r *os.File
+       var err error
+       r, FdStatus, err = os.Pipe()
+       if err != nil {
+               panic(err)
+       }
+       go func() {
+               running := 0
+               waiting := 0
+               done := 0
+               var out string
+               outLenPrev := 0
+               buf := make([]byte, 1)
+               var n int
+               for {
+                       n, err = r.Read(buf)
+                       if err != nil || n != 1 {
+                               break
+                       }
+                       switch buf[0] {
+                       case StatusRun:
+                               running++
+                       case StatusDone:
+                               running--
+                               done++
+                       case StatusWait:
+                               waiting++
+                       case StatusWaited:
+                               waiting--
+                       }
+                       if NoColor {
+                               out = fmt.Sprintf(
+                                       "\rrun: %d wait: %d done: %d\r",
+                                       running, waiting, done,
+                               )
+                       } else {
+                               out = fmt.Sprintf(
+                                       "\rrun: %s%d%s wait: %s%d%s done: %s%d%s\r",
+                                       CRedo, running, CReset,
+                                       CWait, waiting, CReset,
+                                       CJS, done, CReset,
+                               )
+                       }
+                       if len(out) < outLenPrev {
+                               outLenPrev = len(out)
+                               out += strings.Repeat(" ", outLenPrev-len(out))
+                       } else {
+                               outLenPrev = len(out)
+                       }
+                       LogMutex.Lock()
+                       os.Stderr.WriteString(out)
+                       LogMutex.Unlock()
+               }
+       }()
+}