;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2015, 2016, 2017, 2018, 2019, 2020 Ludovic Courtès ;;; Copyright © 2019 Simon Tournier ;;; ;;; This file is part of GNU Guix. ;;; ;;; GNU Guix 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; either version 3 of the License, or (at ;;; your option) any later version. ;;; ;;; GNU Guix 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 GNU Guix. If not, see . (define-module (guix scripts size) #:use-module (guix ui) #:use-module (guix scripts) #:use-module (guix scripts build) #:use-module (guix store) #:use-module (guix monads) #:use-module (guix combinators) #:use-module (guix packages) #:use-module (guix derivations) #:use-module (gnu packages) #:use-module (srfi srfi-1) #:use-module (srfi srfi-9) #:use-module (srfi srfi-11) #:use-module (srfi srfi-26) #:use-module (srfi srfi-34) #:use-module (srfi srfi-37) #:use-module (ice-9 match) #:use-module (ice-9 format) #:use-module (ice-9 vlist) #:export (profile? profile-file profile-self-size profile-closure-size store-profile guix-size)) ;; Size profile of a store item. (define-record-type (profile file self-size closure-size) profile? (file profile-file) ;store item (self-size profile-self-size) ;size in bytes (closure-size profile-closure-size)) ;size of dependencies in bytes (define substitutable-path-info* (store-lift substitutable-path-info)) (define (file-size item) "Return the size in bytes of ITEM, resorting to information from substitutes if ITEM is not in the store." (mlet %store-monad ((info (query-path-info* item))) (if info (return (path-info-nar-size info)) (mlet %store-monad ((info (substitutable-path-info* (list item)))) (match info ((info) ;; The nar size is an approximation, but a good one. (return (substitutable-nar-size info))) (() (leave (G_ "no available substitute information for '~a'~%") item))))))) (define profile-closure name1 self1 total1) ($ name2 self2 total2)) (< total1 total2)))) (define profile-self name1 self1 total1) ($ name2 self2 total2)) (< self1 self2)))) (define* (display-profile profile #:optional (port (current-output-port)) #:key (profile name self total) (format port "~64a ~6,1f ~6,1f ~5,1f%\n" name (/ total MiB) (/ self MiB) (* 100. (/ self whole 1.))))) (sort profile (negate profile) items))) (values (delete-duplicates (append (requisites store local) (substitutable-requisites store missing))) store)))) (define (store-profile items) "Return as a monadic value a list of objects representing the profile of ITEMS and their requisites." (mlet* %store-monad ((refs (>>= (requisites* items) (lambda (refs) (return (delete-duplicates (append items refs)))))) (sizes (mapm %store-monad (lambda (item) (>>= (file-size item) (lambda (size) (return (cons item size))))) refs))) (define size-table (fold (lambda (pair result) (match pair ((item . size) (vhash-cons item size result)))) vlist-null sizes)) (define (dependency-size item) (mlet %store-monad ((deps (requisites* (list item)))) (foldm %store-monad (lambda (item total) (return (+ (match (vhash-assoc item size-table) ((_ . size) size)) total))) 0 (delete-duplicates (cons item deps))))) (mapm %store-monad (match-lambda ((item . size) (mlet %store-monad ((dependencies (dependency-size item))) (return (profile item size dependencies))))) sizes))) (define* (ensure-store-item spec-or-item #:optional (system (%current-system))) "Return a store file name. If SPEC-OR-ITEM is a store file name, return it as is. Otherwise, assume SPEC-OR-ITEM is a package output specification such as \"guile:debug\" or \"gcc-4.8\" and return its store file name." (with-monad %store-monad (if (store-path? spec-or-item) (return spec-or-item) (let-values (((package output) (specification->package+output spec-or-item))) (mlet %store-monad ((drv (package->derivation package system))) ;; Note: we don't try building DRV like 'guix archive' does ;; because we don't have to since we can instead rely on ;; substitute meta-data. (return (derivation->output-path drv output))))))) ;;; ;;; Charts. ;;; ;; Autoload Guile-Charting. ;; XXX: Use this hack instead of #:autoload to avoid compilation errors. ;; See . (module-autoload! (current-module) '(charting) '(make-page-map)) (define (profile->page-map profiles file) "Write a 'page map' chart of PROFILES, a list of objects, to FILE, the name of a PNG file." (define (strip name) (string-drop name (+ (string-length (%store-prefix)) 28))) (define data (fold2 (lambda (profile result offset) (match profile (($ name self) (let ((self (inexact->exact (round (/ self (expt 2. 10)))))) (values `((,(strip name) ,offset . ,self) ,@result) (+ offset self)))))) '() 0 (sort profiles (match-lambda* ((($ name1 self1 total1) ($ name2 self2 total2)) (> total1 total2)))))) ;; TRANSLATORS: This is the title of a graph, meaning that the graph ;; represents a profile of the store (the "store" being the place where ;; packages are stored.) (make-page-map (G_ "store profile") data #:write-to-png file)) ;;; ;;; Options. ;;; (define (show-help) (display (G_ "Usage: guix size [OPTION]... PACKAGE|STORE-ITEM Report the size of the PACKAGE or STORE-ITEM, with its dependencies.\n")) (display (G_ " --substitute-urls=URLS fetch substitute from URLS if they are authorized")) ;; TRANSLATORS: "closure" and "self" must not be translated. (display (G_ " --sort=KEY sort according to KEY--\"closure\" or \"self\"")) (display (G_ " -m, --map-file=FILE write to FILE a graphical map of disk usage")) (newline) (display (G_ " -L, --load-path=DIR prepend DIR to the package module search path")) (newline) (display (G_ " -h, --help display this help and exit")) (display (G_ " -V, --version display version information and exit")) (newline) (show-native-build-options-help) (newline) (show-bug-report-information)) (define %options ;; Specifications of the command-line options. (cons* (option '("substitute-urls") #t #f (lambda (opt name arg result . rest) (apply values (alist-cons 'substitute-urls (string-tokenize arg) (alist-delete 'substitute-urls result)) rest))) (option '("sort") #t #f (lambda (opt name arg result . rest) (match arg ("closure" (alist-cons 'profile system) files)) (profile (store-profile items))) (if map-file (begin (profile->page-map profile map-file) (return #t)) (display-profile* profile (current-output-port) #:profile