daemon: Support multiplexed build output.
This allows clients to tell whether output comes from the daemon or, if it comes from a builder, from which builder it comes. The latter is particularly useful when MAX-BUILD-JOBS > 1. * nix/libstore/build.cc (DerivationGoal::tryBuildHook) (DerivationGoal::startBuilder): Print the child's PID in "@ build-started" traces. (DerivationGoal::handleChildOutput): Define 'prefix', pass it to 'writeToStderr'. * nix/libstore/globals.cc (Settings:Settings): Initialize 'multiplexedBuildOutput'. (Settings::update): Likewise. * nix/libstore/globals.hh (Settings)[multiplexedBuildOutput]: New field. Update 'printBuildTrace' documentation. * nix/libstore/worker-protocol.hh (PROTOCOL_VERSION): Bump to 0.163. * nix/nix-daemon/nix-daemon.cc (performOp) <wopSetOptions>: Special-case "multiplexed-build-output" and remove "use-ssh-substituter". * guix/store.scm (set-build-options): Add #:multiplexed-build-output? and honor it. (%protocol-version): Bump to #x163. * tests/store.scm ("multiplexed-build-output"): New test. fixletmaster
parent
2ab321ca37
commit
6ef61cc4c3
|
@ -155,7 +155,7 @@
|
||||||
derivation-log-file
|
derivation-log-file
|
||||||
log-file))
|
log-file))
|
||||||
|
|
||||||
(define %protocol-version #x162)
|
(define %protocol-version #x163)
|
||||||
|
|
||||||
(define %worker-magic-1 #x6e697863) ; "nixc"
|
(define %worker-magic-1 #x6e697863) ; "nixc"
|
||||||
(define %worker-magic-2 #x6478696f) ; "dxio"
|
(define %worker-magic-2 #x6478696f) ; "dxio"
|
||||||
|
@ -709,6 +709,15 @@ encoding conversion errors."
|
||||||
;; disabled by default.
|
;; disabled by default.
|
||||||
print-extended-build-trace?
|
print-extended-build-trace?
|
||||||
|
|
||||||
|
;; When true, the daemon prefixes builder output
|
||||||
|
;; with "@ build-log" traces so we can
|
||||||
|
;; distinguish it from daemon output, and we can
|
||||||
|
;; distinguish each builder's output
|
||||||
|
;; (PRINT-BUILD-TRACE must be true as well.) The
|
||||||
|
;; latter is particularly useful when
|
||||||
|
;; MAX-BUILD-JOBS > 1.
|
||||||
|
multiplexed-build-output?
|
||||||
|
|
||||||
build-cores
|
build-cores
|
||||||
(use-substitutes? #t)
|
(use-substitutes? #t)
|
||||||
|
|
||||||
|
@ -757,6 +766,10 @@ encoding conversion errors."
|
||||||
`(("print-extended-build-trace"
|
`(("print-extended-build-trace"
|
||||||
. ,(if print-extended-build-trace? "1" "0")))
|
. ,(if print-extended-build-trace? "1" "0")))
|
||||||
'())
|
'())
|
||||||
|
,@(if multiplexed-build-output?
|
||||||
|
`(("multiplexed-build-output"
|
||||||
|
. ,(if multiplexed-build-output? "true" "false")))
|
||||||
|
'())
|
||||||
,@(if timeout
|
,@(if timeout
|
||||||
`(("build-timeout" . ,(number->string timeout)))
|
`(("build-timeout" . ,(number->string timeout)))
|
||||||
'())
|
'())
|
||||||
|
|
|
@ -1652,8 +1652,8 @@ HookReply DerivationGoal::tryBuildHook()
|
||||||
worker.childStarted(shared_from_this(), hook->pid, fds, false, false);
|
worker.childStarted(shared_from_this(), hook->pid, fds, false, false);
|
||||||
|
|
||||||
if (settings.printBuildTrace)
|
if (settings.printBuildTrace)
|
||||||
printMsg(lvlError, format("@ build-started %1% - %2% %3%")
|
printMsg(lvlError, format("@ build-started %1% - %2% %3% %4%")
|
||||||
% drvPath % drv.platform % logFile);
|
% drvPath % drv.platform % logFile % hook->pid);
|
||||||
|
|
||||||
return rpAccept;
|
return rpAccept;
|
||||||
}
|
}
|
||||||
|
@ -2038,8 +2038,8 @@ void DerivationGoal::startBuilder()
|
||||||
if (!msg.empty()) throw Error(msg);
|
if (!msg.empty()) throw Error(msg);
|
||||||
|
|
||||||
if (settings.printBuildTrace) {
|
if (settings.printBuildTrace) {
|
||||||
printMsg(lvlError, format("@ build-started %1% - %2% %3%")
|
printMsg(lvlError, format("@ build-started %1% - %2% %3% %4%")
|
||||||
% drvPath % drv.platform % logFile);
|
% drvPath % drv.platform % logFile % pid);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2736,6 +2736,19 @@ void DerivationGoal::deleteTmpDir(bool force)
|
||||||
|
|
||||||
void DerivationGoal::handleChildOutput(int fd, const string & data)
|
void DerivationGoal::handleChildOutput(int fd, const string & data)
|
||||||
{
|
{
|
||||||
|
string prefix;
|
||||||
|
|
||||||
|
if (settings.multiplexedBuildOutput) {
|
||||||
|
/* Print a prefix that allows clients to determine whether a message
|
||||||
|
comes from the daemon or from a build process, and in the latter
|
||||||
|
case, which build process it comes from. The PID here matches the
|
||||||
|
one given in "@ build-started" traces; it's shorter that the
|
||||||
|
derivation file name, hence this choice. */
|
||||||
|
prefix = "@ build-log "
|
||||||
|
+ std::to_string(pid < 0 ? hook->pid : pid)
|
||||||
|
+ " " + std::to_string(data.size()) + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
if ((hook && fd == hook->builderOut.readSide) ||
|
if ((hook && fd == hook->builderOut.readSide) ||
|
||||||
(!hook && fd == builderOut.readSide))
|
(!hook && fd == builderOut.readSide))
|
||||||
{
|
{
|
||||||
|
@ -2748,7 +2761,7 @@ void DerivationGoal::handleChildOutput(int fd, const string & data)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (verbosity >= settings.buildVerbosity)
|
if (verbosity >= settings.buildVerbosity)
|
||||||
writeToStderr(data);
|
writeToStderr(prefix + data);
|
||||||
|
|
||||||
if (gzLogFile) {
|
if (gzLogFile) {
|
||||||
if (data.size() > 0) {
|
if (data.size() > 0) {
|
||||||
|
@ -2767,7 +2780,7 @@ void DerivationGoal::handleChildOutput(int fd, const string & data)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hook && fd == hook->fromHook.readSide)
|
if (hook && fd == hook->fromHook.readSide)
|
||||||
writeToStderr(data);
|
writeToStderr(prefix + data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,7 @@ Settings::Settings()
|
||||||
buildTimeout = 0;
|
buildTimeout = 0;
|
||||||
useBuildHook = true;
|
useBuildHook = true;
|
||||||
printBuildTrace = false;
|
printBuildTrace = false;
|
||||||
|
multiplexedBuildOutput = false;
|
||||||
reservedSize = 8 * 1024 * 1024;
|
reservedSize = 8 * 1024 * 1024;
|
||||||
fsyncMetadata = true;
|
fsyncMetadata = true;
|
||||||
useSQLiteWAL = true;
|
useSQLiteWAL = true;
|
||||||
|
@ -120,6 +121,7 @@ void Settings::update()
|
||||||
_get(maxBuildJobs, "build-max-jobs");
|
_get(maxBuildJobs, "build-max-jobs");
|
||||||
_get(buildCores, "build-cores");
|
_get(buildCores, "build-cores");
|
||||||
_get(thisSystem, "system");
|
_get(thisSystem, "system");
|
||||||
|
_get(multiplexedBuildOutput, "multiplexed-build-output");
|
||||||
_get(maxSilentTime, "build-max-silent-time");
|
_get(maxSilentTime, "build-max-silent-time");
|
||||||
_get(buildTimeout, "build-timeout");
|
_get(buildTimeout, "build-timeout");
|
||||||
_get(reservedSize, "gc-reserved-space");
|
_get(reservedSize, "gc-reserved-space");
|
||||||
|
|
|
@ -127,7 +127,7 @@ struct Settings {
|
||||||
a fixed format to allow its progress to be monitored. Each
|
a fixed format to allow its progress to be monitored. Each
|
||||||
line starts with a "@". The following are defined:
|
line starts with a "@". The following are defined:
|
||||||
|
|
||||||
@ build-started <drvpath> <outpath> <system> <logfile>
|
@ build-started <drvpath> <outpath> <system> <logfile> <pid>
|
||||||
@ build-failed <drvpath> <outpath> <exitcode> <error text>
|
@ build-failed <drvpath> <outpath> <exitcode> <error text>
|
||||||
@ build-succeeded <drvpath> <outpath>
|
@ build-succeeded <drvpath> <outpath>
|
||||||
@ substituter-started <outpath> <substituter>
|
@ substituter-started <outpath> <substituter>
|
||||||
|
@ -139,6 +139,13 @@ struct Settings {
|
||||||
builders. */
|
builders. */
|
||||||
bool printBuildTrace;
|
bool printBuildTrace;
|
||||||
|
|
||||||
|
/* When true, 'buildDerivations' prefixes lines coming from builders so
|
||||||
|
that clients know exactly which line comes from which builder, and
|
||||||
|
which line comes from the daemon itself. The prefix for data coming
|
||||||
|
from builders is "log:PID:LEN:DATA" where PID uniquely identifies the
|
||||||
|
builder (PID is given in "build-started" traces.) */
|
||||||
|
bool multiplexedBuildOutput;
|
||||||
|
|
||||||
/* Amount of reserved space for the garbage collector
|
/* Amount of reserved space for the garbage collector
|
||||||
(/nix/var/nix/db/reserved). */
|
(/nix/var/nix/db/reserved). */
|
||||||
off_t reservedSize;
|
off_t reservedSize;
|
||||||
|
|
|
@ -6,7 +6,7 @@ namespace nix {
|
||||||
#define WORKER_MAGIC_1 0x6e697863
|
#define WORKER_MAGIC_1 0x6e697863
|
||||||
#define WORKER_MAGIC_2 0x6478696f
|
#define WORKER_MAGIC_2 0x6478696f
|
||||||
|
|
||||||
#define PROTOCOL_VERSION 0x162
|
#define PROTOCOL_VERSION 0x163
|
||||||
#define GET_PROTOCOL_MAJOR(x) ((x) & 0xff00)
|
#define GET_PROTOCOL_MAJOR(x) ((x) & 0xff00)
|
||||||
#define GET_PROTOCOL_MINOR(x) ((x) & 0x00ff)
|
#define GET_PROTOCOL_MINOR(x) ((x) & 0x00ff)
|
||||||
|
|
||||||
|
|
|
@ -594,7 +594,7 @@ static void performOp(bool trusted, unsigned int clientVersion,
|
||||||
if (name == "build-timeout" || name == "build-max-silent-time"
|
if (name == "build-timeout" || name == "build-max-silent-time"
|
||||||
|| name == "build-max-jobs" || name == "build-cores"
|
|| name == "build-max-jobs" || name == "build-cores"
|
||||||
|| name == "build-repeat"
|
|| name == "build-repeat"
|
||||||
|| name == "use-ssh-substituter")
|
|| name == "multiplexed-build-output")
|
||||||
settings.set(name, value);
|
settings.set(name, value);
|
||||||
else
|
else
|
||||||
settings.set(trusted ? name : "untrusted-" + name, value);
|
settings.set(trusted ? name : "untrusted-" + name, value);
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
#:use-module (gnu packages)
|
#:use-module (gnu packages)
|
||||||
#:use-module (gnu packages bootstrap)
|
#:use-module (gnu packages bootstrap)
|
||||||
#:use-module (ice-9 match)
|
#:use-module (ice-9 match)
|
||||||
|
#:use-module (ice-9 regex)
|
||||||
#:use-module (rnrs bytevectors)
|
#:use-module (rnrs bytevectors)
|
||||||
#:use-module (rnrs io ports)
|
#:use-module (rnrs io ports)
|
||||||
#:use-module (web uri)
|
#:use-module (web uri)
|
||||||
|
@ -1021,4 +1022,66 @@
|
||||||
(call-with-input-file (derivation->output-path drv2)
|
(call-with-input-file (derivation->output-path drv2)
|
||||||
read))))))
|
read))))))
|
||||||
|
|
||||||
|
(test-equal "multiplexed-build-output"
|
||||||
|
'("Hello from first." "Hello from second.")
|
||||||
|
(with-store store
|
||||||
|
(let* ((build (add-text-to-store store "build.sh"
|
||||||
|
"echo Hello from $NAME.; echo > $out"))
|
||||||
|
(bash (add-to-store store "bash" #t "sha256"
|
||||||
|
(search-bootstrap-binary "bash"
|
||||||
|
(%current-system))))
|
||||||
|
(drv1 (derivation store "one" bash
|
||||||
|
`("-e" ,build)
|
||||||
|
#:inputs `((,bash) (,build))
|
||||||
|
#:env-vars `(("NAME" . "first")
|
||||||
|
("x" . ,(random-text)))))
|
||||||
|
(drv2 (derivation store "two" bash
|
||||||
|
`("-e" ,build)
|
||||||
|
#:inputs `((,bash) (,build))
|
||||||
|
#:env-vars `(("NAME" . "second")
|
||||||
|
("x" . ,(random-text))))))
|
||||||
|
(set-build-options store
|
||||||
|
#:print-build-trace #t
|
||||||
|
#:multiplexed-build-output? #t
|
||||||
|
#:max-build-jobs 10)
|
||||||
|
(let ((port (open-output-string)))
|
||||||
|
;; Send the build log to PORT.
|
||||||
|
(parameterize ((current-build-output-port port))
|
||||||
|
(build-derivations store (list drv1 drv2)))
|
||||||
|
|
||||||
|
;; Retrieve the build log; make sure it contains valid "@ build-log"
|
||||||
|
;; traces that allow us to retrieve each builder's output (we assume
|
||||||
|
;; there's exactly one "build-output" trace for each builder, which is
|
||||||
|
;; reasonable.)
|
||||||
|
(let* ((log (get-output-string port))
|
||||||
|
(started (fold-matches
|
||||||
|
(make-regexp "@ build-started ([^ ]+) - ([^ ]+) ([^ ]+) ([0-9]+)")
|
||||||
|
log '() cons))
|
||||||
|
(done (fold-matches
|
||||||
|
(make-regexp "@ build-succeeded (.*) - (.*) (.*) (.*)")
|
||||||
|
log '() cons))
|
||||||
|
(output (fold-matches
|
||||||
|
(make-regexp "@ build-log ([[:digit:]]+) ([[:digit:]]+)\n([A-Za-z .*]+)\n")
|
||||||
|
log '() cons))
|
||||||
|
(drv-pid (lambda (name)
|
||||||
|
(lambda (m)
|
||||||
|
(let ((drv (match:substring m 1))
|
||||||
|
(pid (string->number
|
||||||
|
(match:substring m 4))))
|
||||||
|
(and (string-suffix? name drv) pid)))))
|
||||||
|
(pid-log (lambda (pid)
|
||||||
|
(lambda (m)
|
||||||
|
(let ((n (string->number
|
||||||
|
(match:substring m 1)))
|
||||||
|
(len (string->number
|
||||||
|
(match:substring m 2)))
|
||||||
|
(str (match:substring m 3)))
|
||||||
|
(and (= pid n)
|
||||||
|
(= (string-length str) (- len 1))
|
||||||
|
str)))))
|
||||||
|
(pid1 (any (drv-pid "one.drv") started))
|
||||||
|
(pid2 (any (drv-pid "two.drv") started)))
|
||||||
|
(list (any (pid-log pid1) output)
|
||||||
|
(any (pid-log pid2) output)))))))
|
||||||
|
|
||||||
(test-end "store")
|
(test-end "store")
|
||||||
|
|
Reference in New Issue