1: | | ;; statprof.scm -- A statistical profiler for Gambit-C 4.0 |
2: | | |
3: | | ;; See the README file for license and usage information. |
4: | | |
5: | | ;; $Id: statprof.scm,v 1.9 2005/03/14 07:35:49 guillaume Exp $ |
6: | | |
7: | | |
8: | | ;; ---------------------------------------------------------------------------- |
9: | | ;; Profiling & interruption handling |
10: | | |
11: | | (define *buckets* #f) |
12: | | (define *total* 0) |
13: | | |
14: | | (define (profile-start!) |
15: | | (set! *buckets* '()) |
16: | | (set! *total* 0) |
17: | | (##interrupt-vector-set! 1 profile-heartbeat!)) |
18: | | |
19: | | (define (profile-stop!) |
20: | | (##interrupt-vector-set! 1 ##thread-heartbeat!)) |
21: | | |
22: | | ;; (define (identify-continuation cont) ; first version |
23: | | ;; (or (##continuation-creator cont) |
24: | | ;; 'unknown)) |
25: | | |
26: | | (define (identify-continuation cont) ; second version |
27: | | (let ((locat (##continuation-locat cont))) |
28: | | (if locat |
29: | | (let* ((container (##locat-container locat)) |
30: | | (file (##container->file container))) |
31: | | (if file |
32: | | (let* ((filepos (##position->filepos (##locat-position locat))) |
33: | | (line (##fixnum.+ (##filepos-line filepos) 1)) |
34: | 0% | (col (##fixnum.+ (##filepos-col filepos) 1))) |
35: | | (list file line col)) |
36: | | 'unknown)) |
37: | | 'unknown))) |
38: | | |
39: | | (define (profile-heartbeat!) |
40: | 0% | (##continuation-capture |
41: | 0% | (lambda (cont) |
42: | 0% | (##thread-heartbeat!) |
43: | 0% | (let ((id (identify-continuation cont))) |
44: | 0% | (if (not (eq? id 'unknown)) |
45: | | (let ((bucket (assoc (car id) *buckets*))) |
46: | | (set! *total* (+ *total* 1)) |
47: | | (if (not bucket) |
48: | | (begin |
49: | | (set! *buckets* (cons |
50: | | (cons (car id) |
51: | | ;; fixme: arbitrary hard limit |
52: | | ;; on the length of source |
53: | | ;; files |
54: | | (make-vector 5000 0)) |
55: | | *buckets*)) |
56: | | (set! bucket (car *buckets*)))) |
57: | | |
58: | 0% | (vector-set! (cdr bucket) |
59: | | (cadr id) |
60: | | (+ (vector-ref (cdr bucket) |
61: | | (cadr id)) |
62: | | 1)))))))) |
63: | | |
64: | | |
65: | | ;; ---------------------------------------------------------------------------- |
66: | | ;; Text formatting |
67: | | |
68: | | (define (pad-left s l c) |
69: | | (let loop ((s (string->list s))) |
70: | | (if (< (length s) l) |
71: | | (loop (cons c s)) |
72: | | (list->string s)))) |
73: | | |
74: | | |
75: | | ;; ---------------------------------------------------------------------------- |
76: | | ;; Palette generation & color formatting |
77: | | |
78: | | (define (gradient from to step) |
79: | | (let ((inc (map (lambda (x) (/ x step)) |
80: | | (map - to from)))) |
81: | | |
82: | | (let loop ((i 0) |
83: | | (acc '())) |
84: | | (if (= i step) |
85: | | (reverse acc) |
86: | | (loop (+ i 1) |
87: | | (cons (map |
88: | | (lambda (x o) |
89: | | (round (+ x (* i o)))) |
90: | | from |
91: | | inc) |
92: | | acc)))))) |
93: | | |
94: | | (define (as-rgb col) |
95: | | (apply string-append |
96: | | (map |
97: | | (lambda (x) |
98: | | (pad-left (number->string x 16) 2 #\0)) |
99: | | col))) |
100: | | |
101: | | (define palette |
102: | | (list->vector |
103: | | (cons '(255 255 255) |
104: | | (gradient '(127 127 255) |
105: | | '(255 127 127) |
106: | | 16)))) |
107: | | |
108: | | |
109: | | ;; ---------------------------------------------------------------------------- |
110: | | ;; Functions to generate the report |
111: | | |
112: | | (define (write-profile-report profile-name) |
113: | | |
114: | | (define (iota1 n) |
115: | | (let loop ((n n) |
116: | | (l '())) |
117: | | (if (>= n 1) |
118: | | (loop (- n 1) (cons n l)) |
119: | | l))) |
120: | | |
121: | | (define directory-name (string-append (current-directory) |
122: | | profile-name |
123: | | "/")) |
124: | | (with-exception-catcher |
125: | | (lambda (e) |
126: | | ;; ignore the exception, it probably means that the directory |
127: | | ;; already existed. If there's another problem it will be |
128: | | ;; signaled later. |
129: | | #f) |
130: | | (lambda () |
131: | | (create-directory (list path: directory-name |
132: | | permissions: #o755)))) |
133: | | |
134: | | (let ((max-intensity |
135: | | (apply max |
136: | | (map |
137: | | (lambda (data) |
138: | | (apply max |
139: | | (vector->list data))) |
140: | | (map cdr *buckets*))))) |
141: | | |
142: | | (map |
143: | | (lambda (bucket) |
144: | | (let ((file (car bucket)) |
145: | | (data (cdr bucket))) |
146: | | |
147: | | (define (get-color n) |
148: | | (let ((i (vector-ref data n))) |
149: | | (if (= i 0) |
150: | | (as-rgb (vector-ref palette 0)) |
151: | | (let ((x (* (/ (log (+ 1. i)) |
152: | | (ceiling (log max-intensity))) |
153: | | (- (vector-length palette) 1)))) |
154: | | (as-rgb (vector-ref palette |
155: | | (inexact->exact (ceiling x)))))))) |
156: | | |
157: | | (with-output-to-file (string-append |
158: | | directory-name |
159: | | (path-strip-directory file) |
160: | | ".html") |
161: | | (let ((lines (call-with-input-file file |
162: | | (lambda (p) (read-all p read-line))))) |
163: | | (lambda () |
164: | | (display |
165: | | (sexp->html |
166: | | `(html |
167: | | (body |
168: | | (table |
169: | | cellspacing: 0 |
170: | | cellpadding: 0 |
171: | | border: 0 |
172: | | style: "font-size: 12px;" |
173: | | ,@(map |
174: | | (lambda (line line#) |
175: | | `(tr |
176: | | (td ,(string-append |
177: | | (number->string line#) |
178: | | ": ")) |
179: | | ;; (td |
180: | | ;; align: center |
181: | | ;; ,(let ((n (vector-ref data line#))) |
182: | | ;; (if (= n 0) |
183: | | ;; "" |
184: | | ;; (string-append "[" |
185: | | ;; (number->string n) |
186: | | ;; "/" |
187: | | ;; (number->string *total*) |
188: | | ;; "]")))) |
189: | | |
190: | | (td |
191: | | align: center |
192: | | ,(let ((n (vector-ref data line#))) |
193: | | (if (= n 0) |
194: | | "" |
195: | | (string-append |
196: | | (number->string |
197: | | (round% (/ n *total*))) |
198: | | "% ")))) |
199: | | |
200: | | (td (pre style: ,(string-append |
201: | | "background-color:#" |
202: | | (get-color line#)) |
203: | | ,line)))) |
204: | | lines |
205: | | (iota1 (length lines))))))))))))) |
206: | | |
207: | | *buckets*)) |
208: | | |
209: | | (with-output-to-file (string-append directory-name "index.html") |
210: | | (lambda () |
211: | | (display |
212: | | (sexp->html |
213: | | `(html |
214: | | (body |
215: | | ,@(map (lambda (bucket) |
216: | | (let ((file-path (string-append |
217: | | directory-name |
218: | | (path-strip-directory (car bucket)) |
219: | | ".html"))) |
220: | | `(p (a href: ,file-path ,file-path) |
221: | | " [" |
222: | | ,(round% |
223: | | (/ (apply + (vector->list (cdr bucket))) |
224: | | *total*)) |
225: | | " %]"))) |
226: | | *buckets*)))))))) |
227: | | |
228: | | (define (round% n) |
229: | | (/ (round |
230: | | (* 10000 n)) |
231: | | 100.)) |
232: | | |
233: | | |
234: | | ;; ---------------------------------------------------------------------------- |
235: | | ;; Included file "html.scm" |
236: | | ;; ---------------------------------------------------------------------------- |
237: | | |
238: | | ;; html.scm -- A simple html generator for Gambit-C 4.0 |
239: | | |
240: | | ;; Written by Guillaume Germain (germaing@iro.umontreal.ca) |
241: | | ;; This code is released in the public domain. |
242: | | |
243: | | |
244: | | (define (stringify x) |
245: | | (with-output-to-string "" |
246: | | (lambda () |
247: | | (display x)))) |
248: | | |
249: | | (define (to-escaped-string x) |
250: | | (stringify |
251: | | (map (lambda (c) |
252: | | (case c |
253: | | ((#\<) "<") |
254: | | ((#\>) ">") |
255: | | ((#\&) "&") |
256: | | (else c))) |
257: | | (string->list |
258: | | (stringify x))))) |
259: | | |
260: | | ;; Quick and dirty conversion of s-expressions to html |
261: | | (define (sexp->html exp) |
262: | | |
263: | | ;; write the opening tag |
264: | | (define (open-tag exp) |
265: | | (cond |
266: | | ;; null tag isn't valid |
267: | | ((null? exp) |
268: | | (error "null tag")) |
269: | | |
270: | | ;; a tag must be a list beginning with a symbol |
271: | | ((and (pair? exp) |
272: | | (symbol? (car exp))) |
273: | | (list "<" |
274: | | (car exp) |
275: | | " " |
276: | | (maybe-args (car exp) (cdr exp)))) |
277: | | |
278: | | (else |
279: | | (error "invalid tag" exp)))) |
280: | | |
281: | | ;; take care of the keywords / arguments |
282: | | (define (maybe-args tag exp) |
283: | | |
284: | | (cond |
285: | | ;; does the rest of the list begins with a keyword |
286: | | ((and (pair? exp) |
287: | | (keyword? (car exp))) |
288: | | |
289: | | ;; does the keyword has an associated value? |
290: | | (if (or (null? (cdr exp)) |
291: | | (keyword? (cadr exp))) |
292: | | ;; no, we don't put an associated value |
293: | | (list (keyword->string (car exp)) |
294: | | " " |
295: | | (maybe-args tag (cdr exp))) |
296: | | ;; yes, we take the next element in the list as the value |
297: | | (list (keyword->string (car exp)) |
298: | | "=\"" |
299: | | (cadr exp) |
300: | | "\" " |
301: | | (maybe-args tag (cddr exp))))) |
302: | | |
303: | | ;; must just be some content |
304: | | (else |
305: | | (content tag exp)))) |
306: | | |
307: | | ;; handle the content of the tag and closing it |
308: | | (define (content tag exp) |
309: | | (cond |
310: | | ;; no content... |
311: | | ((null? exp) |
312: | | ;;(list "></" tag ">")) ; close as "<br></br>" |
313: | | (list "/>")) ; close as "<br/>" |
314: | | |
315: | | ;; write the content, handle tags inside |
316: | | ((pair? exp) |
317: | | (list ">" |
318: | | (map (lambda (e) |
319: | | (if (pair? e) |
320: | | (open-tag e) |
321: | | (to-escaped-string e))) |
322: | | exp) |
323: | | "</" |
324: | | tag |
325: | | ">")) |
326: | | |
327: | | ;; non-null terminated list? |
328: | | (else |
329: | | (error "strange content...")))) |
330: | | |
331: | | ;; we rely on Gambit's flattening of list when printed with DISPLAY |
332: | | (with-output-to-string "" |
333: | | (lambda () |
334: | | (display (open-tag exp))))) |