"pdhtml.ss" ;;; pdhtml.ss ;;; Copyright 1984-2016 Cisco Systems, Inc. ;;; ;;; Licensed under the Apache License, Version 2.0 (the "License"); ;;; you may not use this file except in compliance with the License. ;;; You may obtain a copy of the License at ;;; ;;; http://www.apache.org/licenses/LICENSE-2.0 ;;; ;;; Unless required by applicable law or agreed to in writing, software ;;; distributed under the License is distributed on an "AS IS" BASIS, ;;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ;;; See the License for the specific language governing permissions and ;;; limitations under the License. ;;; NOTES: ;;; - fixed bug in define-tags: moved (void) end of text ... to start ;;; ;;; - to change palette to use white background with colorized text: ;;; (profile-palette ;;; (vector-map ;;; (lambda (p) (cons "white" (car p))) ;;; (profile-palette))) ;;; profile-dump-html suggestions from Oscar: ;;; ;;; We could probably build a table mapping source regions to procedure names ;;; in enough cases to actually be useful. If so, showing procedure name instead ;;; of line/char position would help the user get a high-level perspective on the ;;; profile results. Right now the user has to synthesize that perspective by ;;; remembering where each link led. ;;; ;;; Within the file view window, it would be nice to have a way to scan quickly ;;; through the hot spots within that file (we have some obscenely large source ;;; files at work). Perhaps you could reprise the profile spectrum horizontally ;;; in a short frame at the top of the window and rig it so that dragging, scroll ;;; wheel, or clicking on a color cycles through the regions tagged with that col> ;;; ;;; With a large range of profile counts to compress into a fairly small ;;; spectrum, it might be nice if there were a way to zoom into a range by ;;; clicking on the legend, either in the overview window or the file window. ;;; Reallocating the color map could be confusing with multiple windows open, ;;; but perhaps there's some javascript way to rig all the other colors to ;;; desaturate when you zoom into a range in one window. Perhaps intensity ;;; could be used to show the sub-ranges in varying shades of the main legend ;;; color. ;;; ;;; I notice that the profile annotations on the when expressions start at the te> ;;; expression rather than the start of the when. Yet the if expression annotati> ;;; starts at the beginning of the if expression and extends to the closing paren. ;;; Not sure if that made any sense, basically I'm trying to say that the "(when" ;;; itself (and closing paren) isn't colored the same as the test part. ;;; I don't remember exactly how we handled source annotations during wrapping and ;;; unwrapping, but it seems offhand that it might make sense to wrap the input ;;; source annotation around the transformer output so that the source info for t> ;;; when expression is transferred to the generated if expression. (let () (include "types.ss") (define op+ car) (define op- cdr) (define find-pcode (foreign-procedure "(cs)find_pcode" () scheme-object)) (define find-pinfo (lambda (x who) (cond [(procedure? x) ($code-pinfo* ($closure-code x))] [($code? x) ($code-pinfo* x)] [else ($oops who "could not find profiling info in ~s" x)]))) (set-who! profile-clear (lambda () (define clear-links (lambda (op) (if (profile-counter? op) (profile-counter-count-set! op 0) (begin (for-each clear-links (op+ op)) (for-each clear-links (op- op)))))) (for-each (lambda (x) (for-each (lambda (node) (clear-links (rblock-op node))) (find-pinfo x who))) (find-pcode)))) (set-who! profile-dump (lambda () (define rblock-count (lambda (rblock) (let sum ((op (rblock-op rblock))) ; using #3%apply and #3%map in case the #2% versions are profiled, ; to avoid possible negative counts (if (profile-counter? op) (profile-counter-count op) (- (#3%apply + (#3%map sum (op+ op))) (#3%apply + (#3%map sum (op- op)))))))) (fold-left (lambda (r code) (fold-left (lambda (r rblock) (fold-left (let ([count (rblock-count rblock)]) (lambda (r inst) (cons (cons inst count) r))) r (rblock-srecs rblock))) r (find-pinfo code who))) '() (find-pcode))))) (let () (include "types.ss") (define check-dump (lambda (who x) (unless (and (list? x) (andmap (lambda (x) (and (pair? x) (source-object? (car x)) (let ([x (cdr x)]) (and (integer? x) (exact? x))))) x)) ($oops who "invalid dump ~s" x)))) (define-record-type filedata (fields (immutable sfd) (immutable ip) (mutable entry*) ; remaining fields are ignored by profile-dump-list (mutable max-count) (mutable ci) (mutable htmlpath) (mutable htmlfn) (mutable winid)) (nongenerative) (sealed #t) (protocol (lambda (new) (lambda (sfd ip) (new sfd ip '() #f #f #f #f #f))))) (define-record-type entrydata (fields (immutable fdata) (immutable bfp) (immutable efp) (mutable count) (mutable line) (mutable char) ; ci is ignored by profile-dump-list (mutable ci)) (nongenerative) (sealed #t) (protocol (lambda (new) (lambda (fdata bfp efp count) (new fdata bfp efp count #f #f #f))))) (define (gather-filedata who warn? dumpit*) ; returns list of fdata records, each holding a list of entries ; the entries are sorted based on their (unique) bfps (let ([fdata-ht (make-hashtable (lambda (x) (source-file-descriptor-crc x)) (lambda (x y) ; there's no way to make this foolproof, so we identify paths with ; same crc, length, and last component. this can cause problems ; only if two copies of the same file are loaded and used. (or (eq? x y) (and (= (source-file-descriptor-crc x) (source-file-descriptor-crc y)) (= (source-file-descriptor-length x) (source-file-descriptor-length y)) (string=? (path-last (source-file-descriptor-name x)) (path-last (source-file-descriptor-name y)))))))]) (define (open-source sfd) (cond [(hashtable-ref fdata-ht sfd #f)] [($open-source-file sfd) => (lambda (ip) (let ([fdata (make-filedata sfd ip)]) (hashtable-set! fdata-ht sfd fdata) fdata))] [else (when warn? (warningf who "unmodified source file ~s not found in source directories" (source-file-descriptor-name sfd))) (let ([fdata (make-filedata sfd #f)]) (hashtable-set! fdata-ht sfd fdata) fdata)])) (for-each (lambda (dumpit) (let ([source (car dumpit)]) (assert (source? source)) (let ([bfp (source-bfp source)]) (when (>= bfp 0) ; weed out block-profiling entries, whose bfps are negative (let ([fdata (open-source (source-sfd source))]) (filedata-entry*-set! fdata (cons (make-entrydata fdata bfp (source-efp source) (cdr dumpit)) (filedata-entry* fdata)))))))) dumpit*) (let ([fdatav (hashtable-values fdata-ht)]) (vector-for-each (lambda (fdata) (let ([entry* (sort (lambda (x y) (or (> (entrydata-bfp x) (entrydata-bfp y)) (and (= (entrydata-bfp x) (entrydata-bfp y)) (> (entrydata-efp x) (entrydata-efp y))))) (filedata-entry* fdata))]) #;(assert (not (null? entry*))) (let loop ([entry (car entry*)] [entry* (cdr entry*)] [new-entry* '()]) (if (null? entry*) (filedata-entry*-set! fdata (cons entry new-entry*)) (if (and (= (entrydata-bfp (car entry*)) (entrydata-bfp entry)) (= (entrydata-efp (car entry*)) (entrydata-efp entry))) (begin (entrydata-count-set! entry (+ (entrydata-count entry) (entrydata-count (car entry*)))) (loop entry (cdr entry*) new-entry*)) (loop (car entry*) (cdr entry*) (cons entry new-entry*))))))) fdatav) (vector->list fdatav)))) (let () (define (scan-file fdata) (let ([ip (filedata-ip fdata)] [line 1] [char 1]) (define (read-until bfp next) (let loop ([bfp bfp]) (unless (= bfp next) (cond [(eqv? (read-char ip) #\newline) (set! line (+ line 1)) (set! char 1)] [else (set! char (+ char 1))]) (loop (+ bfp 1))))) (let ([entry* (filedata-entry* fdata)]) ; already sorted by gather-filedata (let f ([bfp 0] [entry* entry*]) (unless (null? entry*) (let ([entry (car entry*)] [entry* (cdr entry*)]) (let ([next (entrydata-bfp entry)]) (read-until bfp next) (entrydata-line-set! entry line) (entrydata-char-set! entry char) (f next entry*)))))))) (set-who! profile-dump-list ; return list of lists of: ; - count ; - path ; current if line and char are not #f ; - bfp ; - efp ; - line ; may be #f ; - char ; may be #f (rec profile-dump-list (case-lambda [() (profile-dump-list #t)] [(warn?) (profile-dump-list warn? (profile-dump))] [(warn? dumpit*) (check-dump who dumpit*) (let ([fdata* (gather-filedata who warn? dumpit*)]) (for-each scan-file (remp (lambda (x) (not (filedata-ip x))) fdata*)) (let ([ls (map (lambda (entry) (let ([fdata (entrydata-fdata entry)]) (list (entrydata-count entry) (cond [(filedata-ip fdata) => port-name] [else (source-file-descriptor-name (filedata-sfd fdata))]) (entrydata-bfp entry) (entrydata-efp entry) (entrydata-line entry) (entrydata-char entry)))) (sort (lambda (x y) (> (entrydata-count x) (entrydata-count y))) (apply append (map filedata-entry* fdata*))))]) (for-each (lambda (fdata) (cond [(filedata-ip fdata) => close-input-port])) fdata*) ls))])))) (let () (define-record-type profilit (nongenerative #{profilit iw9f7z5ovg4jjetsvw5m0-2}) (sealed #t) (fields sfd bfp efp weight)) (define make-profile-database (lambda () (make-hashtable source-file-descriptor-crc (lambda (x y) (or (eq? x y) (and (= (source-file-descriptor-crc x) (source-file-descriptor-crc y)) (= (source-file-descriptor-length x) (source-file-descriptor-length y)) (string=? (path-last (source-file-descriptor-name x)) (path-last (source-file-descriptor-name y))))))))) (define profile-database #f) (define profile-source-data? #f) (define profile-block-data? #f) (define update-sfd! (lambda (cell sfd) ; if the recorded sfd is the same but not eq, it's likely from an earlier session. ; overwrite so remaining hashtable equality-procedure checks are more likely to ; succeed at the eq? check (unless (eq? (car cell) sfd) (set-car! cell sfd)))) (set-who! profile-clear-database (lambda () (set! profile-database #f))) (set-who! profile-dump-data (rec profile-dump-data (case-lambda [(ofn) (profile-dump-data ofn (profile-dump))] [(ofn dumpit*) (check-dump who dumpit*) (let ([op ($open-file-output-port who ofn (file-options replace))]) (on-reset (delete-file ofn #f) (on-reset (close-port op) (let* ([dump dumpit*] [max-count (inexact (fold-left max 1 (map cdr dump)))]) (for-each (lambda (dumpit) (let ([source (car dumpit)] [count (cdr dumpit)]) (fasl-write (make-profilit (source-sfd source) (source-bfp source) (source-efp source) ; compute weight as % of max count (fl/ (inexact count) max-count)) op))) dump))) (close-port op)))]))) (set! $profile-source-data? (lambda () profile-source-data?)) (set! $profile-block-data? (lambda () profile-block-data?)) (set-who! profile-load-data (lambda ifn* (define populate! (lambda (x) (unless (profilit? x) ($oops who "invalid profile data element ~s" x)) (unless profile-database (set! profile-database (make-profile-database))) (let ([ht (let* ([sfd (profilit-sfd x)] [cell (hashtable-cell profile-database sfd #f)]) (update-sfd! cell sfd) (or (cdr cell) (let ([ht (make-hashtable values =)]) (set-cdr! cell ht) ht)))]) ; each ht entry is an alist mapping efp -> (weight . n) where n is ; the number of contributing entries so far for this sfd, bfp, and efp. ; n is used to compute the average weight of the contributing entries. (let ([bfp.alist (hashtable-cell ht (profilit-bfp x) '())]) (cond [(assv (profilit-efp x) (cdr bfp.alist)) => (lambda (a) (let ([weight.n (cdr a)]) (let ([weight (car weight.n)] [n (cdr weight.n)]) (let ([new-n (fl+ n 1.0)]) (set-car! weight.n (fl/ (fl+ (* weight n) (profilit-weight x)) new-n)) (set-cdr! weight.n new-n)))))] [else (set-cdr! bfp.alist (cons (cons* (profilit-efp x) (profilit-weight x) 1.0) (cdr bfp.alist)))]))) (if (fxnegative? (profilit-bfp x)) (set! profile-block-data? #t) (set! profile-source-data? #t)))) (define (load-file ifn) (let ([ip ($open-file-input-port who ifn)]) (on-reset (close-port ip) (let f () (let ([x (fasl-read ip)]) (unless (eof-object? x) (with-tc-mutex (populate! x)) (f))))) (close-port ip))) (for-each load-file ifn*))) (set! $profile-show-database (lambda () (when profile-database (let-values ([(sfd* ht*) (hashtable-entries profile-database)]) (vector-for-each (lambda (sfd ht) (printf "~a:\n" (source-file-descriptor-name sfd)) (let-values ([(bfp* alist*) (hashtable-entries ht)]) (vector-for-each (lambda (bfp alist) (for-each (lambda (a) (printf " ~s, ~s: ~s\n" bfp (car a) (cadr a))) alist)) bfp* alist*))) sfd* ht*))))) (set! profile-query-weight (lambda (x) (define src->weight (lambda (src) (cond [(and profile-database (let* ([sfd (source-object-sfd src)] [ht (hashtable-ref profile-database sfd #f)]) (and ht (begin ; could do just one lookup if we had a nondestructive variant of ; hashtable-cell to call above (update-sfd! (hashtable-cell profile-database sfd #f) sfd) ht)))) => (lambda (ht) (let ([alist (hashtable-ref ht (source-object-bfp src) '())]) (cond [(assv (source-object-efp src) alist) => cadr] [(and (fxnegative? (source-object-bfp src)) (not (null? alist))) ($oops #f "block-profiling info is out-of-date for ~s" (source-file-descriptor-name (source-object-sfd src)))] ; no info for given bfp, efp...assume dead code and return 0 [else 0.0])))] ; no info for given sfd...assume not profiled and return #f [else #f]))) (if (source? x) (src->weight x) (let ([x (syntax->annotation x)]) (if (annotation? x) (src->weight (annotation-source x)) #f)))))) (let () ;;; The following copyright notice goes with the %html module. ;;; Copyright (c) 2005 R. Kent Dybvig ;;; Permission is hereby granted, free of charge, to any person obtaining a ;;; copy of this software and associated documentation files (the "Software"), ;;; to deal in the Software without restriction, including without limitation ;;; the rights to use, copy, modify, merge, publish, distribute, sublicense, ;;; and/or sell copies of the Software, and to permit persons to whom the ;;; Software is furnished to do so, subject to the following conditions: ;;; The above copyright notice and this permission notice shall be included in ;;; all copies or substantial portions of the Software. ;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ;;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ;;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL ;;; THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ;;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING ;;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER ;;; DEALINGS IN THE SOFTWARE. (module %html (( <*> attribute $tag) ( <*> attribute $tag) ( <*> attribute $tag) (