weather: Report continuous integration stats.
* guix/scripts/weather.scm (histogram, throughput, queued-subset): New procedures. (report-server-coverage): Report CI information. * doc/guix.texi (Invoking guix weather): Document it.
This commit is contained in:
		
							parent
							
								
									b3517f3f9f
								
							
						
					
					
						commit
						183445a6ed
					
				
					 2 changed files with 120 additions and 3 deletions
				
			
		|  | @ -7912,15 +7912,27 @@ https://guix.example.org | ||||||
|   19,824.2 MiB on disk (uncompressed) |   19,824.2 MiB on disk (uncompressed) | ||||||
|   0.030 seconds per request (182.9 seconds in total) |   0.030 seconds per request (182.9 seconds in total) | ||||||
|   33.5 requests per second |   33.5 requests per second | ||||||
|  | 
 | ||||||
|  |   9.8% (342 out of 3,470) of the missing items are queued | ||||||
|  |   867 queued builds | ||||||
|  |       x86_64-linux: 518 (59.7%) | ||||||
|  |       i686-linux: 221 (25.5%) | ||||||
|  |       aarch64-linux: 128 (14.8%) | ||||||
|  |   build rate: 23.41 builds per hour | ||||||
|  |       x86_64-linux: 11.16 builds per hour | ||||||
|  |       i686-linux: 6.03 builds per hour | ||||||
|  |       aarch64-linux: 6.41 builds per hour | ||||||
| @end example | @end example | ||||||
| 
 | 
 | ||||||
|  | @cindex continuous integration, statistics | ||||||
| As you can see, it reports the fraction of all the packages for which | As you can see, it reports the fraction of all the packages for which | ||||||
| substitutes are available on the server---regardless of whether | substitutes are available on the server---regardless of whether | ||||||
| substitutes are enabled, and regardless of whether this server's signing | substitutes are enabled, and regardless of whether this server's signing | ||||||
| key is authorized.  It also reports the size of the compressed archives | key is authorized.  It also reports the size of the compressed archives | ||||||
| (``nars'') provided by the server, the size the corresponding store | (``nars'') provided by the server, the size the corresponding store | ||||||
| items occupy in the store (assuming deduplication is turned off), and | items occupy in the store (assuming deduplication is turned off), and | ||||||
| the server's throughput. | the server's throughput.  The second part gives continuous integration | ||||||
|  | (CI) statistics, if the server supports it. | ||||||
| 
 | 
 | ||||||
| To achieve that, @command{guix weather} queries over HTTP(S) meta-data | To achieve that, @command{guix weather} queries over HTTP(S) meta-data | ||||||
| (@dfn{narinfos}) for all the relevant store items.  Like @command{guix | (@dfn{narinfos}) for all the relevant store items.  Like @command{guix | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| ;;; GNU Guix --- Functional package management for GNU | ;;; GNU Guix --- Functional package management for GNU | ||||||
| ;;; Copyright © 2017 Ludovic Courtès <ludo@gnu.org> | ;;; Copyright © 2017, 2018 Ludovic Courtès <ludo@gnu.org> | ||||||
| ;;; Copyright © 2017 Ricardo Wurmus <rekado@elephly.net> | ;;; Copyright © 2017 Ricardo Wurmus <rekado@elephly.net> | ||||||
| ;;; | ;;; | ||||||
| ;;; This file is part of GNU Guix. | ;;; This file is part of GNU Guix. | ||||||
|  | @ -29,11 +29,14 @@ | ||||||
|   #:use-module (guix grafts) |   #:use-module (guix grafts) | ||||||
|   #:use-module ((guix build syscalls) #:select (terminal-columns)) |   #:use-module ((guix build syscalls) #:select (terminal-columns)) | ||||||
|   #:use-module (guix scripts substitute) |   #:use-module (guix scripts substitute) | ||||||
|  |   #:use-module (guix http-client) | ||||||
|  |   #:use-module (guix ci) | ||||||
|   #:use-module (gnu packages) |   #:use-module (gnu packages) | ||||||
|   #:use-module (web uri) |   #:use-module (web uri) | ||||||
|   #:use-module (srfi srfi-1) |   #:use-module (srfi srfi-1) | ||||||
|   #:use-module (srfi srfi-19) |   #:use-module (srfi srfi-19) | ||||||
|   #:use-module (srfi srfi-26) |   #:use-module (srfi srfi-26) | ||||||
|  |   #:use-module (srfi srfi-34) | ||||||
|   #:use-module (srfi srfi-37) |   #:use-module (srfi srfi-37) | ||||||
|   #:use-module (ice-9 match) |   #:use-module (ice-9 match) | ||||||
|   #:use-module (ice-9 format) |   #:use-module (ice-9 format) | ||||||
|  | @ -100,6 +103,57 @@ values." | ||||||
| (define-syntax-rule (let/time ((time result exp)) body ...) | (define-syntax-rule (let/time ((time result exp)) body ...) | ||||||
|   (call-with-time (lambda () exp) (lambda (time result) body ...))) |   (call-with-time (lambda () exp) (lambda (time result) body ...))) | ||||||
| 
 | 
 | ||||||
|  | (define (histogram field proc seed lst) | ||||||
|  |   "Return an alist giving a histogram of all the values of FIELD for elements | ||||||
|  | of LST. FIELD must be a one element procedure that returns a field's value. | ||||||
|  | For each FIELD value, call PROC with the previous field-specific result. | ||||||
|  | Example: | ||||||
|  | 
 | ||||||
|  |   (histogram car (lambda (x n) (+ 1 n)) 0 '((a . x)(b . y)(a . z))) | ||||||
|  |   => ((a . 2) (b . 1)) | ||||||
|  | 
 | ||||||
|  | meaning that we have two a's and one b." | ||||||
|  |   (let loop ((lst lst) | ||||||
|  |              (result '())) | ||||||
|  |     (match lst | ||||||
|  |       (() | ||||||
|  |        result) | ||||||
|  |       ((head . tail) | ||||||
|  |        (let ((value (field head))) | ||||||
|  |          (loop tail | ||||||
|  |                (match (assoc-ref result value) | ||||||
|  |                  (#f | ||||||
|  |                   `((,value . ,(proc head seed)) ,@result)) | ||||||
|  |                  (previous | ||||||
|  |                   `((,value . ,(proc head previous)) | ||||||
|  |                     ,@(alist-delete value result)))))))))) | ||||||
|  | 
 | ||||||
|  | (define (throughput lst timestamp) | ||||||
|  |   "Return the throughput, in items per second, given the elements of LST, | ||||||
|  | calling TIMESTAMP to get the \"timestamp\" of each item." | ||||||
|  |   (let ((oldest (reduce min +inf.0 (map build-timestamp lst))) | ||||||
|  |         (now    (time-second (current-time time-utc)))) | ||||||
|  |     (/ (length lst) (- now oldest) 1.))) | ||||||
|  | 
 | ||||||
|  | (define (queued-subset queue items) | ||||||
|  |   "Return the subset of ITEMS, a list of store file names, that appears in | ||||||
|  | QUEUE, a list of builds.  Return #f if elements in QUEUE lack information | ||||||
|  | about the derivations queued, as is the case with Hydra." | ||||||
|  |   (define queued | ||||||
|  |     (append-map (lambda (build) | ||||||
|  |                   (match (false-if-exception | ||||||
|  |                           (read-derivation-from-file (build-derivation build))) | ||||||
|  |                     (#f | ||||||
|  |                      '()) | ||||||
|  |                     (drv | ||||||
|  |                      (match (derivation->output-paths drv) | ||||||
|  |                        (((names . items) ...) items))))) | ||||||
|  |                 queue)) | ||||||
|  | 
 | ||||||
|  |   (if (any (negate build-derivation) queue) | ||||||
|  |       #f                                          ;no derivation information | ||||||
|  |       (lset-intersection string=? queued items))) | ||||||
|  | 
 | ||||||
| (define (report-server-coverage server items) | (define (report-server-coverage server items) | ||||||
|   "Report the subset of ITEMS available as substitutes on SERVER." |   "Report the subset of ITEMS available as substitutes on SERVER." | ||||||
|   (define MiB (* (expt 2 20) 1.)) |   (define MiB (* (expt 2 20) 1.)) | ||||||
|  | @ -111,6 +165,8 @@ values." | ||||||
|     (format #t "~a~%" server) |     (format #t "~a~%" server) | ||||||
|     (let ((obtained  (length narinfos)) |     (let ((obtained  (length narinfos)) | ||||||
|           (requested (length items)) |           (requested (length items)) | ||||||
|  |           (missing   (lset-difference string=? | ||||||
|  |                                       items (map narinfo-path narinfos))) | ||||||
|           (sizes     (filter-map narinfo-file-size narinfos)) |           (sizes     (filter-map narinfo-file-size narinfos)) | ||||||
|           (time      (+ (time-second time) |           (time      (+ (time-second time) | ||||||
|                         (/ (time-nanosecond time) 1e9)))) |                         (/ (time-nanosecond time) 1e9)))) | ||||||
|  | @ -131,7 +187,56 @@ values." | ||||||
|       (format #t (G_ "  ~,3h seconds per request (~,1h seconds in total)~%") |       (format #t (G_ "  ~,3h seconds per request (~,1h seconds in total)~%") | ||||||
|               (/ time requested 1.) time) |               (/ time requested 1.) time) | ||||||
|       (format #t (G_ "  ~,1h requests per second~%") |       (format #t (G_ "  ~,1h requests per second~%") | ||||||
|               (/ requested time 1.))))) |               (/ requested time 1.)) | ||||||
|  | 
 | ||||||
|  |       (guard (c ((http-get-error? c) | ||||||
|  |                  (if (= 404 (http-get-error-code c)) | ||||||
|  |                      (format (current-error-port) | ||||||
|  |                              (G_ "  (continuous integration information \ | ||||||
|  | unavailable)~%")) | ||||||
|  |                      (format (current-error-port) | ||||||
|  |                              (G_ "  '~a' returned ~a (~s)~%") | ||||||
|  |                              (uri->string (http-get-error-uri c)) | ||||||
|  |                              (http-get-error-code c) | ||||||
|  |                              (http-get-error-reason c))))) | ||||||
|  |         (let* ((max    %query-limit) | ||||||
|  |                (queue  (queued-builds server max)) | ||||||
|  |                (len    (length queue)) | ||||||
|  |                (histo  (histogram build-system | ||||||
|  |                                   (lambda (build count) | ||||||
|  |                                     (+ 1 count)) | ||||||
|  |                                   0 queue))) | ||||||
|  |           (newline) | ||||||
|  |           (unless (null? missing) | ||||||
|  |             (let ((missing (length missing))) | ||||||
|  |               (match (queued-subset queue missing) | ||||||
|  |                 (#f #f) | ||||||
|  |                 ((= length queued) | ||||||
|  |                  (format #t (G_ "  ~,1f% (~h out of ~h) of the missing items \ | ||||||
|  | are queued~%") | ||||||
|  |                          (* 100. (/ queued missing)) | ||||||
|  |                          queued missing))))) | ||||||
|  | 
 | ||||||
|  |           (if (>= len max) | ||||||
|  |               (format #t (G_ "  at least ~h queued builds~%") len) | ||||||
|  |               (format #t (G_ "  ~h queued builds~%") len)) | ||||||
|  |           (for-each (match-lambda | ||||||
|  |                       ((system . count) | ||||||
|  |                        (format #t (G_ "      ~a: ~a (~0,1f%)~%") | ||||||
|  |                                system count (* 100. (/ count len))))) | ||||||
|  |                     histo)) | ||||||
|  | 
 | ||||||
|  |         (let* ((latest     (latest-builds server)) | ||||||
|  |                (builds/sec (throughput latest build-timestamp))) | ||||||
|  |           (format #t (G_ "  build rate: ~1,2f builds per hour~%") | ||||||
|  |                   (* builds/sec 3600.)) | ||||||
|  |           (for-each (match-lambda | ||||||
|  |                       ((system . builds) | ||||||
|  |                        (format #t (G_ "      ~a: ~,2f builds per hour~%") | ||||||
|  |                                system | ||||||
|  |                                (* (throughput builds build-timestamp) | ||||||
|  |                                   3600.)))) | ||||||
|  |                     (histogram build-system cons '() latest))))))) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
| ;;; | ;;; | ||||||
|  |  | ||||||
		Reference in a new issue