Commit | Line | Data |
---|---|---|
fcc58db6 | 1 | ;;; GNU Guix --- Functional package management for GNU |
1199da08 | 2 | ;;; Copyright © 2015, 2016, 2017, 2018, 2019 Ludovic Courtès <ludo@gnu.org> |
fcc58db6 LC |
3 | ;;; |
4 | ;;; This file is part of GNU Guix. | |
5 | ;;; | |
6 | ;;; GNU Guix is free software; you can redistribute it and/or modify it | |
7 | ;;; under the terms of the GNU General Public License as published by | |
8 | ;;; the Free Software Foundation; either version 3 of the License, or (at | |
9 | ;;; your option) any later version. | |
10 | ;;; | |
11 | ;;; GNU Guix is distributed in the hope that it will be useful, but | |
12 | ;;; WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | ;;; GNU General Public License for more details. | |
15 | ;;; | |
16 | ;;; You should have received a copy of the GNU General Public License | |
17 | ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. | |
18 | ||
19 | (define-module (guix scripts size) | |
20 | #:use-module (guix ui) | |
88981dd3 | 21 | #:use-module (guix scripts) |
fcc58db6 LC |
22 | #:use-module (guix store) |
23 | #:use-module (guix monads) | |
958dd3ce | 24 | #:use-module (guix combinators) |
3667bb6c | 25 | #:use-module (guix grafts) |
fcc58db6 LC |
26 | #:use-module (guix packages) |
27 | #:use-module (guix derivations) | |
28 | #:use-module (gnu packages) | |
29 | #:use-module (srfi srfi-1) | |
30 | #:use-module (srfi srfi-9) | |
31 | #:use-module (srfi srfi-11) | |
da2d893e | 32 | #:use-module (srfi srfi-26) |
fcc58db6 LC |
33 | #:use-module (srfi srfi-34) |
34 | #:use-module (srfi srfi-37) | |
fcc58db6 LC |
35 | #:use-module (ice-9 match) |
36 | #:use-module (ice-9 format) | |
1199da08 | 37 | #:use-module (ice-9 vlist) |
fcc58db6 LC |
38 | #:export (profile? |
39 | profile-file | |
40 | profile-self-size | |
41 | profile-closure-size | |
42 | store-profile | |
43 | ||
44 | guix-size)) | |
45 | ||
46 | ;; Size profile of a store item. | |
47 | (define-record-type <profile> | |
48 | (profile file self-size closure-size) | |
49 | profile? | |
50 | (file profile-file) ;store item | |
51 | (self-size profile-self-size) ;size in bytes | |
52 | (closure-size profile-closure-size)) ;size of dependencies in bytes | |
53 | ||
fcc58db6 LC |
54 | (define substitutable-path-info* |
55 | (store-lift substitutable-path-info)) | |
56 | ||
83bde59f LC |
57 | (define (file-size item) |
58 | "Return the size in bytes of ITEM, resorting to information from substitutes | |
59 | if ITEM is not in the store." | |
60 | (mlet %store-monad ((info (query-path-info* item))) | |
61 | (if info | |
62 | (return (path-info-nar-size info)) | |
fcc58db6 LC |
63 | (mlet %store-monad ((info (substitutable-path-info* (list item)))) |
64 | (match info | |
65 | ((info) | |
66 | ;; The nar size is an approximation, but a good one. | |
67 | (return (substitutable-nar-size info))) | |
68 | (() | |
69daee23 | 69 | (leave (G_ "no available substitute information for '~a'~%") |
fcc58db6 LC |
70 | item))))))) |
71 | ||
a6c1fe82 LC |
72 | (define profile-closure<? |
73 | (match-lambda* | |
74 | ((($ <profile> name1 self1 total1) | |
75 | ($ <profile> name2 self2 total2)) | |
76 | (< total1 total2)))) | |
77 | ||
78 | (define profile-self<? | |
79 | (match-lambda* | |
80 | ((($ <profile> name1 self1 total1) | |
81 | ($ <profile> name2 self2 total2)) | |
82 | (< self1 self2)))) | |
83 | ||
84 | (define* (display-profile profile #:optional (port (current-output-port)) | |
85 | #:key (profile<? profile-closure<?)) | |
86 | "Display PROFILE, a list of PROFILE objects, to PORT. Sort entries | |
87 | according to PROFILE<?." | |
fcc58db6 LC |
88 | (define MiB (expt 2 20)) |
89 | ||
90 | (format port "~64a ~8a ~a\n" | |
69daee23 | 91 | (G_ "store item") (G_ "total") (G_ "self")) |
fcc58db6 LC |
92 | (let ((whole (reduce + 0 (map profile-self-size profile)))) |
93 | (for-each (match-lambda | |
94 | (($ <profile> name self total) | |
95 | (format port "~64a ~6,1f ~6,1f ~5,1f%\n" | |
96 | name (/ total MiB) (/ self MiB) | |
97 | (* 100. (/ self whole 1.))))) | |
a6c1fe82 | 98 | (sort profile (negate profile<?))) |
69daee23 | 99 | (format port (G_ "total: ~,1f MiB~%") (/ whole MiB 1.)))) |
fcc58db6 LC |
100 | |
101 | (define display-profile* | |
102 | (lift display-profile %store-monad)) | |
103 | ||
92ed837a LC |
104 | (define (substitutable-requisites store items) |
105 | "Return the list of requisites of ITEMS based on information available in | |
fcc58db6 | 106 | substitutes." |
92ed837a | 107 | (let loop ((items items) |
fcc58db6 LC |
108 | (result '())) |
109 | (match items | |
110 | (() | |
111 | (delete-duplicates result)) | |
112 | (items | |
113 | (let ((info (substitutable-path-info store | |
114 | (delete-duplicates items)))) | |
115 | (loop (remove (lambda (item) ;XXX: complexity | |
116 | (member item result)) | |
117 | (append-map substitutable-references info)) | |
118 | (append (append-map substitutable-references info) | |
119 | result))))))) | |
120 | ||
92ed837a | 121 | (define (requisites* items) |
fcc58db6 LC |
122 | "Return as a monadic value the requisites of ITEMS, based either on the |
123 | information available in the local store or using information about | |
124 | substitutes." | |
125 | (lambda (store) | |
da2d893e LC |
126 | (let-values (((local missing) |
127 | (partition (cut valid-path? store <>) items))) | |
128 | (values (delete-duplicates | |
129 | (append (requisites store local) | |
130 | (substitutable-requisites store missing))) | |
131 | store)))) | |
a371aa22 LC |
132 | |
133 | (define (store-profile items) | |
fcc58db6 | 134 | "Return as a monadic value a list of <profile> objects representing the |
a371aa22 | 135 | profile of ITEMS and their requisites." |
92ed837a | 136 | (mlet* %store-monad ((refs (>>= (requisites* items) |
fcc58db6 LC |
137 | (lambda (refs) |
138 | (return (delete-duplicates | |
a371aa22 | 139 | (append items refs)))))) |
fcc58db6 LC |
140 | (sizes (mapm %store-monad |
141 | (lambda (item) | |
83bde59f | 142 | (>>= (file-size item) |
fcc58db6 LC |
143 | (lambda (size) |
144 | (return (cons item size))))) | |
145 | refs))) | |
1199da08 LC |
146 | (define size-table |
147 | (fold (lambda (pair result) | |
148 | (match pair | |
149 | ((item . size) | |
150 | (vhash-cons item size result)))) | |
151 | vlist-null sizes)) | |
152 | ||
fcc58db6 | 153 | (define (dependency-size item) |
92ed837a | 154 | (mlet %store-monad ((deps (requisites* (list item)))) |
fcc58db6 LC |
155 | (foldm %store-monad |
156 | (lambda (item total) | |
1199da08 LC |
157 | (return (+ (match (vhash-assoc item size-table) |
158 | ((_ . size) size)) | |
159 | total))) | |
fcc58db6 LC |
160 | 0 |
161 | (delete-duplicates (cons item deps))))) | |
162 | ||
163 | (mapm %store-monad | |
164 | (match-lambda | |
165 | ((item . size) | |
166 | (mlet %store-monad ((dependencies (dependency-size item))) | |
167 | (return (profile item size dependencies))))) | |
168 | sizes))) | |
169 | ||
a8aa84af | 170 | (define* (ensure-store-item spec-or-item) |
fcc58db6 LC |
171 | "Return a store file name. If SPEC-OR-ITEM is a store file name, return it |
172 | as is. Otherwise, assume SPEC-OR-ITEM is a package output specification such | |
173 | as \"guile:debug\" or \"gcc-4.8\" and return its store file name." | |
174 | (with-monad %store-monad | |
175 | (if (store-path? spec-or-item) | |
176 | (return spec-or-item) | |
177 | (let-values (((package output) | |
178 | (specification->package+output spec-or-item))) | |
179 | (mlet %store-monad ((drv (package->derivation package))) | |
180 | ;; Note: we don't try building DRV like 'guix archive' does | |
181 | ;; because we don't have to since we can instead rely on | |
182 | ;; substitute meta-data. | |
183 | (return (derivation->output-path drv output))))))) | |
184 | ||
185 | \f | |
a8f996c6 LC |
186 | ;;; |
187 | ;;; Charts. | |
188 | ;;; | |
189 | ||
190 | ;; Autoload Guile-Charting. | |
191 | ;; XXX: Use this hack instead of #:autoload to avoid compilation errors. | |
192 | ;; See <http://bugs.gnu.org/12202>. | |
193 | (module-autoload! (current-module) | |
194 | '(charting) '(make-page-map)) | |
195 | ||
196 | (define (profile->page-map profiles file) | |
197 | "Write a 'page map' chart of PROFILES, a list of <profile> objects, to FILE, | |
198 | the name of a PNG file." | |
199 | (define (strip name) | |
200 | (string-drop name (+ (string-length (%store-prefix)) 28))) | |
201 | ||
202 | (define data | |
203 | (fold2 (lambda (profile result offset) | |
204 | (match profile | |
205 | (($ <profile> name self) | |
206 | (let ((self (inexact->exact | |
207 | (round (/ self (expt 2. 10)))))) | |
208 | (values `((,(strip name) ,offset . ,self) | |
209 | ,@result) | |
210 | (+ offset self)))))) | |
211 | '() | |
212 | 0 | |
213 | (sort profiles | |
214 | (match-lambda* | |
9a3762a7 LC |
215 | ((($ <profile> name1 self1 total1) |
216 | ($ <profile> name2 self2 total2)) | |
a8f996c6 LC |
217 | (> total1 total2)))))) |
218 | ||
219 | ;; TRANSLATORS: This is the title of a graph, meaning that the graph | |
220 | ;; represents a profile of the store (the "store" being the place where | |
221 | ;; packages are stored.) | |
69daee23 | 222 | (make-page-map (G_ "store profile") data |
a8f996c6 LC |
223 | #:write-to-png file)) |
224 | ||
225 | \f | |
fcc58db6 LC |
226 | ;;; |
227 | ;;; Options. | |
228 | ;;; | |
229 | ||
230 | (define (show-help) | |
69daee23 | 231 | (display (G_ "Usage: guix size [OPTION]... PACKAGE |
fcc58db6 | 232 | Report the size of PACKAGE and its dependencies.\n")) |
69daee23 | 233 | (display (G_ " |
d490d06e LC |
234 | --substitute-urls=URLS |
235 | fetch substitute from URLS if they are authorized")) | |
69daee23 | 236 | (display (G_ " |
fcc58db6 | 237 | -s, --system=SYSTEM consider packages for SYSTEM--e.g., \"i686-linux\"")) |
a6c1fe82 LC |
238 | ;; TRANSLATORS: "closure" and "self" must not be translated. |
239 | (display (G_ " | |
240 | --sort=KEY sort according to KEY--\"closure\" or \"self\"")) | |
69daee23 | 241 | (display (G_ " |
d490d06e | 242 | -m, --map-file=FILE write to FILE a graphical map of disk usage")) |
fcc58db6 | 243 | (newline) |
69daee23 | 244 | (display (G_ " |
fcc58db6 | 245 | -h, --help display this help and exit")) |
69daee23 | 246 | (display (G_ " |
fcc58db6 LC |
247 | -V, --version display version information and exit")) |
248 | (newline) | |
249 | (show-bug-report-information)) | |
250 | ||
251 | (define %options | |
252 | ;; Specifications of the command-line options. | |
253 | (list (option '(#\s "system") #t #f | |
254 | (lambda (opt name arg result) | |
255 | (alist-cons 'system arg | |
256 | (alist-delete 'system result eq?)))) | |
d490d06e LC |
257 | (option '("substitute-urls") #t #f |
258 | (lambda (opt name arg result . rest) | |
259 | (apply values | |
260 | (alist-cons 'substitute-urls | |
261 | (string-tokenize arg) | |
262 | (alist-delete 'substitute-urls result)) | |
263 | rest))) | |
a6c1fe82 LC |
264 | (option '("sort") #t #f |
265 | (lambda (opt name arg result . rest) | |
266 | (match arg | |
267 | ("closure" | |
268 | (alist-cons 'profile<? profile-closure<? result)) | |
269 | ("self" | |
270 | (alist-cons 'profile<? profile-self<? result)) | |
271 | (_ | |
272 | (leave (G_ "~a: invalid sorting key~%") arg))))) | |
a8f996c6 LC |
273 | (option '(#\m "map-file") #t #f |
274 | (lambda (opt name arg result) | |
275 | (alist-cons 'map-file arg result))) | |
fcc58db6 LC |
276 | (option '(#\h "help") #f #f |
277 | (lambda args | |
278 | (show-help) | |
279 | (exit 0))) | |
280 | (option '(#\V "version") #f #f | |
281 | (lambda args | |
282 | (show-version-and-exit "guix size"))))) | |
283 | ||
284 | (define %default-options | |
a6c1fe82 | 285 | `((system . ,(%current-system)) |
fb255088 | 286 | (profile<? . ,profile-self<?))) |
fcc58db6 LC |
287 | |
288 | \f | |
289 | ;;; | |
290 | ;;; Entry point. | |
291 | ;;; | |
292 | ||
293 | (define (guix-size . args) | |
294 | (with-error-handling | |
a1ff7e1d LC |
295 | (let* ((opts (parse-command-line args %options (list %default-options) |
296 | #:build-options? #f)) | |
fcc58db6 LC |
297 | (files (filter-map (match-lambda |
298 | (('argument . file) file) | |
299 | (_ #f)) | |
300 | opts)) | |
a6c1fe82 | 301 | (profile<? (assoc-ref opts 'profile<?)) |
a8f996c6 | 302 | (map-file (assoc-ref opts 'map-file)) |
d490d06e LC |
303 | (system (assoc-ref opts 'system)) |
304 | (urls (assoc-ref opts 'substitute-urls))) | |
fcc58db6 LC |
305 | (match files |
306 | (() | |
69daee23 | 307 | (leave (G_ "missing store item argument\n"))) |
db761534 | 308 | ((files ..1) |
d2f2c8f1 | 309 | (leave-on-EPIPE |
0bc02bec | 310 | ;; Turn off grafts because (1) substitute servers do not serve grafted |
3667bb6c LC |
311 | ;; packages, and (2) they do not make any difference on the |
312 | ;; resulting size. | |
313 | (parameterize ((%graft? #f)) | |
314 | (with-store store | |
315 | (set-build-options store | |
316 | #:use-substitutes? #t | |
317 | #:substitute-urls urls) | |
d490d06e | 318 | |
3667bb6c | 319 | (run-with-store store |
db761534 LC |
320 | (mlet* %store-monad ((items (mapm %store-monad |
321 | ensure-store-item files)) | |
322 | (profile (store-profile items))) | |
3667bb6c LC |
323 | (if map-file |
324 | (begin | |
325 | (profile->page-map profile map-file) | |
326 | (return #t)) | |
a6c1fe82 LC |
327 | (display-profile* profile (current-output-port) |
328 | #:profile<? profile<?))) | |
db761534 | 329 | #:system system))))))))) |