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)
import (
"flag"
+ "fmt"
"log"
"os"
"os/signal"
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() {
"fmt"
"os"
"strings"
+ "sync"
)
const (
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{}) {
}
switch level {
case CNone:
+ LogMutex.Lock()
os.Stderr.WriteString(StderrPrefix + p + fmt.Sprintf(format+"\n", args...))
+ LogMutex.Unlock()
return
case CDebug:
if !Debug {
}
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 {
raw[0:4], raw[4:6], raw[6:8], raw[8:10], raw[10:],
)
}
+ statusInit()
tgts := flag.Args()
if len(tgts) == 0 {
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 {
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 {
"%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))
Jobs.Add(1)
go func() {
jsAcquire(shCtx)
+ if FdStatus != nil {
+ FdStatus.Write([]byte{StatusRun})
+ }
defer func() {
jsRelease(shCtx)
lockRelease()
os.Remove(fdStdout.Name())
os.Remove(tmpPath)
os.Remove(fdLock.Name())
+ if FdStatus != nil {
+ FdStatus.Write([]byte{StatusDone})
+ }
Jobs.Done()
}()
started := time.Now()
}
if fdStderr != nil {
tai64nNow(ts)
+ LogMutex.Lock()
fmt.Fprintf(fdStderr, "@%s %s\n", hex.EncodeToString(ts[:]), line)
+ LogMutex.Unlock()
}
if StderrSilent {
continue
--- /dev/null
+/*
+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()
+ }
+ }()
+}