#!/bin/sh # -*- scheme -*- exec guile --debug -e main -s "$0" "$@" !# ;;; Copyright (c) 2007 Andy Wingo ;;; LGPL, like Guile itself. (use-modules (srfi srfi-13) (ice-9 optargs) (ice-9 regex) (ice-9 rdelim) (ice-9 format)) (define-macro (define-accessors type . fields) `(begin ,@(map (lambda (field) ;; can't have : as a symbol `(begin (define (,(string->symbol (string-append (symbol->string type) ":" (symbol->string field))) obj) (vector-ref obj ,(list-index fields field))) (define (,(symbol-append 'set- type '- field '!) obj v) (vector-set! obj ,(list-index fields field) v)))) fields))) (define (make-call-data) (vector 0 0 0 #f #f #f)) (define-accessors call-data calls self incl name source object) (define-accessors grow-vector len constructor vector) (define (make-grow-vector element-constructor) (vector 0 element-constructor (vector))) (define (grow-vector-set! gvec n val) (cond ((< n (grow-vector:len gvec)) (vector-set! (grow-vector:vector gvec) n val)) (else (let ((new (make-vector (if (zero? (grow-vector:len gvec)) 1 (* (grow-vector:len gvec) 2)) #f))) (do ((i 0 (1+ i))) ((>= i (grow-vector:len gvec))) (vector-set! new i (vector-ref (grow-vector:vector gvec) i))) (set-grow-vector-vector! gvec new) (set-grow-vector-len! gvec (vector-length new)) (grow-vector-set! gvec n val))))) (define (grow-vector-ref gvec n) (or (and (< n (grow-vector:len gvec)) (vector-ref (grow-vector:vector gvec) n)) (let ((data ((grow-vector:constructor gvec)))) (grow-vector-set! gvec n data) data))) (define (grow-vector-fold gvec proc init) (let ((vec (grow-vector:vector gvec))) (let lp ((i 0) (ret init)) (cond ((>= i (vector-length vec)) ret) ((not (vector-ref vec i)) (lp (1+ i) ret)) (else (lp (1+ i) (proc i (vector-ref vec i) ret))))))) (define-macro (match-bind regex str vars consequent . alternate) (or (string? regex) (error "regex needs to be a string so we can compile it")) (let ((re (gensym)) (match (gensym))) `(let ((,re ,(make-regexp regex))) ((lambda (,match) (if ,match (apply (lambda ,vars ,consequent) (map (lambda (x) (match:substring ,match x)) (iota (match:count ,match)))) ,@alternate)) (regexp-exec ,re ,str))))) (define (parse-paren val set) (match-bind "^\\(([0-9]+)\\) *(.+)?$" val (_ n name) (let ((n (string->number n))) (if name (set n name)) n) (error "could not parse" val))) (define (parse-data) (let ((functions (make-grow-vector make-call-data)) (sources (make-grow-vector (lambda () #f))) (objects (make-grow-vector (lambda () #f))) (headers '())) (define (inc-self-cost! n cost) (let ((call-data (grow-vector-ref functions n))) (set-call-data-self! call-data (+ cost (call-data:self call-data))))) (define (inc-incl-cost! n cost) (let ((call-data (grow-vector-ref functions n))) (set-call-data-incl! call-data (+ cost (call-data:incl call-data))))) (define (inc-calls! n calls) (let ((call-data (grow-vector-ref functions n))) (set-call-data-calls! call-data (+ calls (call-data:calls call-data))))) (let lp ((fn #f) (cfn #f) (fi #f) (ob #f) (cfi #f) (cob #f)) (let ((line (read-line))) (define (parse-func val fi ob) (parse-paren val (lambda (n name) (let ((call-data (grow-vector-ref functions n))) (set-call-data-name! call-data name) (if fi (set-call-data-source! call-data fi)) (if ob (set-call-data-object! call-data ob)))))) (define (-fn val) (lp (parse-func val fi ob) #f fi ob fi ob)) (define (-cfn val) (lp fn (parse-func val (or cfi fi) (or cob ob)) fi ob fi ob)) (define (-calls val) (match-bind "^([0-9]+)[ ]+(.*)$" val (_ calls dest-pos) (inc-calls! cfn (string->number calls)) (error "count not parse calls" val)) (lp fn cfn fi ob cfi cob)) (define (-ob val) (let ((n (parse-paren val (lambda (n name) (grow-vector-set! objects n name))))) (lp fn cfn fi n cfi n))) (define (-cob val) (let ((n (parse-paren val (lambda (n name) (grow-vector-set! objects n name))))) (lp fn cfn fi ob cfi n))) (define (-fi val) (let ((n (parse-paren val (lambda (n name) (grow-vector-set! sources n name))))) (lp fn cfn n ob n cob))) (define (-cfi val) (let ((n (parse-paren val (lambda (n name) (grow-vector-set! sources n name))))) (lp fn cfn fi ob n cob))) (define (-ignore val) (lp fn cfn fi ob cfi cob)) (cond ((eof-object? line) (values headers functions sources objects)) ((string-index line #\:) (match-bind "^(.*):[ ]*(.*)$" line (_ key val) (set! headers (acons key val headers))) (lp fn cfn fi ob cfi cob)) ((or (= (string-length line) 0) (char=? (string-ref line 0) #\#)) (lp fn cfn fi ob cfi cob)) ((string-index line #\=) (match-bind "^(.*)= *(.*)$" line (_ spec position) ((assoc-ref `(("fn" . ,-fn) ("cfn" . ,-cfn) ("ob" . ,-ob) ("cob" . ,-cob) ("fl" . ,-fi) ("fi" . ,-fi) ("cfi" . ,-cfi) ("calls" . ,-calls) ("fe" . ,-ignore)) spec) position) (error "unparseable:" line))) (else (match-bind "^([^ ]+)[ ]([0-9]+)$" line (_ pos cost) (let ((cost (string->number cost))) (inc-incl-cost! fn cost) (if (not cfn) (inc-self-cost! fn cost)))) (lp fn #f fi ob fi ob))))))) ; fixme: self secs per call (define (display-stats headers functions sources objects) (let ((total-count (grow-vector-fold functions (lambda (i call-data x) (if (not (call-data:source call-data)) (pk i call-data)) (+ x (call-data:self call-data))) 0))) (sort! (grow-vector:vector functions) (lambda (x y) (cond ((not x) #f) ((not y) #t) (else (let ((diff (- (call-data:self x) (call-data:self y)))) (positive? (if (= diff 0) (- (call-data:incl x) (call-data:incl y)) diff))))))) (cond ((zero? total-count) (format port "No samples recorded.\n")) (else (for-each (lambda (pair) (format #t "~a: ~a\n" (car pair) (cdr pair))) (reverse! headers)) (display "---\n") (format #t "cumulative self total\n") (format #t " percent percent calls file:function\n") (grow-vector-fold functions (lambda (i call-data _) (and call-data (format #t "~8,2f ~6,2f ~10d ~a:~a[~a]\n" (* (/ (call-data:incl call-data) total-count) 100.0) (* (/ (call-data:self call-data) total-count) 100.0) (call-data:calls call-data) (or (and=> (call-data:source call-data) (lambda (n) (grow-vector-ref sources n))) "???") (call-data:name call-data) (or (and=> (call-data:object call-data) (lambda (n) (grow-vector-ref objects n))) "???")))) #f) (display "---\n") (format #t "Sample count: ~A\n" total-count))))) (define* (main args) (with-input-from-port (if (null? (cdr args)) (current-input-port) (apply open-input-file (cdr args))) (lambda () (call-with-values parse-data display-stats))))