~ chicken-core (chicken-5) /chicken-profile.scm


  1;;;; chicken-profile.scm - Formatted display of profile outputs - felix -*- Scheme -*-
  2;
  3; Copyright (c) 2008-2022, The CHICKEN Team
  4; Copyright (c) 2000-2007, Felix L. Winkelmann
  5; All rights reserved.
  6;
  7; Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following
  8; conditions are met:
  9;
 10;   Redistributions of source code must retain the above copyright notice, this list of conditions and the following
 11;     disclaimer.
 12;   Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
 13;     disclaimer in the documentation and/or other materials provided with the distribution.
 14;   Neither the name of the author nor the names of its contributors may be used to endorse or promote
 15;     products derived from this software without specific prior written permission.
 16;
 17; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 18; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 19; AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
 20; CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 21; CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 22; SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 23; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 24; OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 25; POSSIBILITY OF SUCH DAMAGE.
 26
 27(declare (block))
 28
 29(module main ()
 30
 31(import scheme
 32	chicken.base
 33	chicken.file
 34	chicken.file.posix
 35	chicken.fixnum
 36	chicken.internal
 37	chicken.platform
 38	chicken.process-context
 39	chicken.sort
 40	chicken.string)
 41
 42(include "mini-srfi-1.scm")
 43
 44(define symbol-table-size 3001)
 45
 46(define sort-by #f)
 47(define file #f)
 48(define no-unused #f)
 49(define seconds-digits 3)
 50(define average-digits 3)
 51(define percent-digits 3)
 52(define top 0)
 53
 54(define (print-usage)
 55  (display #<#EOF
 56Usage: chicken-profile [OPTION ...] [FILENAME ...]
 57
 58 -sort-by-calls            sort output by call frequency
 59 -sort-by-time             sort output by procedure execution time
 60 -sort-by-avg              sort output by average procedure execution time
 61 -sort-by-name             sort output alphabetically by procedure name
 62 -decimals DDD             set number of decimals for seconds, average and
 63                           percent columns (three digits, default: #{seconds-digits}#{average-digits}#{percent-digits})
 64 -no-unused                remove procedures that are never called
 65 -top N                    display only the top N entries
 66 -help                     show this text and exit
 67 -version                  show version and exit
 68 -release                  show release number and exit
 69
 70 FILENAME defaults to the `PROFILE.<number>', selecting the one with
 71 the highest modification time, in case multiple profiles exist.
 72
 73EOF
 74;|
 75)
 76 (exit 64) )
 77
 78(define (run args)
 79  (let loop ([args args])
 80    (if (null? args)
 81	(begin
 82	  (unless file 
 83	    (set! file
 84	      (let ((fs (glob "PROFILE.*")))
 85		(if (null? fs)
 86		    (error "no PROFILEs found")
 87		    (first (sort fs 
 88				 (lambda (f1 f2)
 89				   (> (file-modification-time f1)
 90				      (file-modification-time f2))) ) ) ) ) ) )
 91	  (write-profile) )
 92	(let ([arg (car args)]
 93	      [rest (cdr args)] )
 94	  (define (next-arg)
 95	    (if (null? rest)
 96		(error "missing argument to option" arg)
 97		(let ((narg (car rest)))
 98		  (set! rest (cdr rest))
 99		  narg)))
100	  (define (next-number)
101	    (let ((n (string->number (next-arg))))
102	      (if (and n (> n 0)) n (error "invalid argument to option" arg))))
103	  (cond 
104	   [(member arg '("-h" "-help" "--help")) (print-usage)]
105	   [(string=? arg "-version")
106	    (print "chicken-profile - Version " (chicken-version))
107	    (exit) ]
108	   [(string=? arg "-release")
109	    (print (chicken-version))
110	    (exit) ]
111	   [(string=? arg "-no-unused") (set! no-unused #t)]
112	   [(string=? arg "-top") (set! top (next-number))]
113	   [(string=? arg "-sort-by-calls") (set! sort-by sort-by-calls)]
114	   [(string=? arg "-sort-by-time") (set! sort-by sort-by-time)]
115	   [(string=? arg "-sort-by-avg") (set! sort-by sort-by-avg)]
116	   [(string=? arg "-sort-by-name") (set! sort-by sort-by-name)]
117	   [(string=? arg "-decimals") (set-decimals (next-arg))]
118	   [(and (> (string-length arg) 1) (char=? #\- (string-ref arg 0)))
119	    (error "invalid option" arg) ]
120	   [file (print-usage)]
121	   [else (set! file arg)] )
122	  (loop rest) ) ) ) )
123
124(define (sort-by-calls x y)
125  (let ([c1 (second x)]
126	[c2 (second y)] )
127    (if (eqv? c1 c2)
128	(> (third x) (third y))
129	(if c1 (if c2 (> c1 c2) #t) #t) ) ) )
130
131(define (sort-by-time x y)
132  (let ([c1 (third x)]
133	[c2 (third y)] )
134    (if (= c1 c2)
135	(> (second x) (second y))
136	(> c1 c2) ) ) )
137
138(define (sort-by-avg x y)
139  (let ([c1 (cadddr x)]
140	[c2 (cadddr y)] )
141    (if (eqv? c1 c2)
142	(> (third x) (third y))
143	(> c1 c2) ) ) )
144
145(define (sort-by-name x y)
146  (string<? (symbol->string (first x)) (symbol->string (first y))) )
147
148(set! sort-by sort-by-time)
149
150(define (set-decimals arg)
151  (define (arg-digit n)
152    (let ((n (- (char->integer (string-ref arg n))
153		(char->integer #\0))))
154      (if (<= 0 n 9)
155	  (if (= n 9) 8 n)		; 9 => overflow in format-real
156	  (error "invalid argument to -decimals option" arg))))
157  (if (= (string-length arg) 3)
158      (begin
159	(set! seconds-digits (arg-digit 0))
160	(set! average-digits (arg-digit 1))
161	(set! percent-digits (arg-digit 2)))
162      (error "invalid argument to -decimals option" arg)))
163
164(define (make-symbol-table)
165  (make-vector symbol-table-size '()))
166
167(define (read-profile)
168  (let* ((hash (make-symbol-table))
169	 (header (read))
170	 (type (if (symbol? header) header 'instrumented)))
171    (do ((line (if (symbol? header) (read) header) (read)))
172	((eof-object? line))
173      (hash-table-set!
174       hash (first line)
175       (map (lambda (x y) (and x y (+ x y)))
176	    (or (hash-table-ref hash (first line)) '(0 0))
177	    (cdr line))))
178    (let ((alist '()))
179      (hash-table-for-each
180       (lambda (sym counts)
181	 (set! alist (alist-cons sym counts alist)))
182       hash)
183      (cons type alist))))
184
185(define (format-string str cols #!optional right (padc #\space))
186  (let* ((len (string-length str))
187	 (pad (make-string (fxmax 0 (fx- cols len)) padc)) )
188    (if right
189	(string-append pad str)
190	(string-append str pad) ) ) )
191
192(define (format-real n d)
193  (let ((exact-value (inexact->exact (truncate n))))
194    (string-append
195     (number->string exact-value)
196     (if (> d 0) "." "")
197     (substring
198      (number->string
199       (inexact->exact
200	(truncate
201	 (* (- n exact-value -1) (expt 10 d)))))
202      1 (+ d 1)))))
203
204(define (write-profile)
205  (print "reading `" file "' ...\n")
206  (let* ((type&data0 (with-input-from-file file read-profile))
207	 (type  (car type&data0))
208	 (data0 (cdr type&data0))
209	 ;; Instrumented profiling results in total runtime being
210	 ;; counted for the outermost "main" procedure, while
211	 ;; statistical counts time spent only inside the procedure
212	 ;; itself.  Ideally we'd have both, but that's tricky to do.
213	 (total-t (foldl (if (eq? type 'instrumented)
214			     (lambda (r t) (max r (third t)))
215			     (lambda (r t) (+ r (third t))))
216			 0 data0))
217	 (data (sort (map
218		      (lambda (t)
219			(append
220			 t
221			 (let ((c (second t)) ; count
222			       (t (third t))) ; time tallied to procedure
223			   (list (or (and c (> c 0) (/ t c)) ; time / count
224				     0)
225				 (or (and (> total-t 0) (* (/ t total-t) 100)) ; % of total-time
226				     0)
227				 ))))
228		      data0)
229                     sort-by)))
230    (if (< 0 top (length data))
231	(set! data (take data top)))
232    (set! data (map (lambda (entry)
233		      (let ((c (second entry)) ; count
234			    (t (third entry))  ; total time
235			    (a (fourth entry)) ; average time
236			    (p (fifth entry)) ) ; % of max time
237			(list (##sys#symbol->string (first entry))
238			      (if (not c) "overflow" (number->string c))
239			      (format-real (/ t 1000) seconds-digits)
240			      (format-real (/ a 1000) average-digits)
241			      (format-real p percent-digits))))
242		    (if no-unused
243			(filter (lambda (entry) (> (second entry) 0)) data)
244			data)))
245    (let* ((headers (list "procedure" "calls" "seconds" "average" "percent"))
246	   (alignments (list #f #t #t #t #t))
247	   (spacing 2)
248	   (spacer (make-string spacing #\space))
249	   (column-widths (foldl
250			   (lambda (max-widths row)
251			     (map max (map string-length row) max-widths))
252			   (list 0 0 0 0 0)
253			   (cons headers data))))
254      (define (print-row row)
255	(print (string-intersperse (map format-string row column-widths alignments) spacer)))
256      (print-row headers)
257      (print (make-string (+ (foldl + 0 column-widths)
258			     (* spacing (- (length alignments) 1)))
259			  #\-))
260      (for-each print-row data))))
261  
262(run (command-line-arguments))
263
264)
Trap