diff --git a/scheme/httpd/surflets/profile.scm b/scheme/httpd/surflets/profile.scm index b730da4..de218fd 100644 --- a/scheme/httpd/surflets/profile.scm +++ b/scheme/httpd/surflets/profile.scm @@ -1,8 +1,73 @@ -;; Does profiling things, mainly space measurements +;; Does space measurements using spatial ;; Copyright Andreas Bernauer, 2002 +;;; Entry points: +;;; +;;; (PROFILE-SPACE [file-name]) +;;; +;;; Save output of (SPACE) to a file. The file is either a (fresh) +;;; temporary file (created with CREATE-TEMP-FILE) or the one +;;; specified by FILE-NAME. In either cases the FILE-NAME is returned. +;;; +;;; +;;; (PROFILE-RESULT file-name nth) +;;; +;;; Return the result of the NTH output in the file named FILE-NAME as +;;; as SPACE-INFO object (see below for SPACE-INFO). +;;; +;;; +;;; (PROFILE-RESULTS file-name) +;;; +;;; Perform a repeated PROFILE-RESULT on FILE-NAME, returning a list +;;; that contains all SPACE-INFO objects (see below for SPACE-INFO). +;;; +;;; +;;; (MAKE-GNUPLOT-DATA output-file selector space-info-list) +;;; +;;; Creates a file named OUTPUT-FILE out of SPACE-INFO-LIST. The +;;; created file can be used with GNUPLOT (external program, not part +;;; of SUnet, http://www.gnuplot.info/) to create plot images. +;;; SELECTOR is a procedure that gets a SPACE-INFO object and returns +;;; an integer number. See below for possible SELECTORS on SPACE-INFO +;;; objects. +;;; +;;; +;;; SPACE-INFO object +;;; +;;; A container for all the information (SPACE) returns. Try +;;; +;;; > ,open spatial +;;; > (space) +;;; +;;; to see, what I mean. See the following record definition for +;;; selectors and mutators. The lists returned by the selectors may be +;;; inspected with PURE-COUNT, PURE-BYTES, IMPURE-COUNT, IMPURE-BYTES, +;;; TOTAL-COUNT, TOTAL-BYTES +;;; +;;; +;;; Possible strategy to profile space usage: Spread calls to +;;; PROFILE-SPACE throughout your program. After you've finished, call +;;; PROFILE-RESULTS and feed the resulting list into +;;; WRITE-GNUPLOT-DATA-FILE. Run GNUPLOT (external program) with this +;;; file to get a plot image. +;;; +;;; +;;; +;;; Example: +;;; (write-gnuplot-data-file "result.dat" +;;; (lambda (space-info) +;;; (total-bytes (space-info-total space-info))) +;;; (profile-results "concatenated-profile-files")) +;;; +;;; in GNUPLOT: +;;; set terminal png +;;; set output "result.png" +;;; plot "result.dat" title "My profile" with lines + + (define *debug* #f) +;; SPACE-INFO (define-record-type space-info :space-info (make-space-info pair symbol vector closure location cell channel port ratnum record continuation extended-number template @@ -32,6 +97,7 @@ (bignum space-info-bignum set-space-info-bignum!) (total space-info-total set-space-info-total!)) +;; FIELD-INSPECTORS (define pure-count first) (define pure-bytes second) (define impure-count third) @@ -41,9 +107,8 @@ (define *run-count* 0) -(define (set-run-count! x) - (set! *run-count* x)) +;; PROFILE-SPACE writes result of call to (SPACE) to a file (define (profile-space . maybe-file-name) (let ((file-name (:optional maybe-file-name @@ -55,13 +120,48 @@ (close out)) file-name)) - +;; PROFILE-RESULT returns a SPACE-INFO object representing the output +;; of (SPACE) of the NTH run. If there is no such run, 'EOF is returned. (define (profile-result file-name nth) (let* ((in (open-input-file file-name)) (result (get-space-info in nth))) (close in) result)) +;; PROFILE-RESULTS returns a list of all SPACE-INFO objects that may +;; be represented in FILE-NAME. +(define (profile-results file-name) + (let ((in (open-input-file file-name))) + (let loop ((space-info (get-space-info in 'first)) + (result '())) + (if (eq? 'eof space-info) + (reverse result) + (loop (get-space-info in 'first) + (cons space-info result)))))) + + +;; WRITE-GNUPLOT-DATA-FILE converts a SPACE-INFO-LIST into a data file +;; for a simple 2D plot, readable by GNUPLOT (external program, not +;; part of SUnet, http://www.gnuplot.info/) +(define (write-gnuplot-data-file output-file selector space-info-list) + (let ((out (open-output-file output-file))) + (display "# generated by profile.scm\n" out) + (let loop ((count 0) + (space-info-list space-info-list)) + (if (null? space-info-list) + (close out) + (begin + (display count out) + (display " " out) + (display (selector (car space-info-list)) out) + (newline out) + (loop (+ 1 count) + (cdr space-info-list))))))) + +;; GET-SPACE-INFO returns the result of the NTH call to (SPACE) stored +;; in IN. If NTH is 'FIRST, the SPACE-INFO object representing the +;; next run is returned. If EOF is reached before a run is found, 'EOF +;; is returned. (define (get-space-info in nth) (if (eof-object? (skip-runs in nth)) 'eof @@ -92,19 +192,26 @@ (get-record "bignum" assoc-list) (get-record "total" assoc-list)))))) +;; GET-RECORD returns the data of the element in ASSOC-LIST indexed by KEY. (define (get-record key assoc-list) (let ((record (assoc key assoc-list))) (if record (cdr record) (error "wrong data format - field missing" 'pair)))) +;; SKIP-HEADERS just skips two lines in IN (the header lines of a +;; (SPACE) outpout) (define (skip-headers in) (read-line in) (read-line in)) -;; skip runs until write-out is reached +;; RUN-REGEXP matches the run number in the output file of +;; PROFILE-SPACE (define run-regexp (rx "Run #" (submatch (* digit)))) +;; SKIP-RUNS goes forward in IN, until a run in IN with number NTH is +;; found. If NTH is 'FIRST, just the next reachable run is reached. If +;; EOF occurs while searching, 'EOF is returned. (define (skip-runs in nth) (let loop ((line (read-line in))) (if (eof-object? line) @@ -116,10 +223,15 @@ nth))) #t (loop (read-line in))))))) - + +;; TRIM-STRING->NUMBER returns the number stored in S, perhaps padded +;; with spaces on its left: (TRIM-STRING->NUMBER " 123") ==> 123 (define (trim-string->number s) (string->number (string-trim s))) +;; READ-DATA parses the output of (SPACES). It expects the next line +;; in IN to be the first data line (not a header). It returns an +;; a-list (row name . data-list) (define (read-data in) (let loop ((count 0) (line (read-line in)) @@ -140,27 +252,3 @@ assoc-list)) assoc-list))) -(define (profile-results file-name) - (let ((in (open-input-file file-name))) - (let loop ((space-info (get-space-info in 'first)) - (result '())) - (if (eq? 'eof space-info) - (reverse result) - (loop (get-space-info in 'first) - (cons space-info result)))))) - - -(define (make-gnuplot-data output-file selector space-info-list) - (let ((out (open-output-file output-file))) - (display "# generated by profile.scm\n" out) - (let loop ((count 0) - (space-info-list space-info-list)) - (if (null? space-info-list) - (close out) - (begin - (display count out) - (display " " out) - (display (selector (car space-info-list)) out) - (newline out) - (loop (+ 1 count) - (cdr space-info-list))))))) \ No newline at end of file