racket/collects/drracket/sprof.rkt
Robby Findler 0d6b825377 add a preference to control whether or not there is an extra
pixel of space in between lines in DrRacket.

This change is based on Matthew's experience having a look
at the font setup on the three platforms.

He writes:
>  * Mac OS X: the convention seems to be to add space between lines.
>   TextEdit, for example, looks like DrRacket: the maze has spaces.
>
>   (I can't find a font that makes the maze look right, actually, even
>   if I adjust the line spacing.)
>
>  * Windows: the convention seems to be that space is built into the
>   font. DrRacket (and SirMail) draw lines more sparsely than Notepad.
>
>   Perhaps consistent with the differing conventions, the height of
>   "Courier New" at 11-pixel size is 14 on Windows, 13 on Mac OS X.
>
>  * Unix: the convention seems to be to add space. DrRacket looks like
>   the default Terminal and Text Editor programs on Ubuntu.
>
>   The maze nevertheless looks right everywhere, because the glyphs
>   extend an extra pixel above the declared bounding box!
2012-03-24 20:20:05 -05:00

399 lines
14 KiB
Racket

#lang racket/base
(require racket/gui/base
framework
racket/class)
;; how long between samples
(define pause-time 0.1)
;; gui updates occur every 'update-frequency' samples
(define update-frequency 4)
(define (make-prod-thread get-threads update-gui)
(thread (lambda ()
(define traces-table (make-hash))
(let loop ([i 0])
(sleep pause-time)
(let ([new-traces
(map (λ (t) (continuation-mark-set->context (continuation-marks t)))
(get-threads))])
(for-each
(λ (trace)
(for-each
(λ (line)
(hash-set! traces-table line (cons trace (hash-ref traces-table line '()))))
trace))
new-traces)
(cond
[(zero? i)
(update-gui traces-table)
(loop update-frequency)]
[else
(loop (- i 1))]))))))
(define (format-fn-name i)
(let ([id (car i)]
[src (cdr i)])
(cond
[id (format "~a" id)]
[src
(format "~a:~a~a"
(cond
[(path? (srcloc-source src))
(let-values ([(base name dir?) (split-path (srcloc-source src))])
name)]
[else (srcloc-source src)])
(if (srcloc-line src)
(format "~a:~a"
(srcloc-line src)
(srcloc-column src))
(srcloc-position src))
(if id
(format ": ~a" id)
""))]
[else "???"])))
(define (insert-long-fn-name t i)
(send t begin-edit-sequence)
(send t erase)
(let ([id (car i)]
[src (cdr i)])
(when src
(send t insert
(format "~a:~a"
(srcloc-source src)
(if (srcloc-line src)
(format "~a:~a"
(srcloc-line src)
(srcloc-column src))
(format ":~a" (srcloc-position src))))))
(when (and id src)
(send t insert "\n"))
(when id
(send t insert (format (format "~a" id))))
(unless (or id src)
(send t insert "???")))
(send t end-edit-sequence))
(define (format-percentage n)
(let ([trunc (floor (* n 100))])
(format "~a%" (pad3 trunc))))
(define (pad3 n)
(cond
[(< n 10) (format "00~a" n)]
[(< n 100) (format "0~a" n)]
[else (format "~a" n)]))
(define cumulative-t%
(class text:line-spacing%
(init-field open-button vp ec1 lp info-editor)
(inherit begin-edit-sequence
end-edit-sequence
erase
find-position
get-admin
dc-location-to-editor-location
position-paragraph
insert
last-position
highlight-range
last-paragraph
lock)
(define gui-display-data '())
(define clicked-srcloc-pr #f)
(define line-to-source (make-hasheq))
(define clear-old-pr void)
(define/override (on-event event)
(cond
[(send event button-up? 'left)
(let ([admin (get-admin)])
(when admin
(let ([dc (send admin get-dc)])
(let-values ([(x y) (dc-location-to-editor-location (send event get-x)
(send event get-y))])
(let* ([loc (find-position x y)]
[para (position-paragraph loc)])
(set! clicked-srcloc-pr (and (<= 0 para (last-paragraph))
(car (list-ref gui-display-data para))))
(update-gui-display))))))]
[else (void)]))
(define/public (set-gui-display-data/refresh traces-table)
(set! gui-display-data
(sort (hash-map traces-table (λ (k v) (cons k v)))
>
#:key (λ (x) (length (cdr x)))))
(update-gui-display))
(define/public (clear-clicked)
(set! clicked-srcloc-pr #f)
(update-gui-display))
(define/private (update-gui-display)
(lock #f)
(begin-edit-sequence)
(erase)
(set! line-to-source (make-hasheq))
(clear-old-pr)
(set! clear-old-pr void)
(let* ([denom-ht (make-hasheq)]
[filtered-gui-display-data
(map
(λ (pr)
(let ([id (car pr)]
[stacks (filter-stacks (cdr pr))])
(for-each (λ (stack) (hash-set! denom-ht stack #t)) stacks)
(cons id stacks)))
gui-display-data)]
[denom-count (hash-count denom-ht)])
(let loop ([prs filtered-gui-display-data]
[first? #t]
[i 0])
(cond
[(null? prs) (void)]
[else
(let* ([pr (car prs)]
[fn (car pr)]
[count (length (cdr pr))])
(cond
[(zero? count)
(loop (cdr prs) first? i)]
[else
(unless first? (insert "\n"))
(let ([before (last-position)])
(hash-set! line-to-source i pr)
(insert (format-percentage (/ count denom-count)))
(insert (format " ~a" (format-fn-name fn)))
(let ([after (last-position)])
(when (equal? (car pr) clicked-srcloc-pr)
(set! clear-old-pr (highlight-range before after "NavajoWhite")))))
(loop (cdr prs) #f (+ i 1))]))]))
(lock #t)
(end-edit-sequence)
(update-info-editor clicked-srcloc-pr)
(send open-button enable (and clicked-srcloc-pr (path? (srcloc-source (cdr clicked-srcloc-pr)))))))
(define/private (filter-stacks stacks)
(cond
[(not clicked-srcloc-pr) stacks]
[else
(filter (λ (stack) (ormap (λ (stack-ent) (equal? clicked-srcloc-pr stack-ent))
stack))
stacks)]))
(define/public (open-current-pr)
(when clicked-srcloc-pr
(let ([src (cdr clicked-srcloc-pr)])
(when (path? (srcloc-source src))
(printf "open ~s\n" (srcloc-source src))
(when (number? (srcloc-position src))
(printf "go to ~s\n" (srcloc-position src)))))))
(define/private (update-info-editor pr)
(send vp change-children (λ (l) (if pr (list ec1 lp) (list ec1))))
(when pr
(insert-long-fn-name info-editor pr)))
(super-new)))
(define (construct-gui f)
(define info-editor (new text%))
(define vp (new vertical-panel% [parent f]))
(define ec1 (new editor-canvas% [parent vp]))
(define lp (new vertical-panel% [parent vp] [stretchable-height #f]))
(define ec2 (new editor-canvas%
[parent lp]
[min-height 100]
[stretchable-height #f]
[editor info-editor]))
(define bp (new horizontal-panel% [stretchable-height #f] [parent lp] [alignment '(center center)]))
(define open-button (new button%
[parent bp]
[label "Open"]
[callback
(λ (x y)
(send cumulative-t open-current-pr))]))
(define unlock (new button%
[label "Show All"]
[parent bp]
[callback
(λ (x y)
(send cumulative-t clear-clicked))]))
(define cumulative-t (new cumulative-t%
[open-button open-button]
[vp vp]
[ec1 ec1]
[lp lp]
[info-editor info-editor]))
(send ec1 set-editor cumulative-t)
(send vp change-children (λ (l) (list ec1)))
(send cumulative-t hide-caret #t)
(send cumulative-t lock #t)
(send info-editor auto-wrap #t)
(values vp cumulative-t))
;; running an example outside of drscheme
#;
(begin
(define evt (make-eventspace))
(define f (parameterize ([current-eventspace evt])
(new frame%
[label ""]
[width 400]
[height 800])))
(define-values (panel cumulative-t) (construct-gui f))
(send f show #t)
(void (make-prod-thread (let ([t (current-thread)])
(λ () (list t)))
(λ (traces-table)
(parameterize ([current-eventspace evt])
(queue-callback
(λ ()
(send cumulative-t set-gui-display-data/refresh traces-table)))))))
(time (dynamic-require '(lib "scribblings/reference/reference.scrbl")
#f)))
;; tool code, for integration with drscheme
(begin
(require drracket/tool
racket/unit
string-constants/string-constant)
(define sc-show-sprof "Show SProfile")
(define sc-hide-sprof "Hide SProfile")
(provide tool@)
(define tool@
(unit
(import drscheme:tool^)
(export drscheme:tool-exports^)
(define (phase1) (void))
(define (phase2) (void))
(define-local-member-name
show/hide-sprof-panel
update-sprof-panel
toggle-sprof-visiblity
stop-profiling-thread
start-profiling-thread
get-threads-to-profile)
(define unit-frame-mixin
(mixin (drscheme:unit:frame<%>) ()
(inherit get-current-tab)
(define main-panel #f)
(define sprof-main-panel #f)
(define everything-else #f)
(define cumulative-t #f)
(define show/hide-menu-item #f)
(define/public (show/hide-sprof-panel show?)
(let ([main-children (send main-panel get-children)])
(send show/hide-menu-item
set-label
(if show? sc-hide-sprof sc-show-sprof))
(unless (or (and show? (= 2 (length main-children)))
(and (not show?) (= 1 (length main-children))))
(send main-panel change-children
(λ (l)
(if show?
(list everything-else sprof-main-panel)
(list everything-else)))))))
(define/override (make-root-area-container cls parent)
(set! main-panel (super make-root-area-container panel:horizontal-dragable% parent))
(set! everything-else (make-object cls main-panel))
(set!-values (sprof-main-panel cumulative-t) (construct-gui main-panel))
(send main-panel change-children (λ (l) (list everything-else)))
everything-else)
(define/augment (on-tab-change from-tab to-tab)
(inner (void) on-tab-change from-tab to-tab)
(send to-tab update-sprof-panel))
(define/override (add-show-menu-items show-menu)
(super add-show-menu-items show-menu)
(set! show/hide-menu-item
(new menu-item%
[parent show-menu]
[label sc-show-sprof]
[callback
(λ (x y)
(send (get-current-tab) toggle-sprof-visiblity))])))
;; FIX: the cumulative-t text object shouldn't be handed out like this
;; instead its contents need to be tab specific, so switching tabs
;; (ala the update-sprof-panel method) should change the contents of
;; the cumulative-t, presumably via the set-gui-display-data/refresh method.
(define/public (get-cumulative-t) cumulative-t)
(super-new)))
(define tab-mixin
(mixin (drscheme:unit:tab<%>) ()
(inherit get-frame get-ints)
(define prof-visible? #f)
(define/public (toggle-sprof-visiblity)
(set! prof-visible? (not prof-visible?))
(cond
[prof-visible?
(start-profiling-thread)]
[else
(stop-profiling-thread)])
(update-sprof-panel))
(define/public (update-sprof-panel)
(send (get-frame) show/hide-sprof-panel prof-visible?))
(define profiling-thread #f)
(define/public (stop-profiling-thread)
(when profiling-thread
(kill-thread profiling-thread))
(set! profiling-thread #f))
(define current-traces-table #f)
(define/public (start-profiling-thread)
(stop-profiling-thread)
(set! profiling-thread (make-prod-thread
(λ () (send (get-ints) get-threads-to-profile))
(λ (traces-table)
(queue-callback
(λ ()
(send (send (get-frame) get-cumulative-t) set-gui-display-data/refresh traces-table)))))))
(super-new)))
(define system-custodian (current-custodian))
(define repl-mixin
(mixin (drscheme:rep:text<%>) ()
(inherit get-user-custodian)
(define/public (get-threads-to-profile)
(let ([thds '()])
(let loop ([cust (get-user-custodian)])
(for-each
(λ (obj)
(cond
[(custodian? obj) (loop obj)]
[(thread? obj) (set! thds (cons obj thds))]))
(custodian-managed-list cust system-custodian)))
thds))
;; FIX
;; something needs to happen here so that the profiling gets shutdown when the repl dies.
;; the right call back isn't obvious, tho. :(
(super-new)))
(drscheme:get/extend:extend-tab tab-mixin)
(drscheme:get/extend:extend-interactions-text repl-mixin)
(drscheme:get/extend:extend-unit-frame unit-frame-mixin))))