Subject: Re: [bug#44460] Add copyright lines

From: John Soo <[email-masked]>
Date: Thu, 12 Nov 2020 21:33:52 -0800

Hi Ludo,

Ludovic Courtès <[email-masked]> writes:

> Yes it does!  Initially I wondered if it was a term used in recutils,
> but apparently it’s not.

Do you think it is the best term to use?

>> An aside - Probably to be entirely honest about normalizing the output,
>> locks really would be in a separate record set too.
>
> Yeah.

I made Lock a separate record set. This exposes a limitation with
recutils. It is not possible to join more than two record sets together
(though they mentioned it as a goal on IRC).

>> Another challenge is making sure the user can understand what
>> "normalized" means.  I am not sure readers of the manual/cli help will
>> be able to infer what it means from context.  On the other hand, it is
>> such a small use case that it seems imbalanced to provide a lot of
>> background for the term "normal". What do you think?
>
> Sure.
>
> Thinking more about it, to me the appeal of recutils is that it’s both
> human- and machine-readable.  But here we end up having a specific
> machine-readable variant.  But yeah, maybe that’s unavoidable.

I suppose the normalized version is a little less human-readable.  It
would be behind a flag, though.  Is that a reasonable compromise?

After some thought I realize that the normalized version isn't that much
more useful than the default, but it does enable things like:

---- Example ----
$ guix processes -f normalized \
  | recsel \
    -t ChildProcess \
    -j Session \
    -p PID,Session.PID \
  | recfmt '{{PID}} {{Session.PID}}'
23607 2356724713 2356725002 23576
---- Example ----

This will format all the (PID, Session) pairs. Whereas the
non-normalized version would only print one PID given the following.

---- Example ----
$ guix processes \
  | recsel -p ChildPID,SessionPID \
  | recfmt '{{ChildPID}} {{SessionPID}}'
23607 23567
---

>>>> I wouldn't be opposed to splitting ChildProcess into ChildPID and
>>>> ChildCommand.  I would like it best if that change was made in addition
>>>> to adding the normalized version, since the normalized version allows
>>>> more functionality.
>>>
>>> I would think it’s OK to break compatibility on just these
>>> “ChildProcess” fields.
>>
>> Ok. Would it be ok if I put that in a separate commit?
>
> Yes (you mean in addition to ‘-f normalized’, right?).

I changed ChildProcess: pid: command to ChildPID and ChildCommand in the
default in a separate commit.

Thanks again,

John

>From 6082c559d1200e632b3fb45eb0633d28829667a1 Mon Sep 17 00:00:00 2001
From: John Soo <[email-masked]>
Date: Thu, 12 Nov 2020 21:16:48 -0800
Subject: [PATCH 1/2] processes: Put ChildProcess and ChildPID on separate
 lines.

* guix/scripts/processes.scm: Put child process information in
separate fields.
* doc/guix.texi: Document change in output of guix processes.
---
 doc/guix.texi              | 22 +++++++++++++---------
 guix/scripts/processes.scm |  6 ++++--
 2 files changed, 17 insertions(+), 11 deletions(-)

diff --git a/doc/guix.texi b/doc/guix.texi
index 5e3e0435b4..e8814c686c 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -82,6 +82,7 @@ Copyright @copyright{} 2020 Pierre Langlois@*
 Copyright @copyright{} 2020 pinoaffe@*
 Copyright @copyright{} 2020 André Batista@*
 Copyright @copyright{} 2020 Alexandru-Sergiu Marton@*
+Copyright @copyright{} 2020 John Soo@*
 
 Permission is granted to copy, distribute and/or modify this document
 under the terms of the GNU Free Documentation License, Version 1.3 or
@@ -12661,9 +12662,12 @@ ClientCommand: cuirass --cache-directory /var/cache/cuirass @dots{}
 LockHeld: /gnu/store/@dots{}-perl-ipc-cmd-0.96.lock
 LockHeld: /gnu/store/@dots{}-python-six-bootstrap-1.11.0.lock
 LockHeld: /gnu/store/@dots{}-libjpeg-turbo-2.0.0.lock
-ChildProcess: 20495: guix offload x86_64-linux 7200 1 28800
-ChildProcess: 27733: guix offload x86_64-linux 7200 1 28800
-ChildProcess: 27793: guix offload x86_64-linux 7200 1 28800
+ChildPID: 20495
+ChildCommand: guix offload x86_64-linux 7200 1 28800
+ChildPID: 27733
+ChildCommand: guix offload x86_64-linux 7200 1 28800
+ChildPID: 27793
+ChildCommand: guix offload x86_64-linux 7200 1 28800
 @end example
 
 In this example we see that @command{guix-daemon} has three clients:
@@ -12672,12 +12676,12 @@ integration tool; their process identifier (PID) is given by the
 @code{ClientPID} field.  The @code{SessionPID} field gives the PID of the
 @command{guix-daemon} sub-process of this particular session.
 
-The @code{LockHeld} fields show which store items are currently locked by this
-session, which corresponds to store items being built or substituted (the
-@code{LockHeld} field is not displayed when @command{guix processes} is not
-running as root).  Last, by looking at the @code{ChildProcess} field, we
-understand that these three builds are being offloaded (@pxref{Daemon Offload
-Setup}).
+The @code{LockHeld} fields show which store items are currently locked
+by this session, which corresponds to store items being built or
+substituted (the @code{LockHeld} field is not displayed when
+@command{guix processes} is not running as root).  Last, by looking at
+the @code{ChildPID} and @code{ChildCommand} fields, we understand that
+these three builds are being offloaded (@pxref{Daemon Offload Setup}).
 
 The output is in Recutils format so we can use the handy @command{recsel}
 command to select sessions of interest (@pxref{Selection Expressions,,,
diff --git a/guix/scripts/processes.scm b/guix/scripts/processes.scm
index b4ca7b1687..3a7ea0b89c 100644
--- a/guix/scripts/processes.scm
+++ b/guix/scripts/processes.scm
@@ -1,5 +1,6 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2018, 2019, 2020 Ludovic Courtès <[email-masked]>
+;;; Copyright © 2020 John Soo <[email-masked]>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -188,8 +189,9 @@ active sessions, and the master 'guix-daemon' process."
               (format port "LockHeld: ~a~%" lock))
             (daemon-session-locks-held session))
   (for-each (lambda (process)
-              (format port "ChildProcess: ~a:~{ ~a~}~%"
-                      (process-id process)
+              (format port "ChildPID: ~a~%"
+                      (process-id process))
+              (format port "ChildCommand: :~{ ~a~}~%"
                       (process-command process)))
             (daemon-session-children session)))
 
-- 
2.29.1

>From 81cdc826a89b4f5ee480faa620f3e8f3e12d8979 Mon Sep 17 00:00:00 2001
From: John Soo <[email-masked]>
Date: Wed, 4 Nov 2020 07:51:52 -0800
Subject: [PATCH 2/2] processes: Optionally normalize recutils output.

* guix/scripts/processes.scm: Add "format" and "list-formats" flag.
* doc/guix.texi: Document new flags.
---
 doc/guix.texi              |  38 ++++++++++
 guix/scripts/processes.scm | 144 ++++++++++++++++++++++++++++++++-----
 2 files changed, 163 insertions(+), 19 deletions(-)

diff --git a/doc/guix.texi b/doc/guix.texi
index e8814c686c..7da24977cc 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -12695,6 +12695,44 @@ ClientPID: 19419
 ClientCommand: cuirass --cache-directory /var/cache/cuirass @dots{}
 @end example
 
+Additional options are listed below.
+
+@table @code
+@item --format=@var{format}
+@itemx -f @var{format}
+Produce output in the specified @var{format}, one of:
+
+@table @code
+@item recutils
+The default option. It outputs a set of Session recutils records
+that include each ChildProcess as a field.
+
+@item recutils-normalized
+Normalize the output records into record sets (@pxref{Record Sets,,,
+recutils, GNU recutils manual}).  Normalizing into record sets allows
+joins across record types.  The example below lists the PID of each
+ChildProcess and the associated PID for Session that spawned the
+ChildProcess where the Session was started using guix build.
+
+@example
+$ guix processes --format=recutils-normalized | \
+    recsel \
+    -j Session \
+    -t ChildProcess \
+    -p Session.PID,PID \
+    -e 'Session.ClientCommand ~ "guix build'"
+PID: 4435
+Session_PID: 4278
+
+PID: 4554
+Session_PID: 4278
+
+PID: 4646
+Session_PID: 4278
+@end example
+@end table
+@end table
+
 @node System Configuration
 @chapter System Configuration
 
diff --git a/guix/scripts/processes.scm b/guix/scripts/processes.scm
index 3a7ea0b89c..bcc541badb 100644
--- a/guix/scripts/processes.scm
+++ b/guix/scripts/processes.scm
@@ -177,6 +177,9 @@ active sessions, and the master 'guix-daemon' process."
     (values (filter-map child-process->session children)
             master)))
 
+(define (lock->recutils lock port)
+  (format port "LockHeld: ~a~%" lock))
+
 (define (daemon-session->recutils session port)
   "Display SESSION information in recutils format on PORT."
   (format port "SessionPID: ~a~%"
@@ -185,8 +188,7 @@ active sessions, and the master 'guix-daemon' process."
           (process-id (daemon-session-client session)))
   (format port "ClientCommand:~{ ~a~}~%"
           (process-command (daemon-session-client session)))
-  (for-each (lambda (lock)
-              (format port "LockHeld: ~a~%" lock))
+  (for-each (lambda (lock) (lock->recutils lock port))
             (daemon-session-locks-held session))
   (for-each (lambda (process)
               (format port "ChildPID: ~a~%"
@@ -195,19 +197,102 @@ active sessions, and the master 'guix-daemon' process."
                       (process-command process)))
             (daemon-session-children session)))
 
+(define (daemon-sessions->recutils port sessions)
+  "Display denormalized SESSIONS information to PORT."
+  (for-each (lambda (session)
+              (daemon-session->recutils session port)
+              (newline port))
+            sessions))
+
+(define session-rec-type
+  "%rec: Session
+%type: PID int
+%type: ClientPID int
+%key: PID
+%mandatory: ClientPID ClientCommand")
+
+(define lock-rec-type
+  "%rec: Lock
+%mandatory: LockHeld
+%type: Session rec Session")
+
+(define child-process-rec-type
+  "%rec: ChildProcess
+%type: PID int
+%type: Session rec Session
+%key: PID
+%mandatory: Command")
+
+(define (session-key->recutils session port)
+  "Display SESSION PID as a recutils field on PORT."
+  (format
+   port "Session: ~a"
+   (process-id (daemon-session-process session))))
+
+(define (session-scalars->normalized-record session port)
+  "Display SESSION scalar fields to PORT in normalized form."
+  (format port "PID: ~a~%"
+          (process-id (daemon-session-process session)))
+  (format port "ClientPID: ~a~%"
+          (process-id (daemon-session-client session)))
+  (format port "ClientCommand:~{ ~a~}~%"
+          (process-command (daemon-session-client session))))
+
+(define (child-process->normalized-record process port)
+  "Display PROCESS record on PORT in normalized form"
+  (format port "PID: ~a" (process-id process))
+  (newline port)
+  (format port "Command:~{ ~a~}" (process-command process)))
+
+(define (daemon-sessions->normalized-record port sessions)
+  "Display SESSIONS recutils on PORT in normalized form"
+  (display session-rec-type port)
+  (newline port)
+  (newline port)
+  (for-each (lambda (session)
+              (session-scalars->normalized-record session port)
+              (newline port))
+            sessions)
+
+  (display lock-rec-type port)
+  (newline port)
+  (newline port)
+  (for-each (lambda (session)
+              (for-each (lambda (lock)
+                          (lock->recutils "testing testing" port)
+                          (session-key->recutils session port)
+                          (newline port)
+                          (newline port))
+                        (daemon-session-locks-held session)))
+            sessions)
+
+  (display child-process-rec-type port)
+  (newline port)
+  (newline port)
+  (for-each (lambda (session)
+              (for-each (lambda (process)
+                          (child-process->normalized-record process port)
+                          (newline port)
+                          (session-key->recutils session port)
+                          (newline port)
+                          (newline port))
+                        (daemon-session-children session)))
+            sessions))
+
 
 ;;;
 ;;; Options.
 ;;;
 
-(define %options
-  (list (option '(#\h "help") #f #f
-                (lambda args
-                  (show-help)
-                  (exit 0)))
-        (option '(#\V "version") #f #f
-                (lambda args
-                  (show-version-and-exit "guix processes")))))
+(define %available-formats
+  '("recutils" "normalized"))
+
+(define (list-formats)
+  (display (G_ "The available formats are:\n"))
+  (newline)
+  (for-each (lambda (f)
+              (format #t "  - ~a~%" f))
+            %available-formats))
 
 (define (show-help)
   (display (G_ "Usage: guix processes
@@ -218,8 +303,33 @@ List the current Guix sessions and their processes."))
   (display (G_ "
   -V, --version          display version information and exit"))
   (newline)
+  (display (G_ "
+  -f, --format=FORMAT    display results as normalized record sets"))
+  (display (G_ "
+  --list-formats         display available formats"))
+  (newline)
   (show-bug-report-information))
 
+(define %options
+  (list (option '(#\h "help") #f #f
+                (lambda args
+                  (show-help)
+                  (exit 0)))
+        (option '(#\V "version") #f #f
+                (lambda args
+                  (show-version-and-exit "guix processes")))
+        (option '(#\f "format") #t #f
+                (lambda (opt name arg result)
+                  (unless (member arg %available-formats)
+                    (leave (G_ "~a: unsupported output format~%") arg))
+                  (alist-cons 'format (string->symbol arg) result)))
+        (option '("list-formats") #f #f
+                (lambda (opt name arg result)
+                  (list-formats)
+                  (exit 0)))))
+
+(define %default-options '((format . recutils)))
+
 
 ;;;
 ;;; Entry point.
@@ -229,17 +339,13 @@ List the current Guix sessions and their processes."))
   (category plumbing)
   (synopsis "list currently running sessions")
   (define options
-    (args-fold* args %options
-                (lambda (opt name arg result)
-                  (leave (G_ "~A: unrecognized option~%") name))
-                cons
-                '()))
+    (parse-command-line args %options (list %default-options)))
 
   (with-paginated-output-port port
-    (for-each (lambda (session)
-                (daemon-session->recutils session port)
-                (newline port))
-              (daemon-sessions))
+    (match (assoc-ref options 'format)
+      ('normalized
+       (daemon-sessions->normalized-record port (daemon-sessions)))
+      (_ (daemon-sessions->recutils port (daemon-sessions))))
 
     ;; Pass 'R' (instead of 'r') so 'less' correctly estimates line length.
     #:less-options "FRX"))
-- 
2.29.1