size.scm 12 KB

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