From e4b56fae9492bf7287490d72772d97784154b565 Mon Sep 17 00:00:00 2001 From: James Bornholt Date: Thu, 23 Aug 2018 13:44:24 -0700 Subject: [PATCH] Add symbolic profiling to Rosette. --- .travis.yml | 2 +- NOTES.md | 2 + info.rkt | 3 +- rosette/base/core/lift.rkt | 4 +- rosette/base/core/merge.rkt | 12 +- rosette/base/core/reporter.rkt | 15 + rosette/base/core/term.rkt | 8 +- rosette/base/core/union.rkt | 11 +- .../scribble/performance/performance-log.txt | 252 +++++++ .../scribble/performance/performance.scrbl | 424 ++++++++++++ .../scribble/performance/profile-xform.png | Bin 0 -> 129417 bytes .../guide/scribble/performance/profile.png | Bin 0 -> 186765 bytes rosette/doc/guide/scribble/refs.scrbl | 6 + .../doc/guide/scribble/rosette-guide.scrbl | 1 + rosette/doc/guide/scribble/util/lifted.rkt | 8 +- rosette/info.rkt | 6 + rosette/lib/profile.rkt | 15 + rosette/lib/profile/compile.rkt | 485 +++++++++++++ rosette/lib/profile/data.rkt | 42 ++ rosette/lib/profile/feature.rkt | 119 ++++ rosette/lib/profile/graph.rkt | 84 +++ rosette/lib/profile/raco.rkt | 103 +++ rosette/lib/profile/record.rkt | 155 +++++ rosette/lib/profile/renderer/heap.rkt | 205 ++++++ rosette/lib/profile/renderer/noop.rkt | 22 + rosette/lib/profile/renderer/renderer.rkt | 8 + rosette/lib/profile/renderer/report.rkt | 263 +++++++ .../lib/profile/renderer/report/callgraph.rkt | 49 ++ .../lib/profile/renderer/report/generic.rkt | 8 + .../report/html/css/d3-stack-graph.css | 21 + .../renderer/report/html/css/profile.css | 158 +++++ .../renderer/report/html/css/spinner.gif | Bin 0 -> 1533 bytes .../renderer/report/html/css/tablesort.css | 35 + .../renderer/report/html/js/analysis.js | 404 +++++++++++ .../renderer/report/html/js/analysis.ts | 406 +++++++++++ .../renderer/report/html/js/d3-stack-graph.js | 395 +++++++++++ .../renderer/report/html/js/d3-stack-graph.ts | 460 +++++++++++++ .../html/js/d3fc-discontinuous-scale.js | 645 ++++++++++++++++++ .../profile/renderer/report/html/js/data.js | 252 +++++++ .../profile/renderer/report/html/js/data.ts | 282 ++++++++ .../renderer/report/html/js/profile.js | 497 ++++++++++++++ .../renderer/report/html/js/profile.ts | 535 +++++++++++++++ .../renderer/report/html/js/tablesort.js | 296 ++++++++ .../renderer/report/html/js/tooltip.js | 81 +++ .../renderer/report/html/js/tooltip.ts | 83 +++ .../renderer/report/html/js/tsconfig.json | 7 + .../profile/renderer/report/html/profile.html | 41 ++ .../lib/profile/renderer/report/solver.rkt | 41 ++ rosette/lib/profile/renderer/report/terms.rkt | 64 ++ .../lib/profile/renderer/report/ws-server.rkt | 63 ++ rosette/lib/profile/renderer/syntax.rkt | 48 ++ rosette/lib/profile/renderer/trace.rkt | 33 + rosette/lib/profile/reporter.rkt | 116 ++++ rosette/lib/profile/tool.rkt | 33 + rosette/query/finitize.rkt | 5 + rosette/solver/smt/base-solver.rkt | 17 +- rosette/solver/smt/cmd.rkt | 2 + sdsl/bv/examples/easy.rkt | 3 +- sdsl/bv/examples/hard.rkt | 3 +- sdsl/bv/examples/medium.rkt | 3 +- sdsl/bv/examples/reference.rkt | 3 +- sdsl/bv/test/easy.rkt | 3 +- sdsl/bv/test/medium.rkt | 3 +- test/all-rosette-tests.rkt | 3 +- test/profile/benchmarks/exn.rkt | 17 + test/profile/benchmarks/list.rkt | 12 + test/profile/benchmarks/update-at.rkt | 25 + test/profile/output/micro-exn.out | 1 + test/profile/output/micro-update-at.out | 1 + test/profile/renderer.rkt | 53 ++ test/profile/test.rkt | 39 ++ 71 files changed, 7466 insertions(+), 35 deletions(-) create mode 100644 rosette/base/core/reporter.rkt create mode 100644 rosette/doc/guide/scribble/performance/performance-log.txt create mode 100644 rosette/doc/guide/scribble/performance/performance.scrbl create mode 100644 rosette/doc/guide/scribble/performance/profile-xform.png create mode 100644 rosette/doc/guide/scribble/performance/profile.png create mode 100644 rosette/lib/profile.rkt create mode 100644 rosette/lib/profile/compile.rkt create mode 100644 rosette/lib/profile/data.rkt create mode 100644 rosette/lib/profile/feature.rkt create mode 100644 rosette/lib/profile/graph.rkt create mode 100644 rosette/lib/profile/raco.rkt create mode 100644 rosette/lib/profile/record.rkt create mode 100644 rosette/lib/profile/renderer/heap.rkt create mode 100644 rosette/lib/profile/renderer/noop.rkt create mode 100644 rosette/lib/profile/renderer/renderer.rkt create mode 100644 rosette/lib/profile/renderer/report.rkt create mode 100644 rosette/lib/profile/renderer/report/callgraph.rkt create mode 100644 rosette/lib/profile/renderer/report/generic.rkt create mode 100644 rosette/lib/profile/renderer/report/html/css/d3-stack-graph.css create mode 100644 rosette/lib/profile/renderer/report/html/css/profile.css create mode 100644 rosette/lib/profile/renderer/report/html/css/spinner.gif create mode 100644 rosette/lib/profile/renderer/report/html/css/tablesort.css create mode 100644 rosette/lib/profile/renderer/report/html/js/analysis.js create mode 100644 rosette/lib/profile/renderer/report/html/js/analysis.ts create mode 100644 rosette/lib/profile/renderer/report/html/js/d3-stack-graph.js create mode 100644 rosette/lib/profile/renderer/report/html/js/d3-stack-graph.ts create mode 100644 rosette/lib/profile/renderer/report/html/js/d3fc-discontinuous-scale.js create mode 100644 rosette/lib/profile/renderer/report/html/js/data.js create mode 100644 rosette/lib/profile/renderer/report/html/js/data.ts create mode 100644 rosette/lib/profile/renderer/report/html/js/profile.js create mode 100644 rosette/lib/profile/renderer/report/html/js/profile.ts create mode 100644 rosette/lib/profile/renderer/report/html/js/tablesort.js create mode 100644 rosette/lib/profile/renderer/report/html/js/tooltip.js create mode 100644 rosette/lib/profile/renderer/report/html/js/tooltip.ts create mode 100644 rosette/lib/profile/renderer/report/html/js/tsconfig.json create mode 100644 rosette/lib/profile/renderer/report/html/profile.html create mode 100644 rosette/lib/profile/renderer/report/solver.rkt create mode 100644 rosette/lib/profile/renderer/report/terms.rkt create mode 100644 rosette/lib/profile/renderer/report/ws-server.rkt create mode 100644 rosette/lib/profile/renderer/syntax.rkt create mode 100644 rosette/lib/profile/renderer/trace.rkt create mode 100644 rosette/lib/profile/reporter.rkt create mode 100644 rosette/lib/profile/tool.rkt create mode 100644 test/profile/benchmarks/exn.rkt create mode 100644 test/profile/benchmarks/list.rkt create mode 100644 test/profile/benchmarks/update-at.rkt create mode 100644 test/profile/output/micro-exn.out create mode 100644 test/profile/output/micro-update-at.out create mode 100644 test/profile/renderer.rkt create mode 100644 test/profile/test.rkt diff --git a/.travis.yml b/.travis.yml index 9448f4ed..829f68f6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,7 +34,7 @@ before_install: install: - - raco pkg install + - raco pkg install --auto script: - time raco make test/all-rosette-tests.rkt diff --git a/NOTES.md b/NOTES.md index 7a0be8b0..f17983d4 100644 --- a/NOTES.md +++ b/NOTES.md @@ -11,9 +11,11 @@ The semantics of Rosette 3.0 differs from Rosette 2.x in two ways: This release also includes the following new functionality and features contributed by [James Bornholt][] and [Phitchaya Mangpo Phothilimthana][]: +- Developed a new *symbolic profiler* for diagnosing performance issues in Rosette programs. The symbolic profiler instruments Rosette and tracks key performance metrics to identify potential issues. To use the symbolic profiler, run the command `raco symprofile program.rkt`. A new [performance][] chapter in the Rosette guide details common performance issues and how to use the symbolic profiler to identify them. - Extended and generalized the interface to constraint solvers. The new interface allows the client code to specify a path to the solver, set the logic, provide solver-specific configuration options, and export the problem encodings sent to the solver. - Added support for four new solvers: [Boolector][], [CVC4][], [Yices][], and [CPLEX][]. These solvers are not included in the default distribution and need to be installed separately for use with Rosette. +[performance]: https://docs.racket-lang.org/rosette-guide/ch_performance.html [Boolector]: https://docs.racket-lang.org/rosette-guide/sec_solvers-and-solutions.html#%28def._%28%28lib._rosette%2Fsolver%2Fsmt%2Fboolector..rkt%29._boolector%29%29 [CVC4]: https://docs.racket-lang.org/rosette-guide/sec_solvers-and-solutions.html#%28def._%28%28lib._rosette%2Fsolver%2Fsmt%2Fcvc4..rkt%29._cvc4%29%29 [Yices]: https://docs.racket-lang.org/rosette-guide/sec_solvers-and-solutions.html#%28def._%28%28lib._rosette%2Fsolver%2Fsmt%2Fyices..rkt%29._yices%29%29 diff --git a/info.rkt b/info.rkt index 063fef11..8452bd90 100644 --- a/info.rkt +++ b/info.rkt @@ -3,13 +3,14 @@ (define collection 'multi) (define deps '("r6rs-lib" + "rfc6455" "rackunit-lib" "slideshow-lib" "base")) (define build-deps '("pict-doc" "scribble-lib" - "racket-doc")) + "racket-doc")) (define test-omit-paths (if (getenv "PLT_PKG_BUILD_SERVICE") 'all '())) diff --git a/rosette/base/core/lift.rkt b/rosette/base/core/lift.rkt index d2343034..d6901ae3 100644 --- a/rosette/base/core/lift.rkt +++ b/rosette/base/core/lift.rkt @@ -52,12 +52,12 @@ (define-syntax (define/lift stx) (syntax-case stx (: :: ->) [(_ (id0 id ...) :: contracted? -> rosette-type?) - (or (identifier? #'contracted) (raise-argument-error "identifier?" #'contracted?)) + (or (identifier? #'contracted?) (raise-argument-error "identifier?" #'contracted?)) #'(begin (define/lift id0 :: contracted? -> rosette-type?) (define/lift id :: contracted? -> rosette-type?) ...)] [(_ id :: contracted? -> rosette-type?) ; repeated from (_ id : contracted? -> rosette-type?) - params don't work - (or (identifier? #'contracted) (raise-argument-error "identifier?" #'contracted?)) + (or (identifier? #'contracted?) (raise-argument-error "identifier?" #'contracted?)) #`(define (#,(lift-id #'id) val) (if (contracted? val) (id val) diff --git a/rosette/base/core/merge.rkt b/rosette/base/core/merge.rkt index 73ea7e30..856049d5 100644 --- a/rosette/base/core/merge.rkt +++ b/rosette/base/core/merge.rkt @@ -2,7 +2,7 @@ (require (only-in rnrs/base-6 assert) (only-in racket/unsafe/ops [unsafe-car car] [unsafe-cdr cdr]) - "term.rkt" "union.rkt" "bool.rkt") + "term.rkt" "union.rkt" "bool.rkt" "reporter.rkt") (provide merge merge* unsafe-merge* merge-same) @@ -32,10 +32,12 @@ (do-merge* #t ps)) (define-syntax-rule (do-merge* force? ps) - (match (compress force? (simplify ps)) - [(list (cons g v)) (assert (not (false? g))) v] - [(list _ (... ...) (cons #t v) _ (... ...)) v] - [vs (apply union vs)])) + (let ([simp (simplify ps)]) + ((current-reporter) 'merge (length simp)) + (match (compress force? simp) + [(list (cons g v)) (assert (not (false? g))) v] + [(list _ (... ...) (cons #t v) _ (... ...)) v] + [vs (apply union vs)]))) (define (guard-&& a b) (match b diff --git a/rosette/base/core/reporter.rkt b/rosette/base/core/reporter.rkt new file mode 100644 index 00000000..f95a18a2 --- /dev/null +++ b/rosette/base/core/reporter.rkt @@ -0,0 +1,15 @@ +#lang racket + +(require racket/generic (for-syntax racket/syntax)) +(provide (all-defined-out)) + +; The reporter is called when "interesting" +; events happen during symbolic execution; for example, +; when a merge occurs or a new term is created. +(define current-reporter + (make-parameter + void + (lambda (new-reporter) + (unless (procedure? new-reporter) + (raise-argument-error 'current-reporder "procedure?" new-reporter)) + new-reporter))) diff --git a/rosette/base/core/term.rkt b/rosette/base/core/term.rkt index 57278a33..57f7c885 100644 --- a/rosette/base/core/term.rkt +++ b/rosette/base/core/term.rkt @@ -1,6 +1,7 @@ #lang racket -(require racket/syntax (for-syntax racket racket/syntax) racket/generic "type.rkt") +(require racket/syntax (for-syntax racket racket/syntax) racket/generic + "type.rkt" "reporter.rkt") (provide term-cache clear-terms! @@ -18,7 +19,7 @@ ; of expressions with commutative operators. #|-----------------------------------------------------------------------------------|# (define term-cache (make-parameter (make-hash))) -(define term-count (make-parameter 0)) +(define term-count (make-parameter 0)) ; Clears the entire term-cache if invoked with #f (default), or ; it clears all terms reachable from the given set of leaf terms. @@ -67,11 +68,12 @@ (define (term x 15))) + ((3) + 1 + (((lib "rosette/doc/guide/scribble/util/lifted.rkt") + . + deserialize-info:opaque-v0)) + 0 + () + () + (c values c (0 (u . "(unsat)")))) + #"" + #"") +((define (list-set lst idx val) + (let-values (((front back) (split-at lst idx))) + (append front (cons val (cdr back))))) + ((3) 0 () 0 () () (c values c (void))) + #"" + #"") +((list-set '(a b c) 1 'd) ((3) 0 () 0 () () (q values (a d c))) #"" #"") +((define-symbolic* idx len integer?) + ((3) 0 () 0 () () (c values c (void))) + #"" + #"") +((define lst (take '(a b c) len)) + ((3) 0 () 0 () () (c values c (void))) + #"" + #"") +((list-set lst idx 'd) + ((3) + 1 + (((lib "rosette/doc/guide/scribble/util/lifted.rkt") + . + deserialize-info:opaque-v0)) + 0 + () + () + (c values c (0 (u . "{2846099531607739527:6}")))) + #"" + #"") +((define (list-set* lst idx val) + (for/all + ((lst lst)) + (map + (lambda (i v) (if (= idx i) val v)) + (build-list (length lst) identity) + lst))) + ((3) 0 () 0 () () (c values c (void))) + #"" + #"") +((list-set* '(a b c) 1 'd) ((3) 0 () 0 () () (q values (a d c))) #"" #"") +((list-set* lst idx 'd) + ((3) + 1 + (((lib "rosette/doc/guide/scribble/util/lifted.rkt") + . + deserialize-info:opaque-v0)) + 0 + () + () + (c values c (0 (u . "{-4322348582163789884:4}")))) + #"" + #"") +((define-values (width height) (values 5 5)) + ((3) 0 () 0 () () (c values c (void))) + #"" + #"") +((define-symbolic* x y integer?) + ((3) 0 () 0 () () (c values c (void))) + #"" + #"") +((define grid/2d (for/vector ((_ height)) (make-vector width #f))) + ((3) 0 () 0 () () (c values c (void))) + #"" + #"") +((vector-set! (vector-ref grid/2d y) x 'a) + ((3) 0 () 0 () () (c values c (void))) + #"" + #"") +((define grid/flat (make-vector (* width height) #f)) + ((3) 0 () 0 () () (c values c (void))) + #"" + #"") +((vector-set! grid/flat (+ (* y width) x) 'a) + ((3) 0 () 0 () () (c values c (void))) + #"" + #"") +((define (maybe-ref lst idx) (if (<= 0 idx 1) (list-ref lst idx) -1)) + ((3) 0 () 0 () () (c values c (void))) + #"" + #"") +((define-symbolic* idx integer?) + ((3) 0 () 0 () () (c values c (void))) + #"" + #"") +((maybe-ref '(5 6 7) idx) + ((3) + 1 + (((lib "rosette/doc/guide/scribble/util/lifted.rkt") + . + deserialize-info:opaque-v0)) + 0 + () + () + (c + values + c + (0 + (u + . + "(ite (&& (<= 0 idx$0) (<= idx$0 1)) (ite* (⊢ (= 0 idx$0) 5) (⊢ (= 1 idx$0) 6) (⊢ (= 2 idx$0) 7)) -1)")))) + #"" + #"") +((define (maybe-ref* lst idx) + (cond ((= idx 0) (list-ref lst 0)) ((= idx 1) (list-ref lst 1)) (else -1))) + ((3) 0 () 0 () () (c values c (void))) + #"" + #"") +((maybe-ref* '(5 6 7) idx) + ((3) + 1 + (((lib "rosette/doc/guide/scribble/util/lifted.rkt") + . + deserialize-info:opaque-v0)) + 0 + () + () + (c values c (0 (u . "(ite (= 0 idx$0) 5 (ite (= 1 idx$0) 6 -1))")))) + #"" + #"") +((define-values (Add Sub Sqr Nop) (values (bv 0 2) (bv 1 2) (bv 2 2) (bv 3 2))) + ((3) 0 () 0 () () (c values c (void))) + #"" + #"") +((define (calculate prog (acc (bv 0 4))) + (cond + ((null? prog) acc) + (else + (define ins (car prog)) + (define op (car ins)) + (calculate + (cdr prog) + (cond + ((eq? op Add) (bvadd acc (cadr ins))) + ((eq? op Sub) (bvsub acc (cadr ins))) + ((eq? op Sqr) (bvmul acc acc)) + (else acc)))))) + ((3) 0 () 0 () () (c values c (void))) + #"" + #"") +((define (list-set lst idx val) + (match + lst + ((cons x xs) + (if (= idx 0) (cons val xs) (cons x (list-set xs (- idx 1) val)))) + (_ lst))) + ((3) 0 () 0 () () (c values c (void))) + #"" + #"") +((define (sub->add prog idx) + (define ins (list-ref prog idx)) + (if (eq? (car ins) Sub) + (list-set prog idx (list Add (bvneg (cadr ins)))) + prog)) + ((3) 0 () 0 () () (c values c (void))) + #"" + #"") +((define (verify-xform xform N) + (define P + (for/list + ((i N)) + (define-symbolic* op (bitvector 2)) + (define-symbolic* arg (bitvector 4)) + (if (eq? op Sqr) (list op) (list op arg)))) + (define-symbolic* acc (bitvector 4)) + (define-symbolic* idx integer?) + (define xP (xform P idx)) + (verify (assert (eq? (calculate P acc) (calculate xP acc))))) + ((3) 0 () 0 () () (c values c (void))) + #"" + #"") +((verify-xform sub->add 5) + ((3) + 1 + (((lib "rosette/doc/guide/scribble/util/lifted.rkt") + . + deserialize-info:opaque-v0)) + 0 + () + () + (c values c (0 (u . "(unsat)")))) + #"" + #"") +((clear-asserts!) ((3) 0 () 0 () () (c values c (void))) #"" #"") +((clear-terms!) ((3) 0 () 0 () () (c values c (void))) #"" #"") +((define-symbolic* idx integer?) + ((3) 0 () 0 () () (c values c (void))) + #"" + #"") +((list-set '(1 2 3) idx 4) + ((3) + 1 + (((lib "rosette/doc/guide/scribble/util/lifted.rkt") + . + deserialize-info:opaque-v0)) + 0 + () + () + (c + values + c + (c + (0 (u . "(ite (= 0 idx$0) 4 1)")) + c + (0 (u . "(ite (= 0 idx$0) 2 (ite (= 0 (+ -1 idx$0)) 4 2))")) + c + (0 + (u + . + "(ite (= 0 idx$0) 3 (ite (= 0 (+ -1 idx$0)) 3 (ite (= 0 (+ -2 idx$0)) 4 3)))"))))) + #"" + #"") +((define (list-set* lst idx val) + (match + lst + ((cons x xs) (cons (if (= idx 0) val x) (list-set* xs (- idx 1) val))) + (_ lst))) + ((3) 0 () 0 () () (c values c (void))) + #"" + #"") +((list-set* '(1 2 3) idx 4) + ((3) + 1 + (((lib "rosette/doc/guide/scribble/util/lifted.rkt") + . + deserialize-info:opaque-v0)) + 0 + () + () + (c + values + c + (c + (0 (u . "(ite (= 0 idx$0) 4 1)")) + c + (0 (u . "(ite (= 0 (+ -1 idx$0)) 4 2)")) + c + (0 (u . "(ite (= 0 (+ -2 idx$0)) 4 3)"))))) + #"" + #"") diff --git a/rosette/doc/guide/scribble/performance/performance.scrbl b/rosette/doc/guide/scribble/performance/performance.scrbl new file mode 100644 index 00000000..330086a4 --- /dev/null +++ b/rosette/doc/guide/scribble/performance/performance.scrbl @@ -0,0 +1,424 @@ +#lang scribble/manual + +@(require scribble/core scribble/html-properties + scribble/bnf scribble/eval + (for-label (except-in racket list-set) errortrace + rosette/base/core/term + rosette/base/form/define + rosette/query/form + rosette/base/core/union + (only-in rosette/query/finitize current-bitwidth) + (only-in rosette/base/base bitvector) + (only-in rosette/base/core/safe assert) + (only-in rosette/base/core/forall for/all)) + racket/runtime-path + "../util/lifted.rkt" + (only-in "../refs.scrbl" ~cite sympro:oopsla18)) + +@(define-runtime-path root ".") +@(define rosette-eval (rosette-log-evaluator (logfile root "performance-log") #f 'rosette)) + +@(define-runtime-path profile.png "profile.png") +@(define-runtime-path profile-xform.png "profile-xform.png") + +@title[#:tag "ch:performance"]{Performance} + +Rosette provides an efficient, general-purpose runtime for solver-aided programming. +But as with any other form of programming, +scaling this runtime to challenging problems involves careful design, +and sometimes requires changes to code to better suit Rosette's +symbolic virtual machine (SVM) style of execution. +This chapter describes common performance problems and solutions, +as well as tools built into Rosette for diagnosing these problems. + +@section[#:tag "sec:antipatterns"]{Common Performance Issues} + +When a Rosette program performs poorly, +it is often due to one of four common issues, described next. + +@subsection{Integer and Real Theories} + +Rosette supports assertions containing symbolic values of integer or real type. +But satisfiability solving with these types is expensive (or, in the worst case, undecidable), +and even simple queries can be unacceptably slow. + +One solution to performance issues with assertions involving integers or reals +is Rosette's @racket[current-bitwidth] parameter, +which controls the @tech{reasoning precision} used for queries. +When @racket[current-bitwidth] is set to a value @emph{k} other than @racket[#f], +Rosette approximates @racket[integer?] and @racket[real?] values using signed @emph{k}-bit @racket[bitvector]s. +This approximation can make assertions involving integers and reals +more efficiently decidable. + +But this approximation is unsound and may produce results that are +incorrect under the infinite-precision semantics of integers and reals +(while being correct under the finite-precision semantics). +For example, this program incorrectly says that no integer greater than 15 exists, +because the setting of @racket[current-bitwidth] causes it to consider only values of @racket{x} +that can be represented as a 5-bit bitvector. + +@interaction[#:eval rosette-eval +(current-bitwidth 5) +(define-symbolic x integer?) +(solve (assert (> x 15)))] + +So, choosing the right reasoning precision for an application involves navigating this tradeoff +between performance and soundness. + +@subsection{Algorithmic Mismatch} + +Small algorithmic changes can have a large impact on the efficiency +of the symbolic evaluation performed by the Rosette SVM. +Often, the most efficient algorithm for symbolic inputs +is different to the most efficient one for concrete inputs---an +@deftech{algorithmic mismatch}. + +For example, +consider this function to set the @tt{idx}th element of a list @tt{lst} to @tt{val}: + +@interaction[#:eval rosette-eval +(define (list-set lst idx val) + (let-values ([(front back) (split-at lst idx)]) + (append front (cons val (cdr back))))) +(list-set '(a b c) 1 'd) +] + +While appropriate for concrete inputs, +this function exhibits poor performance when the +inputs are symbolic: + +@interaction[#:eval rosette-eval +(define-symbolic* idx len integer?) +(define lst (take '(a b c) len)) +(code:line (list-set lst idx 'd) (code:comment "symbolic union with 6 parts")) +] + +The root cause is the @racket[split-at] operation, +which separates the front and back of the list +into different variables. +Because the index @racket[idx] to split at is symbolic, +the Rosette SVM creates two @tech{symbolic unions} to capture +the possible front and back values +as a function of @racket[idx]. +Even though the possible values of @racket[front] and @racket[back] +are related, this separation loses the relationship. + +A better implementation for symbolic inputs +avoids splitting the list by iterating over it, +updating each position depending on whether its index is equal to @tt{idx}: + +@interaction[#:eval rosette-eval +(define (list-set* lst idx val) + (for/all ([lst lst]) + (map (lambda (i v) (if (= idx i) val v)) + (build-list (length lst) identity) + lst))) +(list-set* '(a b c) 1 'd) +(code:line (list-set* lst idx 'd) (code:comment "smaller symbolic union with 4 parts")) +] + +@subsection{Irregular Representation} + +Just as the best algorithm for symbolic inputs can differ +from that for concrete inputs (an @tech{algorithmic mismatch}), +so can the best data structure. +Programming with symbolic values is most efficient +when data structures are regular; +even though an @deftech{irregular representation} may be more space efficient for concrete data, +it can have negative performance impacts when the data is symbolic. + +For example, consider representing a (mutable) 2D grid data structure +using Rosette's lifted support for @tech[#:doc '(lib "scribblings/reference/reference.scrbl")]{vectors}. +The obvious representation is to use nested vectors to represent the two dimensions: + +@interaction[#:eval rosette-eval +(define-values (width height) (values 5 5)) +(define-symbolic* x y integer?) +(define grid/2d + (for/vector ([_ height]) + (make-vector width #f))) +(vector-set! (vector-ref grid/2d y) x 'a) +] + +This representation is inefficient when indexed with symbolic values, +because the dereferences are irregular: +the dereference of the @racket[y]-coordinate returns a vector, +whereas the dereference of the @racket[x]-coordinate returns a value. +This irregularity requires Rosette to perform more symbolic evaluation work +to faithfully track the usages of the nested vector. + +An alternative representation stores the entire grid in one vector, +indexed using simple arithmetic: + +@interaction[#:eval rosette-eval +(define grid/flat + (make-vector (* width height) #f)) +(vector-set! grid/flat (+ (* y width) x) 'a) +] + +This variant improves performance by about 2×. + +@subsection{Missed Concretization} + +In addition to employing careful algorithmic and representational choices, +fast solver-aided code provides as much information as possible +about the feasible choices of symbolic values. +Failure to make this information explicit +results in @deftech{missed concretization} opportunities, and +these misses can cause significant performance degradation. + +For example, consider the following toy procedure, +that returns the @racket[idx]th element of the list @racket[lst], +but only if @racket[idx] is 0 or 1: + +@interaction[#:eval rosette-eval +(define (maybe-ref lst idx) + (if (<= 0 idx 1) + (list-ref lst idx) + -1)) +(define-symbolic* idx integer?) +(maybe-ref '(5 6 7) idx) +] + +This procedure has poor performance when given a symbolic index @racket[idx], +because the call to @racket[(list-ref lst idx)] +passes a symbolic index, +but the conditional establishes that the only possible values for that index are 0 or 1. +When the Rosette SVM evaluates the first side of the conditional, +it does not simplify the value of @racket[idx] to be only 0 or 1, +and so the resulting encoding creates infeasible branches +for cases where @racket[idx] is outside that range. +An alternative version captures that concreteness: + +@interaction[#:eval rosette-eval +(define (maybe-ref* lst idx) + (cond [(= idx 0) (list-ref lst 0)] + [(= idx 1) (list-ref lst 1)] + [else -1])) +(maybe-ref* '(5 6 7) idx) +] + +This variant avoids generating infeasible return values +for the cases where @racket[idx] is greater than 1. + +@section[#:tag "sec:sympro"]{Symbolic Profiling} + +Rosette includes a @deftech[#:key "symbolic profiler"]{symbolic profiler} +for diagnosing performance issues in solver-aided programs. +The symbolic profiler instruments key metrics in the Rosette SVM +and relates them to the performance of a Rosette program, +suggesting the locations of potential bottlenecks---that is, parts of +the program that are difficult to evaluate symbolically. +More details about symbolic profiling are available +in the related technical paper @~cite[sympro:oopsla18]. + +@bold{Running the symbolic profiler} has the same limitations as @racketmodname[errortrace]: +before running it, throw away any @filepath{.zo} versions of your program +(i.e., delete any @filepath{compiled} folders in your code's folder hierarchy). + +Then invoke the symbolic profiler on a program file @nonterm{prog} using @exec{raco}: + +@commandline{raco symprofile @nonterm{prog}} + +After executing @nonterm{prog}, +the symbolic profiler produces a browser-based output summarizing the results, +similar to this output: + +@(image profile.png #:scale 0.7) + +The top half of this output visualizes the evolution of the Racket call stack +over time. +Each procedure call is a rectangle in this chart, +with its width corresponding to the total time taken by the call. +Blue highlighted regions reflect @tech{solver} activity +generated by Rosette @seclink["sec:queries"]{queries}. + +The bottom half summarizes important metrics about the SVM on a per-procedure basis. +Each procedure invoked by the program has a row in the table, +with columns that measure: + +@itemlist[ + @item{The total @bold{time} taken by all invocations of the procedure.} + @item{The @bold{term count}, the total number of @tech[#:key "symbolic term"]{symbolic terms} created by the procedure.} + @item{The number of @bold{unused terms}, which are terms created but never sent to a solver.} + @item{The @bold{union size}, counting the total size of all @tech[#:key "symbolic unions"]{symbolic unions} created by the procedure.} + @item{The @bold{merge count}, summing the number of execution paths merged by the SVM within the procedure.} + ] + +Procedures are ranked by a @bold{score}, which summarizes the other data in the table. +Procedures with higher scores are more likely to be bottlenecks, +and should be investigated first for performance issues. + + +@subsection[#:tag "sec:sympro:opts"]{Options and Caveats} + +By default, the symbolic profiler instruments only code that is within +a module whose initial module path is either @tt{rosette} or @tt{rosette/safe}. +In other words, only files beginning with @tt{#lang rosette} or @tt{#lang rosette/safe} will be instrumented. +To instrument @emph{all} code, use the @DFlag{racket} flag described below. + +The @exec{raco symprofile @nonterm{prog}} command accepts the following command-line flags: + +@itemlist[ + @item{@DFlag{stream} --- stream profile data to a browser while executing + the program, rather than producing output only once the program completes. + This option is useful for programs that do not terminate, or take a very long + time to run.} + + @item{@Flag{d} @nonterm{delay} --- delay between samples when using the @DFlag{stream} option, + in seconds (defaults to 2 s).} + + @item{@Flag{m} @nonterm{module-name} --- run the specified @nonterm{module-name} + submodule of @nonterm{prog} (defaults to the @tt{main} submodule).} + + @item{@Flag{t} @nonterm{threshold} --- prune function calls whose execution time is less + than @nonterm{threshold} milliseconds (defaults to 1 ms).} + + @item{@DFlag{racket} --- instrument code in any module, not just those + derived from Rosette.} + + ] + + + + +@section{Walkthrough: Debugging Rosette Performance} + +To illustrate a typical Rosette performance debugging process, +consider building a small solver-aided program +for verifying optimizations in a toy calculator language. +First, we define the calculator language, +in which programs are lists of operations, +and specify its semantics +with a simple recursive interpreter: + +@interaction[#:eval rosette-eval +(code:comment "Calculator opcodes.") +(define-values (Add Sub Sqr Nop) + (values (bv 0 2) (bv 1 2) (bv 2 2) (bv 3 2))) + +(code:comment "An interpreter for calculator programs.") +(code:comment "A program is a list of '(op) or '(op arg) instructions") +(code:comment "that update acc, where op is a 2-bit opcode and arg is") +(code:comment "a 4-bit constant.") +(define (calculate prog [acc (bv 0 4)]) + (cond ; An interpreter for + [(null? prog) acc] ; calculator programs. + [else ; A program is list of + (define ins (car prog)) ; '(op) or '(op arg) + (define op (car ins)) ; instructions that up- + (calculate ; date acc, where op is + (cdr prog) ; a 2-bit opcode and arg + (cond ; is a 4-bit constant. + [(eq? op Add) (bvadd acc (cadr ins))] + [(eq? op Sub) (bvsub acc (cadr ins))] + [(eq? op Sqr) (bvmul acc acc)] + [else acc]))]))] + +One potential optimization for programs in this language +is to replace subtractions with additions. +The @tt{sub->add} procedure performs this operation +at a given index in a program: + +@interaction[#:eval rosette-eval +(code:comment "Functionally sets lst[idx] to val.") +(define (list-set lst idx val) ; Functionally sets + (match lst ; lst[idx] to val. + [(cons x xs) + (if (= idx 0) + (cons val xs) + (cons x (list-set xs (- idx 1) val)))] + [_ lst])) + +(code:comment "Replaces Sub with Add if possible.") +(define (sub->add prog idx) ; Replaces Sub with + (define ins (list-ref prog idx)) ; Add if possible. + (if (eq? (car ins) Sub) + (list-set prog idx (list Add (bvneg (cadr ins)))) + prog))] + +To check that this optimization is correct, +we implement a tiny verification tool @tt{verify-xform} +that constructs a symbolic calculator program of size @tt{N}, +applies the optimization, +and checks that the original and optimized programs +produce the same outputs: + +@interaction[#:eval rosette-eval +(code:comment "Verifies the given transform for all programs of length N.") +(define (verify-xform xform N) ; Verifies the given + (define P ; transform for all + (for/list ([i N]) ; programs of length N. + (define-symbolic* op (bitvector 2)) + (define-symbolic* arg (bitvector 4)) + (if (eq? op Sqr) (list op) (list op arg)))) + (define-symbolic* acc (bitvector 4)) + (define-symbolic* idx integer?) + (define xP (xform P idx)) + (verify ; ∀ acc, idx, P. P(acc) = xform(P, idx)(acc) + (assert (eq? (calculate P acc) (calculate xP acc))))) +] + +We can verify @tt{sub->add} for all calculator programs of size 5: + +@interaction[#:eval rosette-eval +(verify-xform sub->add 5) +] + +which produces no counterexamples, as expected. + +@(rosette-eval '(clear-asserts!)) +@(rosette-eval '(clear-terms!)) +@subsection{Performance Bottlenecks} + +Verifying @tt{sub->add} for larger values of @tt{N} +causes the performance of @tt{verify-xform} to degrade, +from less than a second when @tt{N} = 5 to +a dozen seconds when @tt{N} = 20. +To identify the source of this performance issue, +we can invoke the @tech{symbolic profiler} on the verifier, +producing the output below (after selecting the "Collapse solver time" checkbox): + +@(image profile-xform.png #:scale 0.7) + +The symbolic profiler identifies @tt{list-set} as the bottleneck in this program. +The output shows that @tt{list-set} creates many symbolic terms, +and performs many symbolic operations (the "Union Size" and "Merge Count" columns). + +The core issue here is an @tech{algorithmic mismatch}: +@tt{list-set} makes a recursive call guarded by a short-circuiting condition +@racket[(= idx 0)] that is symbolic when @racket[idx] is unknown. +When a condition's truth value is unknown, +the Rosette SVM must execute both branches of the conditional, +and then merge the two resulting values together +under @tech{path conditions} that summarize the branching decisions +required to reach each value. +In this example, this @emph{symbolic execution} +therefore always executes the recursive call, +and each call creates a larger path condition, +since it must summarize all the previous recursive calls. +This behavior leads to a quadratic growth in the symbolic representation of the list +returned by @tt{list-set}: +@interaction[#:eval rosette-eval +(define-symbolic* idx integer?) +(list-set '(1 2 3) idx 4)] + +The solution is to alter @tt{list-set} to recurse unconditionally: + +@interaction[#:eval rosette-eval +(define (list-set* lst idx val) + (match lst + [(cons x xs) + (cons (if (= idx 0) val x) + (list-set* xs (- idx 1) val))] + [_ lst]))] + +In this revision, the SVM still evaluates both branches +of the conditional, but neither side of the conditional recurses, +and so the path conditions no longer grow quadratically. + +@interaction[#:eval rosette-eval +(list-set* '(1 2 3) idx 4)] + +The performance of @tt{verify-xform} after this change +improves by 2× for @tt{N} = 20. \ No newline at end of file diff --git a/rosette/doc/guide/scribble/performance/profile-xform.png b/rosette/doc/guide/scribble/performance/profile-xform.png new file mode 100644 index 0000000000000000000000000000000000000000..179b3e404826f5ef05b99822c2aee40b2c6e1eac GIT binary patch literal 129417 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 MtM;l=`d!F>0dXR0YXATM literal 0 HcmV?d00001 diff --git a/rosette/doc/guide/scribble/performance/profile.png b/rosette/doc/guide/scribble/performance/profile.png new file mode 100644 index 0000000000000000000000000000000000000000..0441a844fd91b623a374751767e2d91f7f43089a GIT binary patch literal 186765 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

;>4x{mXN(eo*Vh*}6EqZ!! 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

  • vT*T<=4`Jl4&;u`yfI3u-U^F!WHfebcGupwJnW|W#-)@t0ZaIJ%Zw2klWlKjT( zKD8@qtkvf9{EHNd{XeGuqr8~SupkGL!J{}z5lNk%nlOb>jgC6Wwqa9P}gP*8BE@cBkZQt9oxw#Z-y)LTcR zXwrjtbc}p1lHY7H$qy5+wy-UVmGJd&bpSZ)s)YW^AUL<`hWrLQ>ZW3~5_zdf|A@?x z;%jq*HO1^{GzDf#^l0PXYb1%Ek9MQ5q^Bgr)MWc#Ih3=7|$7IQ|s9eQ@8 zBi>bW;(!4WstBOLZx;+QQ?hhI!4lH^kqYdw5W*XaoPalI9T_tUi2&@Zs^1}a z*6^vvPsH&_3end?3>>m~0WL}nzEtPX8$ux>L&CX7`##3%ch{fc0`Ab%&)jq%Y;?3g z_dz!f<%8n=71i>`!dZE22a`Gt?IHZtCnIE9K)1cHjjQg7u1T|d>p)p-ZtGlBBULu0 zH@j&)bplle{w)Y#XIAhV-`kgH`zuO~Qz5&Z|4j~_`l;UC$m7Eld~5!)^Ag_U8o;oF8hsVbhOogj*On*@S z?WmnD_-{MfQ=o|8-vb`L1z|ThQRZkaE>2|B{~o45&-;BF=spFa{~lq%i%*OA9mwE8 zBCEvt-=ZO6e*SayKZu0#{>=pc?$2-#WAHug++n>l)M;`=F2djTge=0R00X_Tju0)rmIVCJG_mJzH zLI8a$wR*ccW#I&nm>E;=jC!RFXR8ssL?^+^>(sV^Ju~~h9Nu--qDY@b{wBegFv!vj znDd?xQcB>xVFH2Lwnem#09ts6@9b!n*FPyy{vR3InxVOf45-WLA%Gy{W?OsDw|Iuz zHHoxgc2ul;1)sf&+xrK+s@^osx3hhaxl6$J%`Q|cirPYiSF!GQ85;*_*UdQ2X$I*0 z7N_1Q#45Yd1ROhgKwh-&nej8GpEm=Vr}nwm1Q|iB1vu^~%3^SoleeKu8ciKOs0J})D;}ez2o(m!|6}}5D1!vKH|mU}6&n@5e`)Em)?>jo zjJD`-&1Iu^=E*}$n1O&bn)&SiFqy3N?NP1vK=C^Lx^K{C`rA^ij(t%j6K-T{V6D#W zpSj51SuT_geRg%mDYkE>yq(v8$Bi^iR|$xru+(CC8hT67t6XKOwM1qq_C{r-lEU;P zGfKY<{cZN>ifAjOosS}*o$NX{lS8*P^`V1 z0f@L{F_T}H8vo`B_Y0$|7y4@=yCYJD8+&-T_>=GN7mvkSbiD%7fmt?g_1Ef8q$+nM z`P8G;;*df=B%pfCpWc<2j-_f1tTWzsMAB)W?(Xih!lznobr^+e$ziJr(|n3Zm&r(z zo&Gz#aQ>Z^HnYVBI&AM3X7QQ_->Hr-{nOj#I(l^^F_G%g0uw@WC_;TeFH#9m3h;`^ z{W#Us^XfuD)!GirQX9Ffn}#M9q9!kJnIET=W-G1YSwYO zbAiY>Bt90rhpHvAR~U+YHy7s8PGf-TbfUbmC>y&GNmgkR0_Y3%EMbkLsd5nzaOXt( zJ(OO)fGv=>-?oxI!hHP5_Y!?8!}Gzo^@wMjfDSW~`6c^4zMnl54QQ)B+TQ*kreUFQ z-z0f2VeDn!9|~x9=qpD zN$ymb?+UFLRKlY@hC_oam$wtKJI+|lJzvqL{`gFmrA}Rteb=EUI`4*yyJK5^&+9pb zc+jzsp4xAS6B8So&o7nfyd~6xR6?Crg*S|(;#7bjDqu;1E_G?qQWGhZaDUscR5#TT zLJ@QQ8f3l27;P&5?g(jx=7k#H$Wlsy7(J~0kE**;Owg}QdvaIPjbwG^;b6bZ4lai6 z*wV*YRLxh^G*ABY9Nz~>#;Wf;^~e(Mtjj?1>%I*#cCCUsQr8LsWqt zN(|bytt5!sVvoP(cEsz$r`SMB~)VTo^~!XK6MdB{{^apqHa1)-jPKC?I*a2KaCgA#VjDnAlg zRD@wcy&B<<51f{cDJ$;qr@5McKZQy!(Bh888h-Og&vp(Nt;HEOjtD=gcASnrPc#}_ zpYnYx1-l~lCwn;}Ca)Db!?+;`Bs%(Y$k0CJ>$t;~AP#!Z4K3}v;1s{iP@=iB@3;74 zwi@i&-mHt>D6jLKv44LipKNP|qNCmhVwrULeO-;0$sHrcZFy{ARsF-j(Km#h58IhvN>Qw*TN0== zKmbH(JX#9)7=e;4ub2YI%NC)zfKW{zqO)L8$VD8d9GAQi0#2BpnbI=yxAN@InZRt5 zYa|QGk-*T#aXZ+?dkRu1{ET45o*e9Aan7|Wu{BYlaY^kiE?E#K4+}$uFb+XXS|}q? zR20;ZG|6$x`I7c1$|A70^S9inc7K zAPyzi7(kaIxzGCcNs93oi0p_!5x-5dH7)-BflHl&V6eSBap=!B<8)b5-B&VPaTt*V zJWS72~C7BtGO*9bi${8hB} zH_&nthjtN5qazktql7TB5WREDjouh&VQuV&zLbhFu{nqPYR7A>qkd~#F5^kN4_IFu zv1+GLzT@i(3|jL!zdk%gYltqYEO!8@`@YxR#b6eq>P056J-WUei4^xWzhAY#bB>r1IWsaM?(sb1J7odsrRq7cT1vx!F(Q(<)mgn?_VgNO zZp4Pn?jf~)ru|^z7X(5h)AcNjnZuC-UkV!RUOtz55z;mS$>4hKw_*u>zop2WgpZ{s zIHy*Aukwn(JOMRt;g*jL)v~jxWdVOPG4-0kpFRJ#=Y;LKMk=*GTYW1A|#&V`ovK<5@H(qrgzFnt+Ny0ux zwuoyFe@M*g=uvaap!Mj8cF%IOs&Y<*G)4%r$f{20dyZzl44EDUI|&Wc;&wMYnh|P< zc|nOA7H8bV5Ju;}d07@^Ryvin>WnE8QMrN7!_$Ef^@j(}LKn2qdPS1twt&cA?Y>OM zjkUStO91na;zyh=A{;zC>uJ*kYre2sR{GN9$vL^qv9a!C^!rJKD7b7S3^)&tjbW{+ zNO7}?3WOyGzwjzVF&=B=a-rt&xW*rNvK$XQ(Ai*1RkqrA&60=OW=%w+ZfwLB4jenU z{7T2rjQbw$cX%+5S6@HQB5xVppTIqvZZN8QQ#R18e|BUIELAxloed=|Tdp}OH2 zPbqSr_M}#`sS&|Yt$ZM6T(#Nq%fCPZ1#=ik*tS=cSG=+J%I;^(sg zD*LN0mNIqw5625teCMpMm&lib(5)D^_GQ(g$kC_M%J7xX3MHvv9z!ew8$JhhM9(O> zD*1yxHzUia$?!WZ=;6J{xF!j}j)thUkkn@0LgiWSSRlqLk+$J_&nX<}Pb1OFDve%Y zBAmQH!nGZIq(FzY`DGUXVQh!z^|O&&{=1m^mP_+lk$T zG1nLQ7Svn(el)u>$nK8g^U^0RNm!_o?5BOG&uIqc-lP>X$Oq!C^6Mwtu^61khrXVE z8(ha$ueb@BbqT?AG!$66DwrXxDrz`W*JmWkP1Oqv(plCp9SY_5%LG?T2&=rup9sWE zN~Zm#m`=K$#BWqVp!315lP4QuN0)V&;DMeEvo}8;f8js2f)@Ns#T;Qet0%hi0HUO(f*cJu_`q2BGGZ(7%nX115fLIYLVFXA?jc9l? zln7bncN52${yHr~x*7VP&6Eo)L5IPs_iN@}C&#offA?e=;Q-ml)Tovs~+_*eTWB)+3oQh`7DTH;8r8$G- zZ zM&!uNUidp4XOw|pyPBP8{Q^QM*x&W>6J3hG(cJKV_Fr_beS48-ifjM&gsI|QJdh%O z|6UeB`w!a3w~|8x_|=9>;A$R8yR!&y5SA z!%6CiY7aF)WG!8*f{25=ElN2lhsbDC`U3_c4e1!g;JKk z`m&Q=ahG)y&_QIAmt_<)0)#RP;hUC`halvCB36NW4de=wJz9WWMV zhhe2^%*u#q<;YgYPCG{*0{h^VizX`IQZSd?kSolD-EoZ;?aC42M1|aQgqP%5$s8z; zkB=)kIE%f?wShr!a|5P&Cc!36m9YS)DBIu_a^x{FdZ<`Wy^>rN+^rArVHus@-en$C9`T6gu3x}~pKX8n*B#!y_p9Gdt*>iN%QO^tVL}_Y zfhs}umimWeA`l}_HYKW+A0)vyiWG$#8labUMXjE^GeYm38lb>Ag`Gb}vg8z$OM`^> zaU#+77#L3%#gxkV)WXM+xknX1?iCRp7wVi{zOAa-Go1+z+QJDC%@n1#vbnWZKn>;X zv${>H0c+9+$q-PuFz{;;iZpm7Z(STJwxOTyBq$Xmf0SZDqDIQO;Bj~-4lO;8x4jWp z*yFm4TPqgCez*BScC@Mt}8dnx?_{%XJw`nN{~v=tEc!AQUw>$3B+j z1F0cHUCx`b6+(?1XvAaY0ZTIHY^BQGgoDPb!4$eJz&`A+_Md}CJidLmz_9#;M#j!; zUe)m)v{I74QWJt*@FT$+ZPcuB?+e~sF{anmcs;CLzd!>6V^5e3nGiGvmroemA@)bo zyt|<6YCrv9D>ekTU&*ToNi2MxuD0KM{31UVTdsqqqY(H>vF;08o>xi;>_1O`I%j_+ z%$tblQEvc|luz$X9kkwm;AOq<VzP4Rj8a4SPMpq}nRuX~er=?W<)>YP=O0lw{V)R&Y~xIOwZN-SZx_~hQ0-R3&h%Uh-@E}D z$cHIi-NC12>ydi?y*dK=d-T4c%CkPpcTawyyn1D1pV#WGkMiS~Bi_#K>>jmK#YDRxTXo zI|=SmI)ByD^|NNHwvnNR9)gJka%WUi=Z|?J>Scch5C7JpgX%d*{MJTZtwIOmnE;h>mn5`MtzqUziZY2Nm^ZM7G)*FZA|48;A5K5=FBu? zPPFISetPlVxIU)-i_Jtw@F{9V#3S5i)Vtva&rcq%wf^bod5Yd?i|+&dJr;}ocnPv! zaHyUj7d2LK;jNWz!3wVm+~?p0qMo0yy-j$}lIT%BR-Ja&P|)|(iV_}Cu&~>+yT#7A znJ7s$p!Plru{I_ZE))LL&pu}C_n9=tnAP?PoMy4)2&1E=f#>UoD`^ztEz<{p$o#Kz zp!YNLaE>5#gp(9{a$H#I+-g{;1m(8zd)g-;D)pU8V_H6_4M;;Jf}DLceKPua$ZUDP zpyJ?Q${cSQ@~W7HOZl8U`vV+{3qJCSYedLNLr}v(dme?6)}BX$EL>j6yOY_5DKUr7>cSB zqNgLP4?8D)GG2pZY%q*$#NjxGLwZ?2o^aD{&8Qs$DD4msy5j6I;6Aj-4 z<_z!GTV)e&OqSd9%C>p6Cn6Srf&72{*Iht@f&%~7O9rtO&qYBYboli7oDbg|6B-&e zqlrw>(bBsu_Anx|EyAr=fBn&1Gn}x_|L>ZAI`Us3dc8&U7p2DqWjNn9bL*GW@~`;y zGDQ}o|JB)lYvzAnf({Mbm`kZgK}&Q>`F~>k`(44n`>)h?cW-BisA&9;F#`SxLHj?2 zXoLsqOPXmXM*>6s@4Ni>7J&VO1Agg`V@OFydP4u7arpN>RS63)95mKQiunJhF8>nH z&nF~IyZzps73BO6gL;ODDDzTfg{(;UpT?XJQG)^!vc$Wlxrs)&gn#FMXyuaRI`O13 z09O8CVwt*)VZT|J-6zONDS^bcQ%4C(-5v6A_b7p6{dcM$r{UbXr_poWdDH>F`7usPb?Gfk;%xE^o9or*pUU)Pb5H& z-8ISp=P|^PS?2VaM4HH*2WZf^A6IPI>S7c(qbLp|M*!8420D8-#L{cUNwY^*K^Ccs z7%>vmJ$LpuyPX+Ib0g6n9%jpzxkbkSFOAC9wFWfw~24Ai?1=hNG$BC4E{~{QiAnzgedPZj= zF^u0o*cSNPc3k=|-~F3G7-ZoX|5{kp0ju`ZRSiS2UaS zt)P2AV=R_T!R9~pt&opY+*NtXurdOw(Tw&~T6Z%j8c~A4l&Q4T%}&@mdH=dD3dJ{m z!GZ0mX85Lh6VO}~R5$;a?)@-+-GMdk=#>oFLtEK&yS0KRe;%Tomh38BsBa+7A!P)i zmA(t!qQZ|s$yqsaSD^`RibN0Z43uI@&>3T?Ve|L;@l7C#GXvK* zs&tZWmag(k$atLtt4;Q7&v~C-n%$w%j8MAG`2Jo3a|}6Qf_l*jWr<`de6veOk#M6k zCda>AVM5u=YethdeYUHaYs_)GrlfyURHDS&|P zBj)H)T^pgmE6e-J_2p)z79rnyK%8e023vEOG9MBdQU)C;f$f>s+@i@~YO_!iMba0- zGA$vs7_Cd#Vel=mtZ*bIW6EB9B(tv}LHke6&~FCs?#Nbm#v2R~hGQwi-3IjeuAtvj z5UqNRwW+m<0dyhHjMg;(MFgh=f=mSGKeAxnUBalP1a!OWe-y(#-I8pBGT|VvHx-YB z7;wcnMpwCH`~=r4$>wz=kr@MpD6 zV$hi&hc~vgPag=Uy~3T*_tnk0RmhH8T3I?J1fiehkmfMA>t^za@e@(nMeE(S?V?o} zw=@+HU3Mfdrz<$Z%Y+@D7LU4 z3N+B`^uNGWix=sBGJKcVh;LQX;BlV7;8!vTPt~Gvh%6@d9Xya;gPIiM27n8<>|VH_ zWyK6Bp}D=#4Qr2>gTM~?)Q~txTTl))4pLWS>~-5)=C<)4N3h2^XX=KzJ0*)&JBhBl zC{pj>M?n0X5)s}20?26jP>yM;MgLfRp)uhWY?@DxvGug+_j_mgJSp{)86e~xp5!~2 z_M`YbxRKu*@!8nJ&@bcOyeU@-g}`F`+FMLjX_4RinGS??lQi=Aoa(3V%;JaF{k##? zitx!FeE1z>Cl|@&G!~(sXG_kL9zW=dBbZ5^mpXozlb8Wjt$07C>GQ9PHGQh@mo$a? z3~t1ibcpc>hLF-HE{>crYc(Y4A#t=Z@lP*Wq6_NiHzj2wZ;CSWc1^9od9AEqiK->H zpjz5f^XkEMh2^1K>DcGz##`nhOKOeQBEY#)(Z2W`j%YlCY+cPCM6?#>h+o7vmG&Fs z*%QXE1llbH#5F(vlD_I~+&GvYO{)gU0Q-;K4@kx#DVBr@06(p7m|8l`4Vfmp?K$B- z6zWKKP^K_z-=Cx=x#9Od-by)RR{T*g6I2&R8DHvzl~-9Y4)Z*%_mGE+alv*dYQu5? zhZ09nGmM@pmvSW=7%^9W(oiO2OHC>a_ZaS-5y0c}gq%%ln8$$O zyt&PA#63L@8Z#!&{({G?_&&x~14PQvaogZ0y^x42eBEMm4Q%vH=;!$ikKbNw<4la% zBw%Ja4|jInB67izk0(0vxFQ~q)%Id3IVK6&vatSDXk*{gAN_TB2%hBXlIQqF#qvs) zV>M_oayT9+QW(=8+dKC+9OQtkOigh9dz<{dF?O}(+jR*HI&v21=csw-vqQ4l75&3@ z-`nLCM;bnzeslge7b(jADI~!_D}lCvVaz`O`W5>2nQC|0Qm+^@AA8N zYkoTttiC%YrmxW4UcHJ60Tzaw+YY1GxL#5etI*MYr=I)uOK}IykH|!#HoN^yqIEpb zl-i95pSLH2iL;4cBR;LJKS}%v+QRQrv8I~N8<3-se0lFvDHT#7P_3^l`ig<6ss)N} zHF$4XOi~==Pl>dihWm)Dkb;h2!~|koDV1)wG>(IG$Gg61+53)+I?F-2Yw3UCGM;~M znU+LNXmKQl{|6KRG0g??SOyXn&VYgsRIAgNQWsU1j;vNZy`=D1=DX1Ga&`_z=~!}c zozo2UVQBL^;*Aa+$N5m_yCN50TBoZyN-voJZ@hwY7jicJGfq1`L323fqa1OYic~t;7(d zCnEPmTz}V>qtDaxHL{72Vtmk#0U-Wy-I~2%w|*U3urB|S{+>1XJbnSeEht`%$T!^- zZMrC1nBSrzSg9k(aY25mXlK<|aT~b2wkz5>1pBp9xUUzgeS^-#EmYHue|VZ&_k@`V zT9I#wUWcd>Um>eqoz9$XJTL2jR^56~b1*EGmfwTq}*NJ5!QiJRiwIrD5ol0Hv2O+LF!iZO)(1oW9d()+F#%JU5dml1EwNF z?M4VzW4PYSYVv3|7c@f)vD%g#9Eh^!s$h7}u9%vLNOx?-hL&)AY(&>I)h$V#eF`}*`W+y~0VwrrEy_(lH;|e)`MgSb z6al1L^{NqwtMt0Ch1w6mOku3k$ex*PtHk!BwQ zzU6R@!3)#{GiuxyAwF^;FV>TBS~3wr$b;Bw!H>m{F-x89jc5Q9HNc4p7(j2t(DMUq zATZA90+oSA4iO!@EQ0Ma;H6r~+h;ZMigo)Ct!mio>Po_BIjJ*Aj}!aX)0_>d=evs8(nrQkS@AQ1uka|Q_XLnd#= zefS(o7e)CdRB2gSN_&Q9ju8_M{QNo1ek|V{&B~VvbBpTYlR;FJ#INlF`BZ=uS4?5I zR+GW2^TDHv@4DX$(zv=oe(Fd5X0R;)fKRFI>qAB2lG~L+c{C3k{J1A#aNy7C?gS;{ zN*5k+O|PV+mk$lZ%e|!%TP0@&j1j3gb>BiBff!wyb9G_4&p|7chI@ltEK0qbwj(6G2eKH(BW$fCfji;I^_QU+NepTD z=rxx|OkD=W2#2Paa3K1N~i z^v59YZxQ|5aW>)v*q1nvp|QAdyP`j6-)rmZLvHl*tuLTOj02TbB0uWMtV?dJ}Ji*#AkgDVY2wVuydwurRqS>*ch+{W|qtx-W>4RK33q3jYkGh z_;Fz>KCOy7>Sh|ysLRZKHkKF!9*kgLm=XrBwt5J-moN;Rui=S#+r|NVnbd=E%sdpI z5Zvs zV#twG%8}8kO5u3dmB0zZYQjV?V@OwkQfe~8XcEium9ZNtjEn3C^NyJR?9&mwqZ~pX zP@tD%RgaU07_YWTyyRCB$saJ+tTKA82%Ceqe#|{)p&hfwEW?Nx9EX$^8DeR%d8-sAldR z97c4>OBKRbv09jGW}LsIHNkRTp7<%4`%5A+**nkaBZ6BkTvf`8bYNMQ1#Ij6IULwNk#kwrOmFifM(BRNCKOA=M$hS{8SV^VyC44P4^=vd$B z)to**YvB&zSJMZYS@}SD%0Qjtu<}Ep^x)M#!goPqqTJm`#kxn@q#8n1iVZ^B&CWn^ zQhQT46p~?WqGniruPd7>(ye|MPj9yual>Q?LS90P+uLaO)Jje){Y}yxk)_y9Tq9ph z1g&eXO?JCI!KK=G9nV$-y__o&!3XH`hICG!<8P{G19fnKA%p~*Xqo*WoUHRNLdP?H z$6N6w%VwdlUTkT;drN`QlNpxRMGm@4x=Y~AQ6W_-e#0=laM9DJzJ*};^14C#qBk5} zcR)*Q>lGE5Pwzhj9)etFBn|6-W&G~8p_PQ(8r)R=$LWsz`-@xsnk8IAr(W2s^U~;|_ zV{;Z!?XL+pOq0QVtT0ZvW(`R*{`Bn?+tZlBCkCMDy1ke*xdiVsxS{)`dzFD>p(BU) zt}Y8A&p>=zx{DI0CDfs~C|UH6hB*6RFN4J>=G4J24ou0VWo z93V8q#kp)bTrO8sFDXOFk!);>)+k8+EcH|;Ct}S78ur3^0{iAOcy$`HJ`WvvVh8d} zht>!wS{M@Hxd<=sP@_7X!jv*?$)oxk5)S!Vv3cEFCcvl2Hid0EBi-k zo8?==(BQQ$N2os#vTW?*x4$Uj?+XSK>!kgXLb!16J6gtF={k$2(Cs%|kfj?@GYnf_ zFSPp5t-G>Fd{O(ItSBJEZSl97T_JNM*En>MIXH<O^0ciy@zW+it?rP1 zz>9jX>@bd3Bcutp)Xnia>-|P|-xp@#=W7bTux9=rJ%Y%k^E=5-uyCt_ASRn8hL#=H zsk}HyRCWJeU=e{6C@kJ!a^-LXK_hFiQDBi{$_kp$Kf~zJ&ME6zT18RP66Dc*hiP$y zCGhem70pe$yOGI;Q!U;jWR$>Wh(PI0^y$aL0#G2A zWR%_)O*22UXr*T^HFM$nP$G)MB%JBmBk{4EP~hUJezbveo!PMUNkrwul$h}26&!gT zD&tyVmIoOnn6dT#StQEtQA_g~zCI(yPfAMal|8*{!{vq;*(LwPw2~qnZ9`JhiU%a0 z%p1Qq(!EPU7-7xC=iHAqiGbs4LrIE?a4wW zZN0*^NX6{cxDI_%ejf5frF=1lt)f&(HClquRuotR+S2354S~V%H#5zy753$$%pAEC zKJADNjZKC`C?XK}y_YlY@McMx$WFx@Uk;oXxB00!2d1G`7j*PgCoi=1L4Y>kIM^@E z%UcA9uuWE;1Wd zQXeZrAjbhfTibA6XlH*|+JYhYipc{jl|WqE(wlnmR~epnN{uvfCqIQ?OWaTG9>Tru zfWjSu9CKiEz>IupX`XdX(~dLVVykAaR+(FH(+j(3rS z&?S1nHGQA5oRcq?TYPnm*b3&Ytg_VHs7qF%Vp;I7t6J^LrIsGwz6Y0R?n4N*L4%+B zr8#RZmw1d{cva8V{=p+xm>kurC?)O1njOpDE7$3>BuK_XC5m+2lpicgK_+Zz5l9C& zXMFLIoN6;;jrFiOReNK%fk1)6b(M|T=wXA*4`u3u#aJ!l=ndz8)?~GI2lsBtg`Y?s zHm~Py&t=aX&#*Us{Qa0RiJdRE21s|(>x3l`poq`=wILw08_?}pU13}!XS;A$)vZ{M z@uOt>Ktx>kiC-BFC_~3%IMj3^904ye5+eNhM8vWxd`~@wzJ8qHmpL?P?208mMe`6X zem)*>Q~m2Xp2NM`nvWMT`$mO6ho;lI8aiSuVk=1uU7UQiF}UK_(dQ%95GZXBi`aF8 zNB5DLdk#$_IxDX(G~!88K4n&M0+5J2vli|R`?{}1Qz?_L`}n4tI_f{ZUUQ%FUt9Jc zFaS3-f|PbofVXSQ9i?}%Fj7-(kce9z0BBV-U5>Izc=n+(ll=*)QSqcX6_!tRc2(LF zXR=F;G0JVbhvL-A^gULUeSLoTqOdDOHV!#>_iRxW_i4-;=YUO}TA~m$Wt(~mqS2*z z@0m=E!?LIBw^H$kO%`s=UN8l~OAH_RJP6qN{m!HquTtk>Fh#!qB%&|+a$d?SxjCMu zE|HDAi<`~G#y4oO!eU}lG7eRdJiM3mb%&HP+&oeA*XGlJHA>p1n^$4txiga8iG^VL zcuyO=HfJ%^5$W8sETrX2GL%gX^a0_Rj#cR6QgUhN9LxYlPUJaF$+SPAB z!P>k1Byk~x*dAF=8P1@Kpg@e9VFsqVyO9_qT2&<_5?vUYO!Wu0jY`%>F^^?bo z=@y26JCs^Dm;BpOkzO@inM=L{^*5LNl}9_#(~i>-7yoZq)`kysP6F?rkj7}xT(c@x z)!=z~Q~_z;w+~`dt1L#ZL0$A(ZU1f@9}d4=C;T>j51 z$*QkD@v^?2?CQ5H2e%7H+(qVgWk!}g)!0hta?Q_FvKX;S0$AM6Hb1a@^SIW#HiG3| zjbb##{oF$1w@Q6A==R0JjAnLaI=1>^0o`-@^`K`H6C8I7Rq+?_5@3j`x%-z5~9{^%h1*-RE-6p#|ODlF<_=VY!+yy~BF%@TCQBbw^iU z@Lt^tFmF4x_7}NH{<$y5&UUxI4#u)^Sn&~*c~WD2>XqXMvjy~H2=kO<=}fw(&l_`E zypj0_GP2%Qu$HuA#u(9^iauv*(~1&s4evCBV+yThDlgGVsWsR5L~_eiVblbzP(r85 zcxGpLpQ@GLtr*Ye>!Fs;JBn+Tkok6B3lJGraj%;}4p?QE*U~k3l`7{kVsKse(hNZR%ZY-eiq;X)eGacY|xroRTJl>4eNq37O>gdWHn_x#U_F#>?qYu56Ox9WX{J*2j~qjZxEl7%6{V)TcEP zJ+E0*Skl>%=Fb2#y(OxIH*v*P;du+m)5Gyph+i~dedXhSD!FeUL8-qvQ*UO<2nz(!0HKdQ?g=c{5ca%pyrgF5{s)qlV5mv|o}8 z-Kc=y7CU5cFltX>lx6Nme_TGmCFFDSs!?qlZqFKiCoJg1^yq=O-x@QjbqkKrIu4t#bb85h|Cl^giu_L2&0z*^q6@2V(tqV#Ins#%OUg!#zGVgDUQG4j6#1=N zz^D~lZ{&q)24ui&R*|6*s^s5TQx4EZdOeLFYB!XcP=bYSjY1Q%}y^xD&2|A8H+ySFNL0Y(s3X< zLHM+B2 zrYS9O0A+5oED1FEeZDUH6ynqft|~6~20l?Ofq3MV+zZ{LK;l#QKTb z{y}a0h;d|Dp9Ltq^_Yf?c$*)hXuq)l+_$3~Phh0Fbpr5t}q?02Wbg=t!iZ zPT>^1S308`5E=g|4sRW&mOlEtoNZPKrAUgPDrM7NRfA4 z5t9rjrHv36HCa>UgpCl>-12*zu`TdZHv6W9xhhs+I=-uQg>_Yb4w{gM6B+b71>_VaTO#HBiMJ zd8TyZNAhss4438%t4vOF=SO!d8B9vbVPuwL9V^Z>cF!T<5KhEf4Cx|E21CIbcGmv$ zL)8P~2|(c2Vq?nvZ^=iZ%@7(7zfNB1tO(b|;T0XbPC*~H)q#RN6Jc|%4<+xQ-*}a~ z1(C9V@${ztn+Tdck~8k9u`$z#kCLY(E#!Z@EG3j9t$fVkOkK`Jdn}#(BGpSf;aK47N`se>7$KeUt5nJ_9Bv~ zpQ4I(9drrpzZH4Net8+^pS5NVUt^G$I8l1BC z>8E`Lw5rek&<52Rb<8mVi`5vl2)f(0&;?B9InN=5jNSk(hMAhlJ_MwvLZ@EZLOj5O9Xb8OF0 zdvFO?>B8yaT2RN?uAvuC^j8_pmI>Ag+1daO9e~4x{ex9i#H9@9RL`d=sR^A|{2M)# ztG!iERG1qV6@Wwru;GL@GvZyhCMQO#IUQc5i8_9MF)OtJi3gXupkQWLjhH_>99DTS z&qMHWyCUJbW|IjHpOe3MrC1E@$O?h7E1o4x)}Db{UrOya-DH5D1aOQ#EB{^@gapJFrUd!~KjizUA_lP$8e(N|=*bb!cq`H5&(tV_R& z+Zr=})NzeTt#Ci=B~dBIbf99FvmLtT)7s+=rmqvV=HsJb8+W57#co;wD*E0Q^b5D| z>n_&1^Oc_CvBk8;w~~bQKCU;IBfPNLjA4v3uRJjfEqe#IC;DC*uqXA$B1UoCfc zT5_9>p2aj3v`q)Zlwc=y8j1>p2UIm#DC&HJ#|7MrZ8-hBjnZ%M$ni&Tg7h{-(g4Hm zE^dN@4<3>`ie>Ugq-gCJjGu9T(k@=8@K0E^wp6{z1o~&oL$(slktDP}%PA4U9Muo7 z#Kte}f;ttDQ%-)H~)@aE;UWcx)6*DVDGt zUG87XmPDfJ zvJhwK$QHzk7J{PIi&iwg>8`<<$?po|lsE)D1IGKkzfBP=j|V$9tVqf|$zD?W(crJ~bn?*IDnYCsar<3E2^>Px$&|A~ zTn8-%6mP4E>#{1SL#g^;lGqVX1kX4jKH;PM6KtSB^$88esbV1!h#!Ecy&K!Qr2 z9CrD_(k_bQ*7v?BMF#S~jsu4;0t@&P*qBpm@AnL7`gubD-HFhC^J1a@H4}aPl`}5; zuqOXtWJ~G^lzPb{*4IFW={+hns6!NWrqlrnvh?9D&|Pcefq*mm_8J=-24J5H5l6~_ zg)OX$)6ENCFBJDS(qF+jLpynDH$0}_B3IpA(8u^U=2+6nOc5K$SdiE{JEG}^|NH;a z?rBP*)chz;dGbg{;j>V;1YC_tOQ>aPnLMOyEa#VdX?fo;lp?vyT1f_3CfuRTFDA^@ zwXD@;y*Aqe`-27jP|n<(=C}nXsAzPIgMo}HEwhw~xK)9eSlZSXdkxt$;IJOY&4MQ0 zYx#RXIOtG8OJ>ZV6&~JFVp!i1*P*BwOxFx8ta9?FGY*ixn$fJSK826WY4H<`Uu`U? zHtSg>!|?hT#jfcg(&%3v=G?pfNq-wPCUs@q9QYsHyF02; zO|lhRbZ3FjyxvkB$eL|3yhOxc$cLT@(IkL=|W z?VIu-Gk;(9zwYb9>ilm=n0(pKH=!oiSo@|m-MD+gf=(n)9`gPaMKm+=`qQi4>q$+t zzw2C9!}k;Ypc}KjSh`L}FE6Dilnw9l2Q{048KY5uW7r{7=$(wJ&x=@RM+{oZbmj!; z+_oFX&C1d7IgkGg-y87Oy*ai-agHmcH%KkA7(z`>-`ISq!<-ZNw+pxxP8827Bwl2z zvsqk(kR{Cog<%2ST-cvM*8>`}Q5fB|AcRNFx|Cmj|C0N?earnS6P^)VYsC4$9rm>3 z!Gjl!?*D;?M@D-!KFd9JfQVahhojqrEca5Sh>o_fe}TgX(5tGFgv8i#Nz!lX>dvmJDZhaUtQXNOn}{A9Z6@{@~fna>{=>Z;eaZ!s5Bu(s<;Wta0<6# zp{yQKbC0bnXi6qeuymgO)oQ-UZ;mpe^e9*$s>+pFeb!rwlm6R2KzRynf8O8GN?Xa$ zmIwzZgN$Gc>re(l9*(Qx&*+vv37=ht0Ohxd%mV2y6D?$&T3sBpi`JRKo4K}`789}v z3#0^*2~1`BusD-RSpFx2d&>#WuT%75`TFC%C!E#M3KJYnWA&;YMjxI@b2-_?8E@_> z8BVWRs$=hj5}nfy^)?SQT@Mp9wQ9p9R*qgwhhf{wU4U{S%d$@Y|!=h9qt2#G-whs^~Mhd{NbpIdHR01POk}O}o8mHv@`= zLibQC(I6HhSqNLPHf)#KkYk2|sBKPHILs)lB^bVG$cxG*0EB@&dJ zHw92-!ZP*lxuSyQ5xu@Er%^%?k+E*We9HpQVTYvX7)yip4UfG9vM7vv*{cQ%gh*Vi zHDe2i@xU9e$7OtYvWFSkrxAtaWDZ|{I)4TSopF6Y)0DjKA7E6NHA>=*yb>k9z>V91 zZn^jL>u*Azlb3rC%><73Bw>)hIDL4BX{>o$QyIzdI>q)jFWsdY?Wrm0(fgoCr(wUU zo6RORXGu|QhkG(hon8gTsnQ1QEEB#wZBM?ejTW}|hz~=FVG7F~OjAJ1(?9SQok5?w zr;@jb2D`6yf>lQ9l08G;lol5+zb>9(o`(}t#k1sQy@$=tT;8HHvhEqIce_F@*&X%W zH~w6cpD`u04g^wz`um#T2m%zkisRFdagEQSiKSW6sklPAwuEWhqlf!&y2r%Fm# zMrJA_Z^&Vkj%SOZ91d2b@v4^Fa`5BHZ6Wi8^+UWs7MWT%pCnYgL*gtht>?29AdAMu z+wJa|1Bp2aZ;LxAY*Unl!QQKNC1ORHToCDVD@iF^3K&$IrxaI4ywW|V3D^()xnWJo zJct-Bf(RMpzOFDTKSN3(U5x5BAMwK~c)H&5v?!Q*eUeDuHLg$CaQN)+6qTz==_VfO zV}{m1<|*~w!}l&TKx+6&^PFLjSgHrAJ_lJe>{Xr=J2M`7GV0YcrJ%jP%Oa6s{w3AK zPyvIsux&sGYVg1cnzD*C1S(f%J~F(n~X8e=--mR)WbU21k++TfvO=AS~{Z zRx+wjTMpKvUk`%UA;1Uwu;J=dog)#_RGsTAYnfufq!bs)ByGrv#PkIy2*hPTX{7mu{+d%8glT-5PDU3yS zCcua_^_NSS@lKsoMAA>GXCD}hOS)z?T?!njC4}vaijI)7+A=tk1=r$At5q}TmrO?S zeXwIgY?V#g;AA1H3Y6rNQKY_BwLhPjd;pjzSdR3P^l|~$K?c>>CkpSJdO=WMJt_^e z?}#-9quC#`rnRV}P6np9A__?aIHPM-1$BnpvaM0Y7GO2N3cGe+piQo5WB;*<7NCybl%Tfx4PlRI64EF8tuk(C;MB+l@_dlae%vF$S!+wdNDqPFoh!0f9C|6kmqRyFG6*%h<=%m3gzSOG-1}V|$vyxYLtnOKl$V`8TXlVF zui|Yl`F3-Pn?hq`_iCUfo76!sCpOfrCV{GxP2Pe32#LZM!cRE)d0<5LSnBtGX%U+@ zioJjBK1~V~e6O;CqXl3;*-D7Thld6=Z%cjAV26fyE{GDI&3_J_?ZmD=DA8;mh}q*< zP8lsKCXDo9IX-GO$0;-HYtfjE@RrZam5f!W*7g$2CxtCa#nU}nlhQ(QL64)7a#8Qh z(l8L?m(E?vC}~)W+u79~))E-#F$3HO#YYItZ88ncVnf4o&BXJTt@lLIKB^X=34sCX zXnYAZs_*A3P|&bIt6@zwT7jyyO=ZM-VCbgt*={&WoU`#9?ghTe)^1j>un)PPx(|uV z=vl->PJVt5X^bvh*n~Lhta#3eEaMe;@7)bWQG7FqKa^7)mem~YZqb}eqQ=H?)~G3_ ziK>YAY(Eu(U~StBsLS_HW`(s`^7pDPrlZ;S&h^gXxG z=hG6N0x_i~)DNgk5mraXjl>{Nh+LJJEJjjO!IhB`7C6r_q%FwlCb1ju*hjU#v1|P6 z(v9ocq}kh_&~Wa_T4y`a(qV|=lq0tPi?MeOl4J|Jy{FBYwrzW+ZQJgiuC{I4wry+L zwlQt1+O~E3yb?tm&z>t<9A2ShOyr7Z*Z+!+FjG&y zTR>}<{8oE;cnYfUa^>dsFZ$V%^U)VD=a-TGl-BHKcvc-tI(`}kK_J?kQ@AC~k@KGN zTIyZt%iQ(g4Z<)pQ?&-NCdRsKd6H8W+IK78c1e<&_KVG)r+Ecl$=6AA@`fm<7Sl8I zva8D%zh0c+v{kTWy`-?_ATgdrGa?P2r~ievKb>}3tXm*ZVkhP3ih`@M%wFD2S_J-? z*z;;eoy4jFOZ+Gx=Y$YJoZTf4C!k*r(Il$44e1%bOLYzTPUc?w!ut-gjo}@~>(3jK z4&4it&O#kCa`czK(E;hJ=8p+N!uz4d>g@I9ZS+PxYy*8d_t^|7--=%XWDc}L79DNm zob`wI6+Ov>JLC*HA&a0uPauLi@+SMJ7W*mNnNUVR2qg1To$B|?DsiY8JwSny?l;9< zbFW?zj<2dt28)fH9>k{3O`%d#LU4;cPcl(>jWu=Q-=}wk13BYkfav!5Fr|< zX*yMCqKkqtJ-9dNpjnF|ir+0nOD-QL`~_JhwfISOuI92%Kh&G{EsM4M$Db%_Ak}%4 zERaeF+Er7&_$INsUp4zQ@I5#hv+y1#9u$XtTCi_Ze|9g6h%UrT6=0H;!K95X#$e?0 z7vg^);Yv1qsrf^m3{TE)?eky;W3lI6A<9ec!XYAyGl7>#G5ZI6DJ_$7(Wk^fZkxiuXoldmrYp(p!T!Y zm*MdB>E`5jCf{`{@=#TcO@RmURz3R}msYt}=HA{NF9qBqgh5tUpEfT-M0% zHk3@7Oi77Fzp%IA74OXzNV9&y8MC%-0<$>hVt}zj4oQFlR^k1}N`W5xY`JMOuvzVs zUv%yQ(V-iLW&}}l>z#}J^Kxs}WgrUE^A0m+ky&c(%+G6Cs0 z7$b;Le?g+bjM%eiG_sM;{d(hkoITGsF#mo{z81C{Gb`>H(M3&kdG zp<8u;W9nw^?MAuTfV5PW)i&+oPo`(Ra;l}PoGx^Y*nJD?7uEfQ*D>Q&`|-Zj`x2yC zgy0i&#YY9kcRO>uxd$@Na=-4D3glR+3qnEW`_j9He)~f@sU6Uo3K*Q8Bj(SsxW5yu zZkj#RwfI|hwP-jM;&orF$pRP)4NkQzC5jM^j(#Rf<`&MSV%jH9}L zBY;P;LV1#UP}i9CZORs#R<}UTTH7v1z4=M>**ue0Qt^qOZvXfNsbSK+zb)N<66ZWh zrzjplj-QPMvZ!VwP(Pc3{6l3n4`omV@slO5_sC?*BM_m0W%>Z&lU_D>{%W ztG1%Ar{tJTLMP5CSDIWuecLci|1 z4@5bT3ta)U#!I}49eKWx(9+=|h&^<=)@NMp!N96@#oc>Nk7#mF>47hsgya9^?+iRI zmM!C(-jR&Rhx;O145%PY#)@Cu*zCx4ZHb{FmH5^~gs^UijFU6npk9{gj$ZiK=&v12 zjw?ns+Rd>ngGfQ4lH{#Vx z|7lJM(e!S38|;Xy+C#2&ANY<|2ZAQ7_{`0>%J$Nve>)-nfsWGHU*17E?D8^^4wqXT z?T-gm{5ba~$};YnU*`jB;m6lyNXT{ng(<&Gft&v@H@CHheEt8%WsGY`mC0>B0;=Is* z6Ve0RFUD#jLq2{7$aaWoj)k3A6>In_Xl|#V_J2;<)5pI}0auXTYJp|9R8V+BDMBg2 zj?drlVssYvWr?fCV$mXPx z;n*|#N~*BsG&2il57WZ>XcA2%&^<3`2`3rev1|~?xkCI>5=MyfC)>6apltDl5~0uS zYygyZ@C>T6vo+MQ4x`^$3TFX8400jkTvv4H2|DmS%1k=^RP$54F=lZoB8GJxzCz4g zwshS;YYLfUo9pFw5%hE!6bSubG@9mE~u}@?D-&l66CNWLaGh9IxM(QXX zMRCf{jX0L5%mxPg;rI!|n_l{TC;c@4ICq8iUOFpsDN4=F8&Ia2hXJlA`;Ylt>gMU;t%n41tGJ1fuapaxDSCIvs;CH^L2&uaK7l6l3t zY`*iEHFHG8yI{nkN4Y>2#oQ8RXP5CH-*=k< z8qI*w8%V8{Hj)|ilg2(Y44L>qQ`L=CGzhprorsu z{FZNJRbXWHDUvWT()!ts%|)SotN~AptXcKe*{B;hf3h|5Bvp!Q=9`f)sJC_tF$f7V zs5f7=`0meD_C#ZczpXw`Qkl7+7a!fz#H`$)$FrBNcAw2vP%}KTy+yGEIUg^GO;w+v z!|qI8mYJacfYCxELYU000Q6a1%|C2K3yI0vH~Oyv*wYG7`l$R`n^(W)>S3~eR*TK% zhlfxRBHs45l}Jd$vW5!FYk9}MHbZ&9T(Y|d3=6D^MU9w<5AUf@WwZYjur!zI5tKNwMHbrSjq9V*Qw39fT~>Wg*Z^7Uz9XW)6`=M+`x zZA{OGZxV&i5fo;N%l!U>b#e5Tk>XZlG8prZ6Vz2A-Aj@Wz76x{3zKfL&`7dl!1XT5 zT_lU9E}rcVEtyQWln# zT#Mu_-TkXcFA2X96B8~+GAlbvE%?HUhRyB{ZFb{oT(A+jjA%{QYB0LZPZr8^juRJT zlnLdZm8Im`pwdf>{UT-b3x$2XwN0xhJB!J5=z>z6U?1|}*~uq{T5N4#X3}}6il$dD z3S3LgX0?T~gR2FJWx8ayi$Wz65@wF^y(g0e%LqrO?NJ^v%tG1Iw*~417n_}ars(*O z2dxZOX~Q{5dIT+4J!vc^vmc!AThu}1Ll2dQ;9Vze;T38%z#BD_tJee!a?i)=9Ih}F zlP*DwqT{wVG+ECFh-yRD5>LF6X#jb8N`3O_vAl;rf%W4~v?GEmYX5l`ucC&`{cTAB z$GGAs(p$5SA;X?eugzz&)k&OW^>ND630vgK6ZoNE>sL8!VDa&)`-9-qm*!8UdGWX$YpFTS1w}VVdSHntzK4Z$sLC!>hgAX6x?2C*=Z`) z!TN1Z9vx;p0=+6#_kj@i`xd{^i*IIh@)Q8bp{a$A2}i z8e6oDNy}7CUJ#(SJ5`jv4iit$SD&A^8(n6V)vvSsT9lc!LgF+^`)LsLGa_DEAFJE| zM20rkAEH`U4_ym}sjb^#^D#+h6aYH$$f4VL$U`vAap zeVhy0W_^IyJfbufR4MB?|J5N4Jp-j{(OQBqnKDxxC(!1)HFgzJD65THSw*P0$Oxc% z23TXMBrB-EW*koo_M%Jk`X0=QvtU^$>Q{YIRRNc%wq$G;$_}h4Z{t*|Uc-TJQzqKy zCw?Ed$Y7RfByan3qW9e>{f?cIVLCB|%}De>0YHFrlxP;}InVmb$o#ugE{6)%&3It> zs532%sP4y}8hF`cHrA=N) z^P;0mg9IC2&9*!sA0A>hAeTuP&BO1!c%0M-9;bL=vFU!dHa@aXRyMx537Qxg5kK%x zBkb&cz2L$+x=FqIYD*u{>ypA^Ffm++PAkSZ{eX829WpurG26nf^pdcS+shx|b;)eg zJ*_@wPE}2@#$co%kElJ+~>B3acT0 z+s=);$25t%Tcm1xIV)j@iBoHCl1TkT%mEJ4!T_6nFNil^^_ zQau-MP?=3-p*@DZcQyKIs!k1>0Q5NE6B60Lq-l6NG0=6BGEVgxnfch1mXnh!_R)RP zp_}rGV-ULUaL}cko9kg(?hvtVN!hh`D>fPV>07Tngj4CW z8V~g|A^612>ELL@cilcsX(?;sj#b5I499)>McP;~2gSj9Y{j2dEzwyiwbJ9G3CaQ5 zsQVXqa9bmXqlq`kxZwue>S0yU1L7e1Zd58Cp4E(7OkPJHjus^6>njV-MpoL?)CJaX zM(c3Z&IGSDoY~Q6iPaUoO0(JNFev^VdEuV^pf$lzsWq)C2{zTBm1iNml^-T3K9`y? z*AZ+i5(K~u+wHF~$>L3JqdM_q68ugvR^ADbTC(E3ZOwY0N{sy-yvPnxNv^{ASZi^7 zysZo}+V#>`&ec=_NomDj)u7W>>_+D`{3k)Xz*FhKDDZ7xK9)fCTPOe`Q4O?^J1O5g z>Ng~b(!8_1Kj>n^prx_nbRIkR9<^4}vVlOI`AG*sEvv_GpgJXl!J=|Bi}Ib8T#etD ztE`WW-wLS&Hf52#-INgW3mNK_<<3EUM6e#tRs(A(z_i2@ZW53V1nm9ERXW0o*c<3a zP+%U+85!xx?h;kiE_UJ(7BYrFH`gWO;&QL0hqn_#HWw5J8FM9mmbU(VS|)G6(krEJ zHlsLgZ9^f+Zn0q`-c4df)-^o0mrieP)cG0vW--7L` zX0wsArOs>DB(Kk(e$fJ~6TM*zJjF&6Y&le=R-;pJL%VM|imP$3mf|N~JKfQ~5*${P zPV2N?xhGZnrl)sio8P9K64>pb54)*)>x-6b$qp@DPp7HOt2<(p3bqV42eLNw_9j^m!)UIY({!s1I^7d86(6nMotKYHeHcsC0l*EeHdIq%DIqzS*`6sjKQJ$#H-gqh>c%g*Uz%Dot z`Bm%=P^D6a>bsC++8uJhPX4UHZ0DfoIzu;AU>`bg0j#k9VK;eC+MgmM{6T_VekfRw z*Owf~$DjeX{=o=qu4x_>o=(4nP$(J3UY>s&+n&JiN5F^T_KLqqgkR*-@XyrGP1_6n zm@(i*W#t4z({dM5aZ~68=VOae2m`sRcY=gy*)iP{f6saC_@UtE1gk?=kmEz{)Dn?O zijo$wgwNY)AO*it`I}smW<3$s_`!is#k6aHeU0y>9c zrQ{;OoIG#}e~t1CUn6>$QByvX2~~@@xOyF3o=7^wdkjoJ9mH00R`m= zMmKA$YG7S?t8mXF>cojpiM`z%zjJ&G>35d!x=SjgLoe6&Xe+4Duv3=ePXuVPW^q1S z_NzLTvhxCR15IQ+8Zm16VgOZr2TnA;7Ac6IBrHN`ZzNwsU%)J@+|h7yTyeK1dOyS8 zv;msoQ5k?C_q5JI^+a-^51c3>z-K)a!{?n!r4~{8I8y8Y=Di{u9b>N!zwbq4sq6&(qw6M0 zh`XCUUfuy!gb9Nf#ZR#i5~XTEJ@5Yf>2Uo4?#2~sOamM}d&`V%IAf!wYy}e-tpG{; zm_3#sIW6Ty9T0jsGz@Yq(UIId?0=7j(q(?xE406t5-IB{M+*gYE;YbN=)j)r%Muf! z1IEnT_J9#`k_-*=CI&aaxNqw*l>#z2IJgiLJ45Tztz%gzDdN<)p6qi92TjC;h>PgG zH}QHor4v;ojB9UHSOv-n#YHaSYjC`*;OEc@zt?}@wTA_c3PF0h8cw2*6fbzAMx`4* zkeg288G(@Pqcf|#?`hD?EJS@OiX2Z$gfl7?qs7{wz_4ah&UnP@e{`4*I zjZX5&g5SMr%IAsisjfDj-{VDZcx+NEy{#)~6#Fwr)KqC1)UB)E#9SCr|`xd=vl zH-~SHiA2acmyYX3P{|RW_YK^KPCwzJy;8^b-dL_8-?eZfMR)7o@_p37x#iYUgOnya z>Cnog${xtWKegT-=ykm;PdB?ll`*9h<{Jt<8C=t?e~PrGxUNIsnK0kNri$J}aZ6aEg7#QcW4VB%EuvecIe` zcs0(c0KSzJ3S?qy_!;&?ELNzq7esuxm)f#o4P^Qns~sn&s2Bfs-uTBLS1ZJ6AX7li z+C3t3^-C)bH0caAI>2<`-UR5YMno$P9fEo2keR&F3Q$%3g|UpYf!*J;fXz$-(#7IC zdKaWGzND0@-!enIYfRA`9O6!o3kteJ{~LupqC+0+cMB}7_%Sd0u-^| zw+F{YJuQuJ7vEcXuq+}QV3_My|J@(>tj_d!>a9jgj-=sEJ0o%NdKrw2@imK?#iSU@ zHnp`N!lI%#PXLP#i0HxMp?(7o#`@;I0qF>oKXcN-@NhKVh64uqMsev^XOdLHvLgZG z>iQedRv?;Iv^|mJ0&RD-2iz#!qzuz{gX+%jwg|^@`hcw1$8PLnWf%OQEqoCym3^91 z&~;6Dv3PDHZ%c4`aymZgYs25_uI!PFFln^{0*p_@ByBUT@4?w1XH~WPj7G!s&M3=) ztU}sw6_@mk`tf=u-}+;TDh`{1+oDO4xe@8d&^8x&elIsx!K+%^T&+g=a;PMR`+Gn; z5E(bqb$P>-xQ<}CsZUBL&@%Mxik@~j^U&Ml{;2n>s; zI;iY-jKEN!Na#BKkV_~TZNsf@c23>2s@3WC@7m=K%hEiTi+kktB8{A$j_^#%b$oZQ zMXO#-)rUKEePb_GiJ6V6Q9S?cDNcp!eF-L6{~5|xTrEEIJVTbKkH-;dmxu4;g&Hz) z#rK&%r;P4hLglX}INB}XbFJh$LzYb7n-C(Wcjd2p^NXaPmO1Db_uV0|gtg%9O~tcU zXj|6qw1=uKh1LU^&q>Xc`T=6+a$jxt4^y_N&YU~WihM1l(Njf<;C7y%@2(95Tt~O( zOP`}iQffW55;v$BX(-QH5hnL)ZZF5ETG4v#O}!LO^N#yi`oG)_2m>ZrZK%TFxjzA3r&yX?I-u; zlS4C`aH7;x!|W_^eW9Q^mfS|IJkW(o$Zq8Tnmrq+K8ap!1!(oqnqc?q)RKHWb&}^f z`-VrTR`!Dyyp_pkeH@ zQcfCNX+QK);G9P08}LBc;u!s0k|JCsM%7_O0UsK`tHh*U6lw8%{Ti)BImX=C#wUW_ zhA~5A!=BM2oCezUQ|%!~|7+B+IGAav;q_k^0+i%^N5Pt4AWTj1b<`7ZD}3(o%$~E* zlN3d3dQ4taht%vV<`SH=MO&hTcISh|G8upM!VjxTefw1q@K5^IOq}&?D2W2J)FIpB z)H>;D-Jy^UBo(Kw3i}s-uc&48lbx6SbTAt@WSLUp86P{jmF>8MJ;-Uo|8bURm*xe& z0d(|UJX)tO z$);glTh==$!p&`8KL9Z&!6Ze{8;RB_viwRfQ#NY=!*E2(Fxy$&bE)R<(ZmCn6O$i} z)KY(c6}U=`^h@}q!_%9gPW~T}?&ftohHabF0x+LJCH_A-me^1}_e;z48ouv5Vw6U8Qlu#3F${1Yf~zu7ogU)bDe%g038Uh_S&u?j9o zGjZf`av{JBVix6z2mcJ#&Ll~Ikv18DX&Wb@HsZ614KpE<816@8i_0I4(!NZ(S54*$ z^BoM!Ez35_LbxBZN>kyM(lxY%zgM<{{$&pD_Sl07dQU&+-xj9<#ogHOxNvn=aK=AK z7Yi~v!iHu_j$Jo@yNG1l!W};%3!tRvLDsT1B3XkfF*j`fVNV2+9ks9X2i-!o=>$}Z z&UZPRCWVD6md<)aLbt|n#4YO3@GP2fkGKSmf{Manf4`^J;gS5w@}1TfZ`2gC%1XK@ zvmB_(#jTCDwS^&UQR@%YPRa`8}?S=*-98DxRqWgsgz-yi4(;D= zc?&dzzenxccD3Li^0Eh^#F4ljm@oj_5*q;fDtf!>vYk)DcZB+7`%lLeMdAuY-ggNk zlwPA@`@1l>98-AP5t3A71SKyC;$BpeR#J#DgNETsN=mi7=Y=Ql6EqM~$)Q0Zr%%9G z$^euI`gbHdB2J%qe?E_EG|Qj+uRo_TpE9AQIr+uK;9#ggl+<hfZd>ON8tA7J?K=&yU%V)xABzEHLp&+85#S`J^9$DIiL6au}XO> zM!v^oisHUJk!=NZ%L#{0quH*CNleTw2!3XHjNiG(#@tJ$mdvF=Zf0SQ1L%}glNsD0%cNA~ z(}~+5Vyd$(F^y(4A!^}rpkn~Mj_?GW87PQOSO(ioG389@?tKG&%l)&yOd7_~V{baN z-r5xFkomSY&!M!!&=8!ijQX5NdH{i}pfOmi+CgLFIs4_XA6 zM=Nw=rv*X>FODbhtwI?5e$7S7w2fmE)d-nMLr#C(R+mBRw+=yA8l2;;h#Z~AYGRL?yOGx%R7{< z#%=WZpy1fMt3(%i-n&srS}`S|U+HTPr|iqy1UTP1GX8cbooX9u<8QOzqTXkAdo!0- zXpAzdytb6c!|hgDh)9)2x~whoo33LO2SuRIJ)-6i}P+YIfG{&?Li0W9hxI z`E*ixh5~`y z4jnuh2b)dVvvun`oB4(V%-_=@CPvl}AYKh@e%6+Gd9I0U*?U*4igvH>nMz@9q72*? z&7*F?I}|6RYz$1=3%5D~{0+&8R4l=rHB}mtT{1eE&NqwMqD^8`7$L6->f=_Czmi_> z2^TWk6>}3X1UhMHK~Y14g5&nih1(zJ#9};3XfW=onK?S^0Pui6(P|sly;5} z`wC%Xck=Z5RBkcKH&yA7)d+>*Ej|v&Coe_4TT_%W&*-SfJhK*~VkyZhml246R*G;g zYGCHC92xG~H+p=8we*CCY=<@soIFJ$g=Y1AsMmh@XtF}iaS_h>X59sExni z$VC~9*6+LN*{97|mPJTJmUIus?^l>SSHl^suU(cIf~Guw=i{{C>TJHPu`fYvVS&cn z{aiN~&l-)86cOUbMJ<^oI>5X{(?s|Nn%Pdihr5!V;luZBu#F%*R+^MljPF~Bu%6Xn-w5k$Zj?D9hS zPNxw}up(u*f0oj6_x-A%$_D6`5k}H!fu#&X1OBhDTLe&0{kbw`SU(6+{*mR@8W}*I zrY_JWc>TuY`X**cZzccBkW4J(?M1a{RjmmBoFxD#`d8eot0|&<{7p!4`K6(No0SxI zFVEx;r>e26z~4nh{|>%gcY&rXe(S1WPSX4DNLyEVMER3b1WB24`~Qx#?ePln)gQZT zr&hvJh5oN0eozn~gNb~Uzskn){=I;I&iLBBaH{Uh@~=OS|A}?|*H!RQ!BLeya_0-F zL%w2ibN_vn|EI4Z{T!f}P>%awImY}%wp&EGsW-3HV;4E_C39w>d|x!^zeegBgLkq0 zxfN_1+Hos{Kf)Iru@I}hV45#%W5T-WtB)OBH)xo>Cd8+y(5s`^aaxaxPq%YfS8$vS zgZ9jpZ1{dMnygkpf#^GI)>-f@A4Ua!0sZx6_v7NSKW&_s7UUOv*`jZ>NJUM1+Avj3(1-T-9}8 z-*Lv6S5UB`k>Dhnm0(N(YUzH(x*^qiXJ(pt;u57*+S2Np9c9j$6##2zE)n5k>Fhgee<#4v0k&Jb6A^{ zFUB5GcKX3oG%D?a#!Puxc;3sJAe4o*Xj$xVWSul0`HDB-Zb_ h9}=Ja6b&Y4m8 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 (#%variable-reference))) +(define code-insp (variable-reference->module-declaration-inspector + (#%variable-reference))) +(define (rearm orig new) + (syntax-rearm new orig)) +(define (disarm stx) + (syntax-disarm stx code-insp)) +(define (location stx) + (let ([src (syntax-source stx)]) + (define srcfile (path->string* src)) + (list srcfile (syntax-line stx) (syntax-column stx)))) +(define (id=? id name) + (and (identifier? id) (equal? (syntax-e id) name))) +(define (keep-lambda-properties orig new) + (let ([p (syntax-property orig 'method-arity-error)] + [p2 (syntax-property orig 'inferred-name)]) + (let ([new (if p + (syntax-property new 'method-arity-error p) + new)]) + (if p2 + (syntax-property new 'inferred-name p2) + new)))) +(define (rebuild expr replacements) + (let loop ([expr expr] [same-k (lambda () expr)] [diff-k (lambda (x) x)]) + (let ([a (assq expr replacements)]) + (cond + [a (diff-k (cdr a))] + [(pair? expr) + (loop (car expr) + (lambda () + (loop (cdr expr) same-k + (lambda (y) (diff-k (cons (car expr) y))))) + (lambda (x) + (loop (cdr expr) + (lambda () (diff-k (cons x (cdr expr)))) + (lambda (y) (diff-k (cons x y))))))] + [(vector? expr) + (loop (vector->list expr) same-k + (lambda (x) (diff-k (list->vector x))))] + [(box? expr) + (loop (unbox expr) same-k (lambda (x) (diff-k (box x))))] + [(syntax? expr) + (if (identifier? expr) + (same-k) + (loop (syntax-e expr) same-k + (lambda (x) (diff-k (datum->syntax expr x expr expr)))))] + [else (same-k)])))) + + + +;; Path management ------------------------------------------------------------- + +;; Convert a path to a string +(define (path->string* path) + (if (path? path) (path->string path) path)) + +;; We shouldn't instrument procedure calls to Rosette functions +;; that aren't actually exported by Rosette +(define (should-instrument-path? path) + (define the-path (path->string* path)) + (or (not the-path) + (not (or (string-contains? the-path "rosette/base/form/module.rkt") + (string-contains? the-path "rosette/base/form/control.rkt") + (string-contains? the-path "rosette/query/form.rkt") + (string-contains? the-path "rosette/query/core.rkt"))))) + + + +;; Instrumentation for procedure applications ---------------------------------- + +;; This is a giant hack to detect applications of keyword procedures. +;; Macro expansion of the definition and application of a keyword procedure +;; hides the information we need, with no simple way to get it back +;; (the "is original?" syntax property we add below is not preserved). +;; So we have this heuristic to try to detect applications of keyword procedures +;; that originated in a file we're instrumenting. +(define original-files (mutable-set)) +(define (is-keyword-procedure-application? expr head) + ; Here are the rules: + ; - if `head` has an alias or converted args property, it must be a kw procedure application. + ; return #t iff the source of that property is not in rosette/query/form.rkt. + ; - otherwise, it's a kw procedure application if the source of the expr is kw.rkt. + ; - if `head` has a source, it must be an original file + ; - if `head` has no source, assume it's a kw procedure application unless it's the + ; `unpack` procedure generated by kw.rkt + (define source-property + (or (syntax-procedure-alias-property head) + (syntax-procedure-converted-arguments-property head))) + (cond + [source-property + (define src (syntax-source (if (pair? source-property) (car source-property) source-property))) + (should-instrument-path? src)] + [(and (syntax-source expr) + (string-contains? (path->string* (syntax-source expr)) + "racket/private/kw.rkt")) + (if (syntax-source head) + (set-member? original-files (syntax-source head)) + (let ([s (syntax->datum head)]) + (or (not (symbol? s)) + (not (string-prefix? (symbol->string s) "unpack")))))] + [else + #f])) + + +;; Produce a list of syntax objects that quote each item in the input list +(define (quote-list lst phase) + (with-syntax ([qt (syntax-shift-phase-level #'quote (- phase base-phase))]) + (for/list ([x lst]) (quasisyntax (qt #,x))))) + + +;; Wrap an application (passed as the syntax list `rest`, without the #%app first element) +;; with a call to the record-apply! instrumentation +(define (instrument-application stx rest phase) + (with-syntax ([record-apply! record-apply!] + [app (syntax-shift-phase-level #'#%plain-app (- phase base-phase))] + [qt (syntax-shift-phase-level #'quote (- phase base-phase))] + [lst (syntax-shift-phase-level #'list (- phase base-phase))]) + (quasisyntax (app (qt record-apply!) (app lst #,@(quote-list (location stx) phase)) #,@rest)))) + + + +;; Instrumentation for let bindings and define-values -------------------------- + +;; Rewrite a definition of the form +;; (let (names rhs) body) +;; to record source instrumentation when the rhs is a plain-lambda. +(define (instrument-binding phase names rhs [lv #'let-values]) + (let* ([rhs* (annotate rhs phase)] + [rhs** (syntax-case rhs* (#%plain-lambda) + [(#%plain-lambda . rest) + (for/or ([n (syntax->list names)]) (is-original? n)) + (with-syntax ([record-source! record-source!] + [app (syntax-shift-phase-level #'#%plain-app (- phase base-phase))] + [qt (syntax-shift-phase-level #'quote (- phase base-phase))] + [lst (syntax-shift-phase-level #'list (- phase base-phase))] + [lv (syntax-shift-phase-level lv (- phase base-phase))] + [values (syntax-shift-phase-level #'values (- phase base-phase))]) + (quasisyntax + (lv ([#,names #,rhs*]) + (app (qt record-source!) (app lst #,@(syntax->list names)) (app lst #,@(quote-list (location rhs*) phase))) + #,(if (one-name names) ; optimization for single return value + (car (syntax-e names)) + #`(app values #,@(syntax->list names))))))] + [_ rhs*])]) + (cons rhs rhs**))) + + + +;; Core instrumentation procedure ---------------------------------------------- + +;; Recursively annotate a lambda expression +(define (annotate-lambda expr clause bodys-stx phase) + (let* ([bodys (stx->list bodys-stx)] + [bodyl (map (lambda (e) (annotate e phase)) bodys)]) + (rebuild clause (map cons bodys bodyl)))) + +;; Recursively annotate a sequence +(define (annotate-seq expr bodys-stx annotate phase) + (let* ([bodys (syntax->list bodys-stx)] + [bodyl (map (lambda (b) (annotate b phase)) bodys)]) + (rebuild expr (map cons bodys bodyl)))) + +;; Recursively annotate a submodule +(define (annotate-module expr disarmed-expr phase) + (define shifted-disarmed-expr + (syntax-shift-phase-level disarmed-expr (- phase))) + (syntax-case shifted-disarmed-expr () + [(mod name init-import mb) + (syntax-case (disarm #'mb) () + [(__plain-module-begin body ...) + ;; Just wrap body expressions + (let ([bodys (syntax->list (syntax (body ...)))]) + (let ([bodyl (map (lambda (b) (annotate-top b 0)) bodys)] + [mb #'mb]) + (rearm + expr + (syntax-shift-phase-level + (rebuild + shifted-disarmed-expr + (list (cons + mb + (rearm + mb + (rebuild mb (map cons bodys bodyl)))))) + phase))))])])) + +;; Return the first element in a list of syntax objects if the list has length 1 +(define (one-name names-stx) + (let ([l (syntax->list names-stx)]) + (and (pair? l) + (null? (cdr l)) + (car l)))) + + +;; Create a top-level annotation procedure that recurses down the syntax of a +;; fully expanded module. +;; This form is copied from errortrace, with the features we don't use removed, +;; and with additional annotation for let bindings and top level define-values. +(define (make-annotate top?) + (lambda (expr phase) + (define disarmed-expr (disarm expr)) + (kernel-syntax-case/phase disarmed-expr phase + [_ + (identifier? expr) + expr] + [(#%top . id) + expr] + [(#%variable-reference . _) + expr] + [(define-values names rhs) + top? + (let* ([rhsl (instrument-binding phase #'names #'rhs #'letrec-values)]) + (rebuild disarmed-expr (list rhsl)))] + + [(begin . exprs) + top? + (rearm + expr + (annotate-seq disarmed-expr + (syntax exprs) + annotate-top phase))] + [(define-syntaxes (name ...) rhs) + top? + (let* ([marked (annotate (syntax rhs) (add1 phase))] + ;; cover at THIS phase, since thats where its bound + [rebuilt (rebuild disarmed-expr (list (cons #'rhs marked)))]) + (rearm expr rebuilt))] + [(begin-for-syntax . exprs) + top? + (rearm + expr + (annotate-seq disarmed-expr + (syntax exprs) + annotate-top + (add1 phase)))] + + [(module name init-import mb) + (annotate-module expr disarmed-expr 0)] + [(module* name init-import mb) + (annotate-module expr disarmed-expr (if (syntax-e #'init-import) 0 phase))] + + [(#%expression e) + (rearm expr #`(#%expression #,(annotate (syntax e) phase)))] + + ;; No way to wrap + [(#%require i ...) expr] + ;; No error possible (and no way to wrap) + [(#%provide i ...) expr] + [(#%declare i ...) expr] + + ;; No error possible + [(quote _) + expr] + [(quote-syntax . _) + expr] + + ;; Wrap body, also a profile point + [(#%plain-lambda args . body) + (rearm + expr + (keep-lambda-properties + expr + (annotate-lambda expr disarmed-expr (syntax body) + phase)))] + [(case-lambda clause ...) + (with-syntax ([([args . body] ...) + (syntax (clause ...))]) + (let* ([clauses (syntax->list (syntax (clause ...)))] + [clausel (map + (lambda (body clause) + (annotate-lambda + expr clause body phase)) + (syntax->list (syntax (body ...))) + clauses)]) + (rearm + expr + (keep-lambda-properties + expr + (rebuild disarmed-expr (map cons clauses clausel))))))] + + ;; Wrap RHSs and body + [(let-values ([vars rhs] ...) . body) + (let ([rhsl (map (curryr (curry instrument-binding phase) #'let-values) + (syntax->list #'(vars ...)) + (syntax->list #'(rhs ...)))] + [bodysl (map (lambda (body) (cons body (annotate body phase))) (syntax->list #'body))]) + (rearm + expr + (rebuild disarmed-expr (append rhsl bodysl))))] + [(letrec-values ([vars rhs] ...) . body) + (let ([rhsl (map (curryr (curry instrument-binding phase) #'letrec-values) + (syntax->list #'(vars ...)) + (syntax->list #'(rhs ...)))] + [bodysl (map (lambda (body) (cons body (annotate body phase))) (syntax->list #'body))]) + (rearm + expr + (rebuild disarmed-expr (append rhsl bodysl))))] + ;; This case is needed for `#lang errortrace ...', which uses + ;; `local-expand' on the module body. + [(letrec-syntaxes+values sbindings ([vars rhs] ...) . body) + (let ([rhsl (map (curryr (curry instrument-binding phase) #'letrec-values) + (syntax->list #'(vars ...)) + (syntax->list #'(rhs ...)))] + [bodysl (map (lambda (body) (cons body (annotate body phase))) (syntax->list #'body))]) + (rearm + expr + (rebuild disarmed-expr (append rhsl bodysl))))] + + ;; Wrap RHS + [(set! var rhs) + (let ([new-rhs (annotate (syntax rhs) phase)]) + (rearm + expr + (rebuild disarmed-expr (list (cons #'rhs new-rhs)))))] + + ;; Wrap subexpressions only + [(begin e) + ;; Single expression: no mark + (rearm + expr + #`(begin #,(annotate (syntax e) phase)))] + [(begin . body) + (rearm + expr + (annotate-seq disarmed-expr #'body annotate phase))] + [(begin0 . body) + (rearm + expr + (annotate-seq disarmed-expr #'body annotate phase))] + [(if tst thn els) + (let ([w-tst (annotate (syntax tst) phase)] + [w-thn (annotate (syntax thn) phase)] + [w-els (annotate (syntax els) phase)]) + (rearm + expr + (rebuild disarmed-expr (list (cons #'tst w-tst) + (cons #'thn w-thn) + (cons #'els w-els)))))] + [(with-continuation-mark . body) + (rearm + expr + (annotate-seq disarmed-expr (syntax body) + annotate phase))] + + ;; Wrap whole application, plus subexpressions + [(#%plain-app . rest) + (if (stx-null? (syntax rest)) + expr + (let* ([head (car (syntax-e #'rest))] + [restl (map (lambda (body) (cons body (annotate body phase))) (syntax->list #'rest))]) + (rearm + expr + (rebuild + (if (or (and (or (is-original? expr) + (is-original? head)) + (should-instrument-path? (syntax-source head))) + (is-keyword-procedure-application? expr head)) + (instrument-application expr #'rest phase) + disarmed-expr) + restl))))] + [_else + (error 'errortrace "unrecognized expression form~a~a: ~.s" + (if top? " at top-level" "") + (if (zero? phase) "" (format " at phase ~a" phase)) + (syntax->datum expr))]))) + +;; Create two annotation procedures: one for top-level forms and one for everything else +(define annotate (make-annotate #f)) +(define annotate-top (make-annotate #t)) + + + +;; Original syntax detection --------------------------------------------------- +;; This hack is copied from errortrace to detect "original" parts of the input +;; program. + +;; Mark original syntax +(define annotate-key 'sympro:original) +(define (add-annotate-property s) + (cond + [(syntax? s) + (define new-s (syntax-rearm + (let ([s (disarm s)]) + (datum->syntax s + (add-annotate-property (syntax-e s)) + s + s)) + s)) + (syntax-property new-s annotate-key #t #t)] + [(pair? s) + (cons (add-annotate-property (car s)) + (add-annotate-property (cdr s)))] + [(vector? s) + (for/vector #:length (vector-length s) ([e (in-vector s)]) + (add-annotate-property e))] + [(box? s) (box (add-annotate-property (unbox s)))] + [(prefab-struct-key s) + => (lambda (k) + (apply make-prefab-struct + k + (add-annotate-property (cdr (vector->list (struct->vector s))))))] + [(and (hash? s) (immutable? s)) + (cond + [(hash-eq? s) + (for/hasheq ([(k v) (in-hash s)]) + (values k (add-annotate-property v)))] + [(hash-eqv? s) + (for/hasheqv ([(k v) (in-hash s)]) + (values k (add-annotate-property v)))] + [else + (for/hash ([(k v) (in-hash s)]) + (values k (add-annotate-property v)))])] + [else s])) + +;; Is this syntax original? +(define (is-original? stx) + (and (syntax-source stx) + (syntax-property stx annotate-key))) + + + +;; Compile handler ------------------------------------------------------------- + +;; Annotate a top-level expr +(define (profile-annotate stx) + (define annotate-id + (syntax-case stx () + [(mod id lang (mod-begin forms ...)) + (and (id=? #'mod 'module) + (or (id=? #'lang 'rosette) + (id=? #'lang 'rosette/safe) + (not (symbolic-profile-rosette-only?))) + (id=? #'mod-begin '#%module-begin)) + #'id] + [_ #f])) + (if annotate-id + (let () + (printf "INSTRUMENTING ~v\n" (syntax-source stx)) + (set-add! original-files (syntax-source stx)) + (define expanded-e (expand-syntax (add-annotate-property stx))) + (annotate-top expanded-e (namespace-base-phase))) + stx)) + + +;; Create a compile handler that invokes profile-annotate on +;; a piece of syntax that needs compilation, and then runs the +;; existing (current-compile) +(define (make-symbolic-profile-compile-handler) + (define orig (current-compile)) + (lambda (e immediate-eval?) + (orig (profile-annotate + (if (syntax? e) + e + (namespace-syntax-introduce + (datum->syntax #f e)))) + immediate-eval?))) + + +(define symbolic-profile-compile-handler + (make-symbolic-profile-compile-handler)) diff --git a/rosette/lib/profile/data.rkt b/rosette/lib/profile/data.rkt new file mode 100644 index 00000000..9d8cfca2 --- /dev/null +++ b/rosette/lib/profile/data.rkt @@ -0,0 +1,42 @@ +#lang racket + +(provide (all-defined-out)) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Profiler data structures + +;; A profile consists of a mutable box events, which contains a list of +;; profile-event? records. +(struct profile-state (events thd)) + +(struct profile-event (metrics)) +(struct profile-event-enter profile-event (id location procedure inputs) #:transparent) +(struct profile-event-exit profile-event (outputs)) +(struct profile-event-sample profile-event ()) +(struct profile-event-pc profile-event ()) +(struct profile-event-solve profile-event ()) +(struct profile-event-solve-start profile-event-solve ()) +(struct profile-event-solve-finish profile-event-solve (sat?)) +(struct profile-event-solve-encode profile-event-solve (asserts)) +(struct profile-event-finitize profile-event ()) +(struct profile-event-finitize-start profile-event-finitize ()) +(struct profile-event-finitize-finish profile-event-finitize ()) +(struct profile-event-encode profile-event ()) +(struct profile-event-encode-start profile-event-encode ()) +(struct profile-event-encode-finish profile-event-encode ()) +(struct profile-event-term profile-event (val)) +(struct profile-event-term-new profile-event-term ()) + +;; Returns a new profile +(define (make-profile-state) + (profile-state (box '()) (current-thread))) + +;; Append a new event to a profile state +(define (profile-state-append! state evt) + (define the-box (profile-state-events state)) + (let loop () + (let* ([old (unbox the-box)] + [new (cons evt old)]) + (unless (box-cas! the-box old new) + (loop))))) diff --git a/rosette/lib/profile/feature.rkt b/rosette/lib/profile/feature.rkt new file mode 100644 index 00000000..b7428ffa --- /dev/null +++ b/rosette/lib/profile/feature.rkt @@ -0,0 +1,119 @@ +#lang racket + +(require (only-in rosette union? union-contents union expression) + (only-in rosette/base/core/type get-type typed? type-deconstruct) + (only-in rosette/base/core/term term?)) + +(provide (except-out (all-defined-out) flatten-value)) + + + +; Stores a procedure that takes as input a list of values +; and outputs a number that characterizes the cost of those values. +(struct feature (name procedure) + #:property prop:procedure + [struct-field-index procedure] + #:guard (lambda (name proc id) + (unless (procedure? proc) + (error 'feature "Expected a procedure?, given ~a" proc)) + (values name proc)) + #:methods gen:custom-write + [(define (write-proc self port mode) + (fprintf port "(feature ~a)" (feature-name self)))]) + + +; A simple feature that returns the sum of the sizes of the input unions. +(define union-size-feature + (feature + 'union-size + (lambda (xs) + (for/sum ([x xs] #:when (union? x)) + (length (union-contents x)))))) + + +; Updates the footprint map to contain the object graph of x. +; The footprint is a set of key-value pairs, where the key is an +; object (a node in the graph), and the value is the number of +; outgoing edges. Symbolic terms, regardless of size, are treated +; as opaque values with no outgoing edges (just like concrete constants). +(define (measure! footprint x) + (unless (hash-has-key? footprint x) + (match x + [(union gvs) + (hash-set! footprint x (length gvs)) + (for ([gv gvs]) ; don't count the guards + (measure! footprint (cdr gv)))] + [(? list? children) + (hash-set! footprint x (length children)) + (for ([c children]) + (measure! footprint c))] + [(cons a b) + (hash-set! footprint x 2) + (measure! footprint a) + (measure! footprint b)] + [(? vector?) + (hash-set! footprint x (vector-length x)) + (for ([c x]) + (measure! footprint c))] + [(box c) + (hash-set! footprint x 1) + (measure! footprint c)] + [(? typed?) + (match (type-deconstruct (get-type x) x) + [(list (== x)) (hash-set! footprint x 0)] + [children + (hash-set! footprint x (length children)) + (for ([c children]) + (measure! footprint c))])] + [_ (hash-set! footprint x 0)]))) + + + +; A simple feature that measures V + E, where V is the number of vertices and +; E is the number of edges that make up the input object graph. +(define heap-size-feature + (feature + 'heap-size + (let ([cache (make-hash)]) + (lambda (xs) + (hash-ref! cache xs + (thunk + (define footprint (make-hash)) + (for ([x xs]) (measure! footprint x)) + (+ (hash-count footprint) + (for/sum ([v (in-hash-values footprint)]) v)))))))) + + +; A feature that determines the "signature" of a function call -- the types of +; each of its inputs (union, symbolic, or concrete). +(define signature-feature + (feature + 'signature + (lambda (xs) + (let loop ([xs xs]) + (cond + [(null? xs) '()] + [else (let ([x (car xs)]) + (cons (cond + [(union? x) 'union] + [(term? x) 'symbolic] + [else 'concrete]) + (loop (cdr xs))))]))))) + + +; A parameter that holds a list of features to profile. +(define enabled-features + (list signature-feature)) + + +; Convert a feature hash into a plain hash with symbols for keys and s-exps for values +(define (flatten-value x) + (cond + [(list? x) (map flatten-value x)] + [(hash? x) (for/hash ([(k v) x]) (values k (flatten-value v)))] + [(symbol? x) (symbol->string x)] + [else x])) +(define (features->flat-hash feats) + (for/hash ([f/v feats]) + (match-define (cons k v) f/v) + (values (feature-name k) (flatten-value v)))) diff --git a/rosette/lib/profile/graph.rkt b/rosette/lib/profile/graph.rkt new file mode 100644 index 00000000..bd31ffdf --- /dev/null +++ b/rosette/lib/profile/graph.rkt @@ -0,0 +1,84 @@ +#lang racket + +(require racket/hash racket/struct + "data.rkt" "record.rkt" "reporter.rkt" "feature.rkt") +(provide (all-defined-out)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Profile graph data structures + +;; A profile node is an entry in the dynamic control flow graph of the +;; profiled code. It contains a pointer to its parent node, +;; a list of children nodes, and a profile-data struct that contains the +;; actual data for the profile. +(struct profile-node (id parent children data) #:mutable + #:methods gen:custom-write + [(define write-proc + (make-constructor-style-printer + (lambda (obj) 'profile-node) + (lambda (obj) (let ([d (profile-node-data obj)]) + (if d + (list (let ([proc (profile-data-procedure d)]) + (if (symbol? proc) proc (object-name proc))) + (metrics-ref (profile-data-start d) 'time)) + (list #f #f))))))]) + + +;; Profile data for a single procedure invocation. +;; * The location field stores the location at which the given procedure was +;; invoked. +;; * The procedure field is the invoked procedure +;; * The inputs and outputs fields are assoc lists from features to numbers. +;; For each feature in enabled-features, they store the value of that +;; feature for the inputs and outputs of the current invocation. +;; * The metrics field is a hash map from symbols to numbers, where each +;; symbol describes a performance metric collected during symbolic evaluation, +;; e.g., cpu time, real time, gc time, the number of merge invocations, the number +;; of unions and terms created, etc. +;; * The start and finish fields track the value of various metrics at the entry +;; and exit to the current invocation, respectively. +(struct profile-data (location procedure inputs outputs metrics start finish) #:mutable #:transparent) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Conversion + +;; Convert an instance of profile-state (i.e., a list of profile events) into a +;; dynamic call graph representation. +(define (profile-state->graph state) + (define events (reverse (unbox (profile-state-events state)))) + (unless (profile-event-enter? (first events)) + (error 'profile-state->graph "expected first event to be profile-event-enter")) + (define node #f) + (define root #f) + (for ([e events]) + (match e + [(profile-event-enter met id loc proc in) + (define new-in (features->flat-hash in)) + (define new-name proc) + (define new-data (profile-data loc new-name new-in (hash) '() met (hash))) + (define new-node (profile-node id node '() new-data)) + (if (false? node) + (let () + (set-profile-node-parent! new-node new-node) + (set! root new-node)) + (set-profile-node-children! node (append (profile-node-children node) (list new-node)))) + (set! node new-node)] + [(profile-event-exit met out) + (define data (profile-node-data node)) + (define metrics (diff-metrics (profile-data-start data) met)) + (set-profile-data-metrics! data metrics) + (define new-out (features->flat-hash out)) + (set-profile-data-outputs! data new-out) + (set-profile-data-finish! data met) + (set! node (profile-node-parent node))] + [(profile-event-sample met) ; fill in missing statistics up the callgraph + (let rec ([node node]) + (define data (profile-node-data node)) + (define metrics (diff-metrics (profile-data-start data) met)) + (set-profile-data-metrics! data metrics) + (set-profile-data-finish! data met) + (unless (eq? node (profile-node-parent node)) + (rec (profile-node-parent node))))] + [_ void])) + root) diff --git a/rosette/lib/profile/raco.rkt b/rosette/lib/profile/raco.rkt new file mode 100644 index 00000000..7d54d040 --- /dev/null +++ b/rosette/lib/profile/raco.rkt @@ -0,0 +1,103 @@ +#lang racket/base + +(require racket/cmdline + raco/command-name + "compile.rkt" + "tool.rkt" + (only-in "record.rkt" filtering-threshold) + "renderer/trace.rkt" + "renderer/noop.rkt" + "renderer/heap.rkt" + "renderer/report.rkt") + +;; raco symprofile (based on raco feature-profile) +;; profile the main submodule (if there is one), or the top-level module + +(define renderer% (make-parameter make-report-renderer)) +(define run-profiler? (make-parameter #t)) +(define module-name (make-parameter 'main)) +(define renderer-options (make-parameter (hash))) +(define file + (command-line #:program (short-program+command-name) + #:help-labels "" "Profiler modes" + #:once-any ; Profiler selections + ["--trace" "Produce a complete execution trace" + (renderer% make-trace-renderer)] + ["--report" "Produce an interactive report" + (renderer% make-report-renderer)] + ["--stream" "Produce a streaming interactive report" + (renderer% make-report-stream-renderer)] + ["--noop" "Produce no profile output (for testing)" + (renderer% make-noop-renderer)] + ["--heap" "Profile a heap profile" + (renderer% make-heap-renderer)] + ; Tool configuration + #:help-labels "" "Profiled code settings" + #:once-each + [("-l" "--compiler-only") + "Only install the compile handler; do not run the profiler" + (run-profiler? #f)] + [("-m" "--module") name + "Run submodule (defaults to 'main)" + (module-name (string->symbol name))] + [("-r" "--racket") + "Instrument code in any module, not just `#lang rosette`" + (symbolic-profile-rosette-only? #f)] + #:help-labels "" "Profiling settings" + #:once-each + [("-t" "--threshold") t + "Threshold (in milliseconds) for pruning cheap function calls" + (let ([th (string->number t)]) + (when (or (eq? th #f) (< th 0)) + (raise-argument-error 'threshold "number >= 0" t)) + (filtering-threshold th))] + ; Renderer-specific configuration + #:help-labels "" "Mode-specific settings" + #:once-each + [("-d" "--delay") d + "Streaming report: delay between samples, in seconds" + (let ([de (string->number d)]) + (when (or (eq? de #f) (<= de 0)) + (raise-argument-error 'delay "number > 0" d)) + (renderer-options (hash-set (renderer-options) 'interval de)))] + [("-s" "--symlink-html") + "Interactive reports: symlink template instead of copying" + (renderer-options (hash-set (renderer-options) 'symlink #t))] + #:help-labels "" + #:args (filename . args) + ; pass all unused arguments to the file being run + (current-command-line-arguments (list->vector args)) + filename)) + +; Set up the renderer +(define (renderer source-stx name) + ((renderer%) source-stx name (renderer-options))) +(current-renderer renderer) + +(collect-garbage) +(collect-garbage) +(collect-garbage) + +(current-compile symbolic-profile-compile-handler) + + +; check if there's a module of the given name, and if not, +; import the entire file instead +(define (module-to-profile file mod) + (define file-path `(file ,file)) + (define mod-path `(submod ,file-path ,mod)) + (if (module-declared? mod-path #t) + (values mod-path mod-path) + (values file-path file))) + +(define-values (mod mod-pretty) + (module-to-profile file (module-name))) + +(define (run) + (dynamic-require mod #f)) + + +(if (run-profiler?) + (profile-thunk run #:source mod-pretty + #:name (format "~a" file)) + (run)) diff --git a/rosette/lib/profile/record.rkt b/rosette/lib/profile/record.rkt new file mode 100644 index 00000000..07d4e7ce --- /dev/null +++ b/rosette/lib/profile/record.rkt @@ -0,0 +1,155 @@ +#lang racket + +(require rosette/base/core/reporter racket/hash + "data.rkt" "feature.rkt" "reporter.rkt") +(provide (all-defined-out)) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Source location tracking + +;; Represents the global map from procedure objects to their source locations (if known). +(define current-sources (make-parameter (make-hash))) + +;; Records the mapping from the given procedure object to its source info. +(define (record-source! procs location) + (for ([p procs]) + (hash-set! (current-sources) p location))) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Profiler run-time state + +;; A parameter that holds the current profile / call stack. +(define current-profile (make-parameter #f)) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Run-time profile filtering + +; Minimum time in milliseconds for a call to be included in the profile. +; Filtering is disabled if this is zero. +(define filtering-threshold (make-parameter 1.0)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Run-time profile node updates + +;; Records a procedure entry by appending a new profile node to the current one. +;; This procedure should be called after all arguments to the profiled procedure +;; have been evaluated, but before the procedure is invoked. +(define-syntax-rule (record-enter profile reporter) + (let ([next-id (let ([n 0]) (thunk (begin0 n (set! n (add1 n)))))]) + (lambda (loc proc in) + (when (and profile (eq? (current-thread) (profile-state-thd profile))) + (let* ([inputs (compute-features in)] + [metrics (get-current-metrics/call reporter)] + [new (profile-event-enter metrics (next-id) loc proc inputs)]) + (profile-state-append! profile new)))))) + +; Default version just uses the current-profile/current-reporter params +(define default-record-enter + (record-enter (current-profile) (current-reporter))) + +(define record-enter! default-record-enter) + + +;; Records a procedure exit by modifying the data for the current profile node. +;; This procedure should be called after the profiled procedure call returns. +(define-syntax-rule (record-exit profile reporter threshold) + (let ([do-record-exit! + (lambda (out) + (let* ([outputs (compute-features out)] + [metrics (get-current-metrics/call reporter)] + [new (profile-event-exit metrics outputs)]) + (profile-state-append! profile new)))]) + (lambda (out) + (when (and profile (eq? (current-thread) (profile-state-thd profile))) + (let* ([curr profile] + [evts (unbox (profile-state-events curr))]) + (cond + [(or (null? evts) + (not (profile-event-enter? (car evts))) + (>= (- (current-inexact-milliseconds) (get-call-time (car evts))) threshold)) + (do-record-exit! out)] + [else ; ==> (and (< real (filter-threshold)) (profile-event-enter? (car evts))) + ; prune the previous event, which was an enter + (define the-box (profile-state-events curr)) + ; if this CAS fails, it can only be because the streaming thread removed + ; all events from the box. if that happened then the ENTER we're trying + ; to delete has already been published, and so we need to retain the + ; corresponding EXIT. + (unless (box-cas! the-box evts (cdr evts)) + (do-record-exit! out))])))))) + +; Default version just uses the current-profile/current-reporter params +(define default-record-exit + (record-exit (current-profile) (current-reporter) (filtering-threshold))) + +(define record-exit! default-record-exit) + + +; Specialize record-enter! and record-exit! +(define (specialize-recorders! profile reporter threshold) + (set! record-enter! (record-enter profile reporter)) + (set! record-exit! (record-exit profile reporter threshold))) +; Undo specialization +(define (reset-specialized-recorders!) + (set! record-enter! default-record-enter) + (set! record-exit! default-record-exit)) + + +;; Record a sample (used when a profile is unfinished) +(define (record-sample!) + (profile-state-append! (current-profile) (get-sample-event))) + + +;; Compute the interesting features of a list of inputs or outputs +(define (compute-features xs) + (list (cons signature-feature (signature-feature xs)))) + + +;; Records the application of a procedure proc at a location loc to +;; the given by-position and/or keyword arguments. +;; The syntax rule generates the instrumentation for the two different +;; cases (keyword arguments or not), which helps expose some fusion +;; optimization opportunities to the compiler. +(define-syntax-rule (runner app-proc-to-in-expr) + (let* ([handler (lambda (exn) (values exn null))] + [returner (lambda e (values #f e))]) + (lambda (loc proc in) + (record-enter! loc proc in) + (call-with-exception-handler + (lambda (e) + (record-exit! '()) + e) + (thunk + (define ret (call-with-values (lambda () app-proc-to-in-expr) list)) + (record-exit! ret) + (apply values ret)))))) +(define record-apply! + (make-keyword-procedure + (lambda (kws kw-args loc proc . rest) + ((runner (keyword-apply proc kws kw-args rest)) loc proc (append rest kw-args))) + (lambda (loc proc . rest) + ((runner (apply proc rest)) loc proc rest)))) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Top-level profiler invocation + +;; Run a thunk in a given profiler+reporter context. and return its output +(define (run-profile-thunk proc profile reporter) + (parameterize ([current-profile profile] + [current-reporter reporter]) + (specialize-recorders! profile reporter (filtering-threshold)) + (record-enter! #f 'the-profiled-thunk '()) + (define out + (with-handlers ([exn:break? (lambda (e) + ((error-display-handler) (if (exn? e) (exn-message e) (~e e)) e) + (record-sample!) + '())]) + (define out (call-with-values proc list)) + (record-exit! out) + out)) + (reset-specialized-recorders!) + out)) diff --git a/rosette/lib/profile/renderer/heap.rkt b/rosette/lib/profile/renderer/heap.rkt new file mode 100644 index 00000000..4cdaa11f --- /dev/null +++ b/rosette/lib/profile/renderer/heap.rkt @@ -0,0 +1,205 @@ +#lang racket + +(require net/sendurl + "renderer.rkt" + "../data.rkt" "../reporter.rkt" + (only-in rosette/base/core/term expression)) +(provide make-heap-renderer) + +; The summary renderer aggregates inclusive and exclusive time +; per procedure and prints the results. + +(define (make-heap-renderer source name [options (hash)]) + (heap-renderer source name)) + +(struct heap-renderer (source name) + #:transparent + #:methods gen:renderer + [(define start-renderer void) + (define (finish-renderer self profile) + (match-define (heap-renderer source name) self) + (time (analyze-terms profile)))]) + + +(define (analyze-terms profile) + ; a node in the term graph contains three fields + ; * the term + ; * the subterms (term-node?s) the term contains + ; * the creator of the term (a call-node?) + (struct term-node (term subterms creator)) + + ; a node in the call tree contains five fields + ; * the name of the procedure invoked + ; * the children invoked by the procedure (a list of call-node?s) + ; * the total time spent in this node (including children) + ; * a list of terms created by this call (term-node?s) + (struct call-node (proc parent children time created-terms) #:mutable) + + ; term? -> term-node? + (define term->node (make-hasheq)) + ; term?s that reached the solver + (define solved-terms (mutable-seteq)) + ; current call-node? + (define current-call #f) + + + (define events (reverse (unbox (profile-state-events profile)))) + (for ([e events]) + (match e + [(profile-event-enter met id loc proc in) + ; temporarily store the metrics in the time field -- they'll be removed + ; when we reach the corresponding profile-event-exit? for this call + (define node (call-node (object-name proc) current-call '() met '())) + (when current-call + (set-call-node-children! current-call (cons node (call-node-children current-call)))) + (set! current-call node)] + [(profile-event-exit met out) + ; compute the delta metrics (the initial metrics were stored in the time field) + (define metrics (diff-metrics (call-node-time current-call) met)) + (define time (metrics-ref metrics 'time)) + (set-call-node-time! current-call time) + ; reverse the order of the calls since we built that list backwards + (set-call-node-children! current-call (reverse (call-node-children current-call))) + (unless (false? (call-node-parent current-call)) + (set! current-call (call-node-parent current-call)))] + [(profile-event-term-new met t) + (define children + (match t + [(expression op elts ...) + (for/list ([e elts] #:when (hash-has-key? term->node e)) + (hash-ref term->node e))] + [_ '()])) + (define node (term-node t children current-call)) + (set-call-node-created-terms! current-call (cons node (call-node-created-terms current-call))) + (hash-set! term->node t node)] + [(profile-event-solve-encode met lst) + (for* ([assts lst][a assts]) + (set-add! solved-terms a))] + [_ void])) + + + ; output dot + (define p (make-temporary-file "heap~a.dot")) + (define f (open-output-file p #:exists 'truncate)) + (fprintf f "digraph heap {\n ordering=out;\n") + + + ; decide which nodes will we put in the plot + ; by chosing a time threshold based on max time spent in any one call + (define (call-node-time-excl node) ; exclusive time for a call-node + (- (call-node-time node) (apply + (map call-node-time (call-node-children node))))) + (define maxtime-excl ; max exclusive time over all calls + (let loop ([node current-call]) + (apply max (append (list (call-node-time-excl node)) (map loop (call-node-children node)))))) + (define maxtime-incl ; max inclusive time over all calls + (apply max (map call-node-time (call-node-children current-call)))) + (define threshold (* 0.001 maxtime-incl)) + + ; now select the nodes to include and assign them dot names + (define node->label (make-hasheq)) + (let loop ([node current-call]) + (when (> (call-node-time node) threshold) + (hash-set! node->label node (format "n~v" (hash-count node->label))) + (for ([c (call-node-children node)]) (loop c)))) + + + ; determine which terms (transitively) reached the solver + (define reached-solver (mutable-seteq)) + (for ([t solved-terms]) + (define tn (hash-ref term->node t)) + (let loop ([tn tn]) + (unless (set-member? reached-solver tn) + (set-add! reached-solver tn) + (for ([tn* (term-node-subterms tn)]) (loop tn*))))) + + ; determine the sources of terms that did not reach the solver + (define unused-term-sources (make-hasheq)) + (for ([(t tn) term->node] #:unless (set-member? reached-solver tn)) + (define src (term-node-creator tn)) + (hash-set! unused-term-sources src (add1 (hash-ref unused-term-sources src 0)))) + + + ; emit the definitions for each node + (define (width dt) (exact->inexact (+ .75 (min (/ dt maxtime-excl) 1)))) + (define (height dt) (exact->inexact (+ .5 (min (/ dt maxtime-excl) 1)))) + (define (fontsize dt) (exact->inexact (+ 10 (* 8 (min (/ dt maxtime-excl) 1))))) + (define (color node) + (define score + (let ([num-terms (length (call-node-created-terms node))]) + (if (or (= num-terms 0) (< num-terms (* (hash-count term->node) 0.01))) + 0 + (/ (hash-ref unused-term-sources node 0) num-terms)))) + (define 255-color (inexact->exact (round (* (- 1 score) 255)))) + (define hex-color (~r 255-color #:base 16 #:min-width 2 #:pad-string "0")) + (format "#ff~a~a" hex-color hex-color)) + (let loop ([node current-call]) ; loop in call-graph order + (when (hash-has-key? node->label node) + (let* ([dt (call-node-time node)] + [dt-excl (call-node-time-excl node)] + [dtstr (~r dt #:precision 0)]) + (define label + (format "~a\n~a ms\n~v/~v terms" + (call-node-proc node) + dtstr + (hash-ref unused-term-sources node 0) + (length (call-node-created-terms node)))) + (fprintf f " ~a [shape=box,style=filled,fillcolor=\"~a\",label=\"~a\",width=~v,height=~v,fontsize=~v,fixedsize=true];\n" + (hash-ref node->label node) + (color node) + label + (width dt-excl) (height dt-excl) (fontsize dt-excl))) + (for ([c (call-node-children node)]) (loop c)))) + + + ; put control flow edges in the plot + (let loop ([node current-call]) + (when (hash-has-key? node->label node) + (for ([c (call-node-children node)]) + (when (hash-has-key? node->label c) + (fprintf f " ~a -> ~a;\n" (hash-ref node->label node) (hash-ref node->label c)))) + (for ([c (call-node-children node)] #:when (hash-has-key? node->label c)) (loop c)))) + + + ; compute data flow edges + (define dataflow (make-hasheq)) + (for ([tn (hash-values term->node)]) + (define creator (term-node-creator tn)) + (when (hash-has-key? node->label creator) + (define from (hash-ref! dataflow creator make-hasheq)) + (for ([tn* (term-node-subterms tn)]) + (define creator* (term-node-creator tn*)) + (when (hash-has-key? node->label creator*) + (hash-set! from creator* (add1 (hash-ref from creator* 0))))))) + + ; put data flow edges in the plot + (define flow-counts (flatten (map hash-values (hash-values dataflow)))) + (unless (null? flow-counts) + (define maxdataflow (apply max flow-counts)) + (define dataflow-threshold (* 0.01 maxdataflow)) + (for* ([(src dsts) dataflow] + [(dst v) dsts] + #:when (> v dataflow-threshold)) + (fprintf f " ~a -> ~a [penwidth=~v,color=blue];\n" + (hash-ref node->label src) + (hash-ref node->label dst) + (exact->inexact (* (/ v maxdataflow) 4))))) + + ; close the graph + (fprintf f "}\n") + (close-output-port f) + + + (printf "sources of unused terms: ~v\n" (hash-count unused-term-sources)) + (define sorted-sources + (sort (hash-keys unused-term-sources) > #:key (lambda (cn) (hash-ref unused-term-sources cn)))) + (define worst-sources (take sorted-sources (min 10 (length sorted-sources)))) + (for ([s worst-sources]) + (printf "* ~v - ~v\n" (call-node-proc s) (hash-ref unused-term-sources s))) + + + ; find a place to put the pdf + (define pdf-path (make-temporary-file "graph~a.pdf")) + (system (format "dot -Tpdf -q -o ~a ~a" pdf-path p)) + (send-url/file pdf-path) + + (printf "Output to file: ~a\n" pdf-path)) diff --git a/rosette/lib/profile/renderer/noop.rkt b/rosette/lib/profile/renderer/noop.rkt new file mode 100644 index 00000000..102a62a9 --- /dev/null +++ b/rosette/lib/profile/renderer/noop.rkt @@ -0,0 +1,22 @@ +#lang racket + +(require "../data.rkt" "../record.rkt" "renderer.rkt") +(provide make-noop-renderer) + +; The noop renderer does nothing! + +(define (make-noop-renderer source name [options (hash)]) + (noop-renderer source name)) + +(struct noop-renderer (source name) + #:transparent + #:methods gen:renderer + [(define start-renderer void) + (define (finish-renderer self profile) + (printf "Profiled ~v events.\n" (length (unbox (profile-state-events profile)))) + (define types (make-hash)) + (for ([e (unbox (profile-state-events profile))]) + (define type (vector-ref (struct->vector e) 0)) + (hash-set! types type (add1 (hash-ref types type 0)))) + (for ([type (sort (hash-keys types) > #:key (lambda (t) (hash-ref types t)))]) + (printf "* ~a: ~v\n" type (hash-ref types type))))]) diff --git a/rosette/lib/profile/renderer/renderer.rkt b/rosette/lib/profile/renderer/renderer.rkt new file mode 100644 index 00000000..9d8c595a --- /dev/null +++ b/rosette/lib/profile/renderer/renderer.rkt @@ -0,0 +1,8 @@ +#lang racket + +(require racket/generic) +(provide (all-defined-out)) + +(define-generics renderer + (start-renderer renderer profile reporter) + (finish-renderer renderer profile)) diff --git a/rosette/lib/profile/renderer/report.rkt b/rosette/lib/profile/renderer/report.rkt new file mode 100644 index 00000000..6524eee1 --- /dev/null +++ b/rosette/lib/profile/renderer/report.rkt @@ -0,0 +1,263 @@ +#lang racket + +(require racket/date racket/runtime-path racket/async-channel net/sendurl json + "../data.rkt" "../reporter.rkt" (only-in "../record.rkt" filtering-threshold) + "renderer.rkt" "syntax.rkt" + "report/generic.rkt" "report/ws-server.rkt" + "report/callgraph.rkt" "report/solver.rkt" "report/terms.rkt") +(provide make-report-renderer make-report-stream-renderer) + +; The report renderer produces HTML output by sending +; profile events to a collection of report components, +; which each generate JSON-formatted messages to be passed +; to the client webpage (either by streaming or statically). + + +; The path containing the HTML template +(define-runtime-path template-dir "report/html") + + +; Components that will be generating messages for this profile +(define report-components + (list make-callgraph-component + make-solver-calls-component + make-terms-component)) + + +(struct report-options (symlink? interval) #:transparent) + + +; Make a non-streaming report renderer +(define (make-report-renderer source name [options (hash)]) + (define components (for/list ([c report-components]) (c options))) + (report-renderer source name components (report-options (hash-ref options 'symlink #f) + (hash-ref options 'interval 2.0)))) + +; Make a streaming report renderer +(define (make-report-stream-renderer source name [options (hash)]) + (define components (for/list ([c report-components]) (c options))) + (report-renderer/stream source name components + (report-options (hash-ref options 'symlink #f) + (hash-ref options 'interval 2.0)) + #f #f)) + + +(struct report-renderer (source name components options) + #:transparent + #:methods gen:renderer + [(define (start-renderer self profile reporter) + (for ([c (report-renderer-components self)]) + (init-component c))) + (define (finish-renderer self profile) + (match-define (report-renderer source name components options) self) + (define events (prune-short-events (reverse (unbox (profile-state-events profile))))) + (define messages + (cons (metadata-message source name) + (apply append (for/list ([c components]) (receive-data c events))))) + (define path (render-html-template source (report-options-symlink? options))) + (render-report-messages messages path) + (open-report-in-browser path))]) + + +(struct report-renderer/stream report-renderer ([shutdown! #:mutable] [channel #:mutable]) + #:transparent + #:methods gen:renderer + [(define (start-renderer self profile reporter) + (match-define (report-renderer/stream source name components options _ _) self) + (for ([c (report-renderer-components self)]) + (init-component c)) + ; launch the server + (define-values (port shutdown! connected-channel) + (start-streaming-server self profile reporter)) + (set-report-renderer/stream-shutdown!! self shutdown!) + (set-report-renderer/stream-channel! self connected-channel) + ; open the browser with the initial messages + (define path (render-html-template source (report-options-symlink? options))) + (define messages + (list (metadata-message source name) + (stream-start-message port))) + (render-report-messages messages path) + (open-report-in-browser path) + ; wait for the browser to open the connection + (match (channel-get connected-channel) + ['connected void] + [x (error "unexpected response from client" x)])) + (define (finish-renderer self profile) + (match-define (report-renderer/stream _ _ _ _ shutdown! channel) self) + ; tell the connected client to wrap up + (channel-put channel 'finish) + ; wait for it to acknowledge, to give it time to pump its last messages + (match (channel-get channel) + ['finish void] + [x (raise x)]) + ; now safe to shutdown the websocker server + (shutdown!))]) + + +; Launch the WebSocket server +(define (start-streaming-server renderer profile reporter) + (match-define (report-renderer/stream _ _ components options _ _) renderer) + (define channel (make-channel)) ; channel for communicating with main thread + (define connected? (box #f)) ; only one client may connect + (define events-box (profile-state-events profile)) + (define interval (report-options-interval options)) + + ; the procedure for handling connections + (define (ws-connection conn state) + ; only one client may connect + (cond + [(box-cas! connected? #f #t) ; we won the race to connect + ; tell the main thread we're connected + (channel-put channel 'connected) + ; loop until we're done + (let loop () + (define sync-result (sync/timeout/enable-break interval channel)) + ; get the events from the profile and empty its buffer + (define events + (reverse + (cons (profile-event-sample (get-current-metrics/call reporter)) + (unbox/replace! events-box '())))) + (define filtered-events (if (null? events) events (prune-short-events events))) + ; get the messages from each component + (define messages + (apply append (for/list ([c components]) (receive-data c filtered-events)))) + ; send the messages; bail out if it fails + (define continue? (not (eq? sync-result 'finish))) + (with-handlers ([exn:fail? (lambda (e) (set! continue? #f))]) + (ws-send! conn (jsexpr->bytes messages))) + (if continue? + (loop) + (begin + (with-handlers ([exn:fail? void]) ; the connection might be dead, but we don't care + (ws-send! conn (jsexpr->bytes (list (stream-finish-message)))) + (ws-close! conn)) + ; if we weren't told to shut down, we need to wait until we are. + (unless (eq? sync-result 'finish) + (channel-get channel)) + (channel-put channel 'finish))))] + [else + (channel-put channel "another client is already connected") + (ws-close! conn)])) + + ; start the server + (define conf-channel (make-async-channel)) + (define server-shutdown! + (ws-serve #:confirmation-channel conf-channel + #:port 8048 + ws-connection)) + ; wait until it's started + (define the-port (async-channel-get conf-channel)) + (unless (number? the-port) + (raise the-port)) + + (values the-port server-shutdown! channel)) + + +;; Remove enter/exit events corresponding to calls that are too short to render. +;; This only removes enter/exit events that contain no intervening events, to +;; avoid pruning "interesting" calls. +(define (prune-short-events events [min% 0.001]) + ; determine the minimum time for an event to be included + (define (event->time evt) + (metrics-ref (profile-event-metrics evt) 'time)) + (define (dt enter exit) + (- (event->time exit) + (event->time enter))) + + ; find the first and last events that have timestamps + (define-values (first last) + (let loop ([first #f][last #f][events events]) + (cond + [(null? events) (values first last)] + [(false? first) + (if (false? (event->time (car events))) + (loop first last (cdr events)) + (loop (car events) (car events) (cdr events)))] + [else + (if (false? (event->time (car events))) + (loop first last (cdr events)) + (loop first (car events) (cdr events)))]))) + + (define MIN_TIME (if (= (filtering-threshold) 0) + 0 + (* (dt first last) min%))) + (define new-events '()) + + (for ([e events]) + (cond + [(and (profile-event-exit? e) + (not (null? new-events))) + (if (and (profile-event-enter? (car new-events)) + (< (dt (car new-events) e) MIN_TIME)) + (set! new-events (cdr new-events)) + (set! new-events (cons e new-events)))] + [else (set! new-events (cons e new-events))])) + + (reverse new-events)) + + +;; Initialize the template +(define (render-html-template source symlink?) + ; set up output directory + (define output-dir + (if symlink? + (build-path (current-directory) "profiles" (make-folder-name source)) + (build-path (find-system-path 'temp-dir) (make-folder-name source)))) + (make-directory* output-dir) + + ; link/copy the template files into the output directory + (define copy-or-symlink (if symlink? + make-file-or-directory-link + copy-directory/files)) + (let ([src (path->complete-path template-dir)]) + (for ([n (list "profile.html" "css" "js")]) + (copy-or-symlink (build-path src n) (build-path output-dir n)))) + + output-dir) + + +;; A special type of message that contains the metadata +(define (metadata-message source name) + (hash 'type "metadata" + 'name name + 'time (parameterize ([date-display-format 'iso-8601]) + (string-replace (date->string (current-date) #t) "T" " ")) + 'source (syntax-srcloc source) + 'form (if (syntax? source) (~a (syntax->datum source)) "") + 'version 1)) + +;; A special type of message that tells the client to connect to a websocker +(define (stream-start-message port) + (hash 'type "stream" + 'event "start" + 'url (format "ws://localhost:~v/" port))) + +;; A special type of message that tells the client to close its websocket +(define (stream-finish-message) + (hash 'type "stream" 'event "finish")) + + +;; Render a list of messages (which must be jsexpr?s) to the data file. +;; They're written in JSONP format, so will invoke the client's receiveData +;; callback when loaded. +(define (render-report-messages messages path) + (let ([out (open-output-file (build-path path "report_data.js"))]) + (fprintf out "data.receiveData(") + (write-json messages out) + (fprintf out ");\n") + (close-output-port out))) + + +;; Open the report in the system browser +(define (open-report-in-browser path) + (define profile-path (build-path path "profile.html")) + (printf "Wrote profile to: ~a\n" profile-path) + (unless (getenv "SYMPRONOOPEN") + (send-url/file profile-path))) + + +; Atomically replace the value in the box with a new value, +; and return the value that was previously there. +(define (unbox/replace! box new) + (define v (unbox box)) + (if (box-cas! box v new) v (unbox/replace! box new))) diff --git a/rosette/lib/profile/renderer/report/callgraph.rkt b/rosette/lib/profile/renderer/report/callgraph.rkt new file mode 100644 index 00000000..f82089c6 --- /dev/null +++ b/rosette/lib/profile/renderer/report/callgraph.rkt @@ -0,0 +1,49 @@ +#lang racket + +(require "generic.rkt" + "../../data.rkt" "../../reporter.rkt" "../../feature.rkt" "../../record.rkt" + "../syntax.rkt") +(provide make-callgraph-component) + +; The callgraph component simply passes through to the client all the events +; needed to construct the call graph. + +(define (make-callgraph-component options) + (callgraph-report-component)) + +(struct callgraph-report-component () #:transparent + #:methods gen:report-component + [(define (init-component self) + void) + (define (receive-data self events) + (list (hash 'type "callgraph" + 'events (events->jsexpr events))))]) + +(define (render-event? event) + (or (profile-event-enter? event) + (profile-event-exit? event) + (profile-event-sample? event))) + + +(define (render-event event) + (match event + [(profile-event-enter met id loc proc in) + (hash 'type "ENTER" + 'id id + 'function (procedure-name proc) + 'callsite (syntax-srcloc loc) + 'source (let ([src (hash-ref (current-sources) proc #f)]) (if src (syntax-srcloc src) src)) + 'inputs (features->flat-hash in) + 'metrics (metrics->hash met))] + [(profile-event-exit met out) + (hash 'type "EXIT" + 'outputs (features->flat-hash out) + 'metrics (metrics->hash met))] + [(profile-event-sample met) + (hash 'type "SAMPLE" + 'metrics (metrics->hash met))] + [_ (error 'render-event "unknown event ~v" event)])) + + +(define (events->jsexpr events) + (map render-event (filter render-event? events))) diff --git a/rosette/lib/profile/renderer/report/generic.rkt b/rosette/lib/profile/renderer/report/generic.rkt new file mode 100644 index 00000000..79d68248 --- /dev/null +++ b/rosette/lib/profile/renderer/report/generic.rkt @@ -0,0 +1,8 @@ +#lang racket + +(require racket/generic) +(provide (all-defined-out)) + +(define-generics report-component + (init-component report-component) + (receive-data report-component profile)) diff --git a/rosette/lib/profile/renderer/report/html/css/d3-stack-graph.css b/rosette/lib/profile/renderer/report/html/css/d3-stack-graph.css new file mode 100644 index 00000000..818c4fe5 --- /dev/null +++ b/rosette/lib/profile/renderer/report/html/css/d3-stack-graph.css @@ -0,0 +1,21 @@ +.stack-graph-rect { + stroke: #EEEEEE; +} + +.stack-graph-rect:hover { + stroke: #474747; + stroke-width: 0.8; + cursor: pointer; +} + +.stack-graph-label { + pointer-events: none; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + margin-left: 4px; + margin-right: 4px; + line-height: 1.5; + padding: 0 0 0; + text-align: left; +} \ No newline at end of file diff --git a/rosette/lib/profile/renderer/report/html/css/profile.css b/rosette/lib/profile/renderer/report/html/css/profile.css new file mode 100644 index 00000000..33259c50 --- /dev/null +++ b/rosette/lib/profile/renderer/report/html/css/profile.css @@ -0,0 +1,158 @@ +html { + font-family: Helvetica, Arial, sans-serif; +} + +#header { + padding: 0.5em 1em; + background: #002546; + color: #dddddd; +} +#profile-source { + color: #efefef; + font-weight: 600; +} +#progress { + float: right; + display: none; +} + +#content { + padding: 1em; +} + +.help { + display: inline-block; + background-color: #777777; + color: #efefef; + width: 1em; + height: 1em; + border-radius: 0.5em; + font-weight: normal; + cursor: default; + text-align: center; +} + +.ylabel { + font-size: 16px; +} + +.trendgraph-bar { + fill: #0F5492; +} + +.stack-graph-unzoom { + fill: #002546; +} +.stack-graph-unzoom + * .stack-graph-label { + color: #efefef !important; +} + +.stack-graph-label { + font-size: 12px; + font-family: Helvetica, Arial, sans-serif; + color: black; +} + +.stack-graph-node-highlight { + fill: #0F5492; +} +.stack-graph-node-highlight + * .stack-graph-label { + color: #efefef !important; +} + +#stackgraph { + margin-top: 0.5em; +} + +#calls { + margin-top: 1em; +} + +#calltable { + border-collapse: collapse; + margin-top: 0.5rem; +} + +#calltable th, +#calltable td { + padding: 0.25rem 0.15rem; +} + +#calltable-config { + text-align: left; + font-weight: normal; + font-size: 90%; +} +#calltable-config-more { + margin-top: 0.5rem; + display: none; +} +#calltable-config label + label, +#calltable-config-toggle-more { + margin-left: 1.5rem; +} +#calltable-config-toggle-more, +#calltable-config-toggle-more:active, +#calltable-config-toggle-more:hover, +#calltable-config-toggle-more:visited { + color: #333; +} +#calltable td { + text-align: right; +} +#calltable td.score, +#calltable td.name { + text-align: left; +} +.calltable-highlight, +#calltable tbody tr:hover { + background-color: #E3F2FF; +} +#calltable-score-header { + min-width: 8.3em; +} +#calltable .scorebar { + height: 0.5em; +} +#calltable .scorecell-bar { + width: 6em; + text-align: right; + display: inline-block; +} +#calltable .scorecell-score { + width: 2em; + text-align: right; + display: inline-block; + margin-right: 0.3em; +} +#calltable .score-checkbox { + display: none; +} + +.source, .numcalls, .signature { + font-size: 80%; + color: #aaaaaa; + margin-left: 0.4rem; +} + +ul.context-list { + margin: 0; + font-size: 80%; + padding-left: 1em; + list-style-type: none; + color: #777777; +} +ul.context-list li:before { + content: '↴'; + display: inline-block; + font-size: 100%; + transform: rotate(180deg) translateY(0.3em); + color: #777777; +} + +.tooltip { + background-color: #dddddd; + font-size: 90%; + padding: 0.5rem; + max-width: 22em; +} \ No newline at end of file diff --git a/rosette/lib/profile/renderer/report/html/css/spinner.gif b/rosette/lib/profile/renderer/report/html/css/spinner.gif new file mode 100644 index 0000000000000000000000000000000000000000..8fa139e8951d7c76ecbe2aba35c7fe97561ecf48 GIT binary patch literal 1533 zcmbu;X-ty`7zgmT?c4kAOQBXMZFvhts!_%*F1wUrKIZQBOGgQ+>?B8?FcD1rY`yuXPWh4zq4lC6l{wHt+aIg0lJ z#Nz=DAbK_S)tUg>JsRcV@Nu|_Ue);&;Z^DG`Lzeq+wb9@IdvN$XYq&@_-W72k8)hP z_Bmim{`q9uVz&0kH$@+86eV3#>UEcrjz`O{g)A^ux)S2D3@o=@ zI1o2B7Z7s7(yuU(O`=H;nwb(Oz3wd8*nZB9Xo-93`z5E(RZ=54PIx%k_yU*-Nuz?{ z#*Auo1IQ~!S)C2MV-_o}-mWh5Vg*9eHp+o#GONn1UDZ2yoxBixC_KKUGzp|EG#<-hnfsF@xPvflfSmKf+W?v;+ zRB$S>#CT)}6M-O{%(7*j@OY4JIgv>kqV*yu`Xv0&Fu{X9nJ>6PidNRgAVWg>V>T6o zwkmds-KO*x4r%yUmtCJtaX9~2?dS*OEwa%bWtKl3UZwFt1GWGT!!H+jlXsSK2~83t zCz%WxP%3@_tXC6VsVb$hJ`|b8P^3N200yh14F`+hOU}HOpCjfSnp}GXGW0- zXWANo+UoiFX@3K@3`JO=*#iBTc}Qc^03$^-nschur`kje+Y%GqnjhsED$Zq3BzPZ+ zb79#K7l_E;M~%D}i;bWOpQb>Zua-JbbLC^QZmpEoM?^}NK?)ye7C#=(l;4@1of*3A zS{`eh3G+NtG%cAPhvBN?Ef%X4WtS2x=bZN1CNt67UU8OB^TbpqHz$@GfZ@IiPHc0< zc!V!u;>sXwFx!&4?bj2lHW{HU`7qu^mnkXwe5zn%0~G@j6PFDHYx&wPf2pFl~Zy>#v+_2LR{sGa*VLMurt&wM~cX$WS;4Mxfd|C`cTS zI)e5EAul3_N5?KB`8|EU{TFe~k#*BVa`9omO?5MOa(_<1|LKj(#0#^F?v+LSom}Ew zzy2d%{i=M^@r|E;b&SKNbI1v#)V-U~_Rjs(1O-i~4VB^3PQj}`ahd(a4;j1Uib8wz zczoFY0jcgl&w%{i(vRphfRhz@n>S6-sEkj3chhvq-)SJiBYK5}Y&v{g+E`M<0>SM? z`Fsqu8>AKEigmeA4^>xa)z<`fv$PtCED^$xMb^@IOU=&8h}Vi}Jiy$l*t6(1>0iGz z1IP$!rx^Dnm#bNXulekw;()9Ld7l*;FbLx%IU;tDywSyB&qV(47F&7nIZy>KF+v2_ zrYcG2AUx5vQf1zS_RhKkkOy|jO-JiAoefo8FrCh?4a%+_AIGE6=+oH;9n*Ad1eF@$ j9aSL4VFCCF6@x!O8;*)nudIA|xsMsn 0) { + var n = nodes.pop(); + for (var _i = 0, _a = options.columns; _i < _a.length; _i++) { + var c = _a[_i]; + if (n.hasOwnProperty(c.type)) { + var k = c.type + ":" + c.column; + maxValues[k] = Math.max(maxValues[k] || 0, n[c.type][c.column] || 0); + } + } + for (var _b = 0, _c = n.children; _b < _c.length; _b++) { + var c = _c[_b]; + nodes.push(c); + } + } + // second pass: compute scores for each node + nodes.push(root); + var maxScore = 0; + while (nodes.length > 0) { + var n = nodes.pop(); + var score = 0.0; + for (var _d = 0, _e = options.columns; _d < _e.length; _d++) { + var c = _e[_d]; + if (c.score && n.hasOwnProperty(c.type)) { + var k = c.type + ":" + c.column; + if (maxValues[k] > 0) { + score += (n[c.type][c.column] || 0) / maxValues[k]; + } + } + } + n.score = score; + if (score > maxScore) { + maxScore = score; + } + for (var _f = 0, _g = n.children; _f < _g.length; _f++) { + var c = _g[_f]; + nodes.push(c); + } + } + return maxScore; + } + // compute scores for aggregated table rows + function updateScoresForRows(rows) { + if (rows.length == 0) + return rows; + // first pass: compute denominators + var maxValues = []; + for (var _i = 0, rows_1 = rows; _i < rows_1.length; _i++) { + var r = rows_1[_i]; + for (var i = 0; i < r.columns.length; i++) { + if (r.columns[i] >= (maxValues[i] || 0)) { + maxValues[i] = r.columns[i]; + } + } + } + // second pass: compute scores for each row + for (var _a = 0, rows_2 = rows; _a < rows_2.length; _a++) { + var r = rows_2[_a]; + var score = 0.0; + for (var i = 0; i < r.columns.length; i++) { + if (options.columns[i].score && maxValues[i] > 0) { + score += r.columns[i] / maxValues[i]; + } + } + r.score = score; + } + } + // get the key used for aggregating nodes together according to context + function getAggregateKeyForNode(node) { + var context = options.contextDepth; + var key = node.name + "(" + node.source + ")"; + while (context-- != 0 && node.parent) { + node = node.parent; + key = key + "\\" + node.name + "(" + node.source + ")"; + } + if (options.signatures) { + key = (node.inputs["signature"] || []).join("->") + "|" + + (node.outputs["signature"] || []).join("->") + "|" + + key; + } + return key; + } + // compute the analysis rows by aggregating and scoring them + function computeTableRows(root) { + if (root === null) + return []; + if (options.aggregate) { + // group rows by the aggregate key (wrt context) + var nodes = [root]; + var ctxs = {}; + while (nodes.length > 0) { + var n = nodes.pop(); + if (n) { + var k = getAggregateKeyForNode(n); + if (!ctxs.hasOwnProperty(k)) + ctxs[k] = []; + ctxs[k].push(n); + for (var _i = 0, _a = n.children; _i < _a.length; _i++) { + var c = _a[_i]; + nodes.push(c); + } + } + } + // create row for each node + var allRows = []; + for (var k in ctxs) { + var rows = ctxs[k]; + if (rows.length > 0) { + var first = rows[0]; + // compute the row's data as the total within + var maxScore = 0.0; + var totalValues = {}; + for (var _b = 0, rows_3 = rows; _b < rows_3.length; _b++) { + var n = rows_3[_b]; + for (var _c = 0, _d = options.columns; _c < _d.length; _c++) { + var c = _d[_c]; + if (n.hasOwnProperty(c.type)) { + var k_1 = c.type + ":" + c.column; + totalValues[k_1] = (totalValues[k_1] || 0) + (n[c.type][c.column] || 0); + } + } + maxScore = Math.max(maxScore, n.score); + } + var columns = []; + for (var _e = 0, _f = options.columns; _e < _f.length; _e++) { + var k_2 = _f[_e]; + columns.push(totalValues[k_2.type + ":" + k_2.column]); + } + var row = { + function: first.name, + node: first, + allNodes: rows, + score: maxScore, + columns: columns + }; + allRows.push(row); + } + } + updateScoresForRows(allRows); // this should really be an option + return allRows; + } + else { + // create a row for each call + var nodes = [root]; + var rows = []; + while (nodes.length > 0) { + var n = nodes.pop(); + if (!n) + continue; + var values = {}; + for (var _g = 0, _h = options.columns; _g < _h.length; _g++) { + var c = _h[_g]; + if (n.hasOwnProperty(c.type)) { + var k = c.type + ":" + c.column; + values[k] = n[c.type][c.column] || 0; + } + } + var columns = []; + for (var _j = 0, _k = options.columns; _j < _k.length; _j++) { + var k = _k[_j]; + columns.push(values[k.type + ":" + k.column]); + } + var row = { + function: n.name, + node: n, + allNodes: [n], + score: n.score, + columns: columns + }; + rows.push(row); + for (var _l = 0, _m = n.children; _l < _m.length; _l++) { + var c = _m[_l]; + nodes.push(c); + } + } + return rows; + } + } + function collapseRosetteCalls(root) { + var rec = function (node) { + var newExcl = undefined; + var newChildren = []; + var modified = false; + for (var _i = 0, _a = node.children; _i < _a.length; _i++) { + var c = _a[_i]; + var newC = rec(c); // recurse to collapse children + if (newC.name[0] == "@") { + if (typeof newExcl === "undefined") { + newExcl = {}; + for (var _b = 0, _c = Object.keys(node.excl); _b < _c.length; _b++) { + var k = _c[_b]; + newExcl[k] = node.excl[k]; + } + } + for (var _d = 0, _e = Object.keys(newC.excl); _d < _e.length; _d++) { + var k = _e[_d]; + newExcl[k] = (newExcl[k] || 0) + newC.excl[k]; // add all c's children + } + for (var _f = 0, _g = newC.children; _f < _g.length; _f++) { + var cc = _g[_f]; + newChildren.push(cc); + } + modified = true; + } + else { + if (newC !== c) { + modified = true; + } + newChildren.push(newC); // recurse + } + } + if (modified) { + var newNode = {}; + for (var _h = 0, _j = Object.keys(node); _h < _j.length; _h++) { + var k = _j[_h]; + newNode[k] = node[k]; + } + if (typeof newExcl !== "undefined") { + newNode.excl = newExcl; + } + newNode.children = newChildren; + return newNode; + } + else { + return node; + } + }; + var newRoot = rec(root); + return newRoot; + } + // remove all solver time from exclusive time + function collapseSolverTime(root, solverCalls) { + var rec = function (node) { + var exclDt = 0; + var newChildren = []; + var modified = false; + var start = node.start; + // do the spaces before each child + for (var _i = 0, _a = node.children; _i < _a.length; _i++) { + var c = _a[_i]; + var finish_1 = c.start; + for (var _b = 0, solverCalls_1 = solverCalls; _b < solverCalls_1.length; _b++) { + var sc = solverCalls_1[_b]; + var scfinish = typeof sc.finish === "undefined" ? root.finish : sc.finish; + if (scfinish < start) + continue; + if (finish_1 < sc.start) + break; // todo make not quadratic + var delta = Math.min(finish_1, scfinish) - Math.max(start, sc.start); + exclDt += delta; + } + var ret = rec(c); + newChildren.push(ret); + if (ret !== c) + modified = true; + start = c.finish; + } + // do the space between last child and my end + var finish = node.finish; + for (var _c = 0, solverCalls_2 = solverCalls; _c < solverCalls_2.length; _c++) { + var sc = solverCalls_2[_c]; + var scfinish = typeof sc.finish === "undefined" ? root.finish : sc.finish; + if (scfinish < start) + continue; + if (finish < sc.start) + break; + var delta = Math.min(finish, scfinish) - Math.max(start, sc.start); + exclDt += delta; + } + if (exclDt > 0 || modified) { + var newNode = {}; + for (var _d = 0, _e = Object.keys(node); _d < _e.length; _d++) { + var k = _e[_d]; + newNode[k] = node[k]; + } + var newExcl = {}; + for (var _f = 0, _g = Object.keys(node.excl); _f < _g.length; _f++) { + var k = _g[_f]; + newExcl[k] = node.excl[k]; + } + newNode.excl = newExcl; + newNode.excl["time"] -= exclDt; + newNode.children = newChildren; + node = newNode; + } + return node; + }; + return rec(root); + } +})(analysis || (analysis = {})); +//# sourceMappingURL=analysis.js.map \ No newline at end of file diff --git a/rosette/lib/profile/renderer/report/html/js/analysis.ts b/rosette/lib/profile/renderer/report/html/js/analysis.ts new file mode 100644 index 00000000..db8b56fb --- /dev/null +++ b/rosette/lib/profile/renderer/report/html/js/analysis.ts @@ -0,0 +1,406 @@ +namespace analysis { + import CallNode = data.CallNode; + import SolverCall = data.SolverCall; + + type AnalysisCallback = (d: ProfileData) => void; + type ZoomCallback = (d: CallNode) => void; + + export interface ProfileData { + root: CallNode; + solverCalls: SolverCall[]; + rows: AnalysisRow[]; + currentZoom: CallNode; + aggregate: boolean; + maxScore: number; + } + + export interface AnalysisRow { + function: string; + node: CallNode; + allNodes: CallNode[]; + score: number; + columns: number[]; + } + + export interface AnalysisColumn { + type: string; + column: string; + name: string; + score: boolean; + description?: string; + } + + var options = { + aggregate: false, + signatures: false, + collapseRosette: false, + collapseSolver: false, + columns: [], + contextDepth: 0, + histogramBins: 100 + }; + + var analysisCallbacks: AnalysisCallback[] = []; + var zoomCallbacks: ZoomCallback[] = []; + var columnDenominators: Map = {}; + var currentState: data.ProfileState = null; + var currentZoom: CallNode = null; + + // init + export function init() { + data.registerUpdateCallback(receiveDataCallback); + } + + export function registerAnalysisCallback(cb: AnalysisCallback): void { + analysisCallbacks.push(cb); + } + + export function registerZoomCallback(cb: ZoomCallback): void { + zoomCallbacks.push(cb); + } + + export function setAggregate(x: boolean): void { + options.aggregate = x; + refresh(); + } + export function setColumns(x: AnalysisColumn[]): void { + options.columns = x; + refresh(); + } + export function setContextDepth(x: number): void { + options.contextDepth = x; + refresh(); + } + export function setHistogramBins(x: number): void { + options.histogramBins = x; + refresh(); + } + export function setSignatures(x: boolean): void { + options.signatures = x; + refresh(); + } + export function setCollapseRosette(x: boolean): void { + options.collapseRosette = x; + refresh(); + } + export function setCollapseSolver(x: boolean): void { + options.collapseSolver = x; + refresh(); + } + + export function refresh(): void { + if (currentState) receiveDataCallback(currentState); + } + + function receiveDataCallback(d: data.ProfileState): void { + // save the state for reuse when options are changed + currentState = d; + + // which data to actually use + let root: CallNode = d.root; + if (options.collapseRosette) { + root = collapseRosetteCalls(root); + } + if (options.collapseSolver) { + root = collapseSolverTime(root, d.solverCalls); + } + + // compute scores for the data + let maxScore = computeScores(root); + + // compute rows for the table + let rows = computeTableRows(root); + + if (!currentZoom) { + currentZoom = root; + } + + let a: ProfileData = { + root: root, + solverCalls: d.solverCalls, + rows: rows, + currentZoom: currentZoom, + aggregate: options.aggregate, + maxScore: maxScore + }; + + for (let cb of analysisCallbacks) { + cb(a); + } + } + + + export function zoomTo(root: CallNode) { + currentZoom = root; + for (let cb of zoomCallbacks) { + cb(currentZoom); + } + } + + + function computeScores(root: CallNode): number { + if (root === null) return 0.0; + + // first pass: compute denominators + let maxValues: Map = {}; + let nodes = [root]; + while (nodes.length > 0) { + let n = nodes.pop()!; + for (let c of options.columns) { + if (n.hasOwnProperty(c.type)) { + let k = c.type + ":" + c.column; + maxValues[k] = Math.max(maxValues[k] || 0, n[c.type][c.column] || 0); + } + } + for (let c of n.children) nodes.push(c); + } + // second pass: compute scores for each node + nodes.push(root); + let maxScore = 0; + while (nodes.length > 0) { + let n = nodes.pop()!; + let score = 0.0; + for (let c of options.columns) { + if (c.score && n.hasOwnProperty(c.type)) { + let k = c.type + ":" + c.column; + if (maxValues[k] > 0) { + score += (n[c.type][c.column] || 0) / maxValues[k]; + } + } + } + n.score = score; + if (score > maxScore) { + maxScore = score; + } + for (let c of n.children) nodes.push(c); + } + return maxScore; + } + + + // compute scores for aggregated table rows + function updateScoresForRows(rows: AnalysisRow[]) { + if (rows.length == 0) return rows; + + // first pass: compute denominators + let maxValues: number[] = []; + for (let r of rows) { + for (let i = 0; i < r.columns.length; i++) { + if (r.columns[i] >= (maxValues[i] || 0)) { + maxValues[i] = r.columns[i]; + } + } + } + + // second pass: compute scores for each row + for (let r of rows) { + let score = 0.0; + for (let i = 0; i < r.columns.length; i++) { + if (options.columns[i].score && maxValues[i] > 0) { + score += r.columns[i] / maxValues[i]; + } + } + r.score = score; + } + } + + + // get the key used for aggregating nodes together according to context + function getAggregateKeyForNode(node: CallNode): string { + var context = options.contextDepth; + var key = node.name + "(" + node.source + ")"; + while (context-- != 0 && node.parent) { + node = node.parent; + key = key + "\\" + node.name + "(" + node.source + ")"; + } + if (options.signatures) { + key = (node.inputs["signature"] || []).join("->") + "|" + + (node.outputs["signature"] || []).join("->") + "|" + + key; + } + return key; + } + + + // compute the analysis rows by aggregating and scoring them + function computeTableRows(root: CallNode): AnalysisRow[] { + if (root === null) return []; + + if (options.aggregate) { + // group rows by the aggregate key (wrt context) + let nodes = [root]; + let ctxs: Map = {}; + while (nodes.length > 0) { + let n = nodes.pop()!; + if (n) { + let k = getAggregateKeyForNode(n); + if (!ctxs.hasOwnProperty(k)) ctxs[k] = []; + ctxs[k].push(n); + for (let c of n.children) nodes.push(c); + } + } + // create row for each node + let allRows: AnalysisRow[] = []; + for (let k in ctxs) { + let rows = ctxs[k]; + if (rows.length > 0) { + let first = rows[0]!; + // compute the row's data as the total within + let maxScore = 0.0; + let totalValues: Map = {}; + for (let n of rows) { + for (let c of options.columns) { + if (n.hasOwnProperty(c.type)) { + let k = c.type + ":" + c.column; + totalValues[k] = (totalValues[k] || 0) + (n[c.type][c.column] || 0); + } + } + maxScore = Math.max(maxScore, n.score); + } + let columns = []; + for (let k of options.columns) { + columns.push(totalValues[k.type + ":" + k.column]); + } + let row: AnalysisRow = { + function: first.name, + node: first, + allNodes: rows, + score: maxScore, + columns: columns + }; + allRows.push(row); + } + } + updateScoresForRows(allRows); // this should really be an option + return allRows; + } else { + // create a row for each call + let nodes = [root]; + let rows: AnalysisRow[] = []; + while (nodes.length > 0) { + let n = nodes.pop()!; + if (!n) continue; + let values: Map = {}; + for (let c of options.columns) { + if (n.hasOwnProperty(c.type)) { + let k = c.type + ":" + c.column; + values[k] = n[c.type][c.column] || 0; + } + } + let columns = []; + for (let k of options.columns) { + columns.push(values[k.type + ":" + k.column]); + } + let row: AnalysisRow = { + function: n.name, + node: n, + allNodes: [n], + score: n.score, + columns: columns + }; + rows.push(row); + for (let c of n.children) nodes.push(c); + } + return rows; + } + } + + + function collapseRosetteCalls(root: CallNode): CallNode { + let rec = (node: CallNode): CallNode => { + let newExcl = undefined; + let newChildren = []; + let modified = false; + for (let c of node.children) { + let newC = rec(c); // recurse to collapse children + if (newC.name[0] == "@") { + if (typeof newExcl === "undefined") { + newExcl = {}; + for (let k of Object.keys(node.excl)) { + newExcl[k] = node.excl[k]; + } + } + for (let k of Object.keys(newC.excl)) { + newExcl[k] = (newExcl[k] || 0) + newC.excl[k]; // add all c's children + } + for (let cc of newC.children) { + newChildren.push(cc); + } + modified = true; + } else { + if (newC !== c) { + modified = true; + } + newChildren.push(newC); // recurse + } + } + if (modified) { + let newNode = {}; + for (let k of Object.keys(node)) { + newNode[k] = node[k]; + } + if (typeof newExcl !== "undefined") { + (newNode).excl = newExcl; + } + (newNode).children = newChildren; + return newNode; + } else { + return node; + } + } + let newRoot = rec(root); + return newRoot; + } + + + // remove all solver time from exclusive time + function collapseSolverTime(root: CallNode, solverCalls: SolverCall[]): CallNode { + let rec = (node: CallNode) => { + let exclDt = 0; + let newChildren = []; + let modified = false; + let start = node.start; + // do the spaces before each child + for (let c of node.children) { + let finish = c.start; + for (let sc of solverCalls) { + let scfinish = typeof sc.finish === "undefined" ? root.finish : sc.finish; + if (scfinish < start) continue; + if (finish < sc.start) break; // todo make not quadratic + let delta = Math.min(finish, scfinish) - Math.max(start, sc.start); + exclDt += delta; + } + let ret = rec(c); + newChildren.push(ret); + if (ret !== c) modified = true; + start = c.finish; + } + // do the space between last child and my end + let finish = node.finish; + for (let sc of solverCalls) { + let scfinish = typeof sc.finish === "undefined" ? root.finish : sc.finish; + if (scfinish < start) continue; + if (finish < sc.start) break; + let delta = Math.min(finish, scfinish) - Math.max(start, sc.start); + exclDt += delta; + } + if (exclDt > 0 || modified) { + let newNode = {}; + for (let k of Object.keys(node)) { + newNode[k] = node[k]; + } + let newExcl = {}; + for (let k of Object.keys(node.excl)) { + newExcl[k] = node.excl[k]; + } + (newNode).excl = newExcl; + (newNode).excl["time"] -= exclDt; + (newNode).children = newChildren; + node = (newNode); + } + return node; + } + + return rec(root); + } +} \ No newline at end of file diff --git a/rosette/lib/profile/renderer/report/html/js/d3-stack-graph.js b/rosette/lib/profile/renderer/report/html/js/d3-stack-graph.js new file mode 100644 index 00000000..273b6056 --- /dev/null +++ b/rosette/lib/profile/renderer/report/html/js/d3-stack-graph.js @@ -0,0 +1,395 @@ +var d3_stackgraph; +(function (d3_stackgraph) { + var PADDING_X_LEFT = 30; + var PADDING_X_RIGHT = 20; + var PADDING_Y_BOTTOM = 18; + var X_AXIS_TICKS = 10; + var MIN_WIDTH = 0.5; // px + var TOOLTIP_KEY = "stackGraphTooltip"; + var StackGraphNode = /** @class */ (function () { + function StackGraphNode(data) { + this.start = 0; + this.finish = 1; + this.depth = 0; // root has depth 0 + this.data = data; + this.children = []; + } + StackGraphNode.prototype.descendants = function () { + var nodes = [this]; + var descs = []; + while (nodes.length > 0) { + var node = nodes.pop(); + descs.push(node); + for (var _i = 0, _a = node.children; _i < _a.length; _i++) { + var c = _a[_i]; + nodes.push(c); + } + } + return descs; + }; + return StackGraphNode; + }()); + var unzoomData = { + start: 0, finish: 0, parent: null, children: [], name: "", id: "unzoom" + }; + var StackGraph = /** @class */ (function () { + function StackGraph(s) { + this._width = 960; + this._cellHeight = 18; + this._transitionDuration = 750; + this._transitionEase = d3.easeCubic; + this._data = null; + this._highlights = []; + this._color = function (d) { return "#00ff00"; }; + this._textColor = function (d) { return "#000000"; }; + this._discontinuities = []; + this.zoomed = false; + this.nextId = 0; + this.idToNode = {}; + this.mouseoverCallback = null; + this.mouseoutCallback = null; + this.selector = s; + } + StackGraph.prototype.width = function (x) { + if (typeof x === "undefined") + return this._width; + this._width = x; + // this.render(true); + return this; + }; + StackGraph.prototype.height = function (x) { + if (typeof x === "undefined") + return this._height; + this._height = x; + // this.render(true); + return this; + }; + StackGraph.prototype.cellHeight = function (x) { + if (typeof x === "undefined") + return this._cellHeight; + this._cellHeight = x; + // this.render(true); + return this; + }; + StackGraph.prototype.clickHandler = function (x) { + if (typeof x === "undefined") + return this._clickHandler; + this._clickHandler = x; + return this; + }; + StackGraph.prototype.hoverHandler = function (x) { + if (typeof x === "undefined") + return this._hoverHandler; + this._hoverHandler = x; + return this; + }; + StackGraph.prototype.data = function (x) { + if (typeof x === "undefined") + return this._data; + this._data = x; + if (!this.zoomed) { + this.root = this.partition(x); + } + return this; + }; + StackGraph.prototype.highlights = function (x) { + if (typeof x === "undefined") + return this._highlights; + this._highlights = x; + return this; + }; + StackGraph.prototype.color = function (x) { + if (typeof x === "undefined") + return this._color; + this._color = x; + return this; + }; + StackGraph.prototype.textColor = function (x) { + if (typeof x === "undefined") + return this._textColor; + this._textColor = x; + return this; + }; + StackGraph.prototype.discontinuities = function (x) { + if (typeof x === "undefined") + return this._discontinuities; + this._discontinuities = x; + return this; + }; + StackGraph.prototype.clickCallback = function (d) { + if (!this._clickHandler) + return; + if (d.data === unzoomData) { + this._clickHandler(this._data); + } + else if (d.data !== this._data) { + this._clickHandler(d.data); + } + }; + StackGraph.prototype.zoom = function (d) { + if (d === this._data) { + this.zoomed = false; + this.root = this.partition(this._data); + this.render(true); + } + else { + this.zoomed = true; + this.root = this.partition(d); + this.render(true); + } + }; + StackGraph.prototype.highlightData = function (x) { + var _this = this; + // map the data to their corresponding nodes; remove those that don't exist + var nodes = x.map(function (d) { return _this.idToNode[d.id]; }).filter(function (d) { return d; }); + var g = this.svg.selectAll("g.stack-graph-node").data(nodes, function (d) { return d.data.id; }); + // highlight selected nodes + g.select("rect").classed("stack-graph-node-highlight", true); + // unhighlight all other nodes + g.exit().select("rect").classed("stack-graph-node-highlight", false); + }; + StackGraph.prototype.partition = function (rootData) { + this.idToNode = {}; + // first pass: build and layout the hierarchy + var root = new StackGraphNode(rootData); + root.start = rootData.start; + root.finish = rootData.finish; + this.idToNode[rootData.id] = root; + var nodes = [root]; + var maxHeight = 0; + while (nodes.length > 0) { + var node = nodes.pop(); + if (!node.data.id) { + node.data.id = this.nextId.toString(); + this.nextId += 1; + } + for (var _i = 0, _a = node.data.children; _i < _a.length; _i++) { + var c = _a[_i]; + var cn = new StackGraphNode(c); + cn.parent = node; + cn.depth = node.depth + 1; + cn.start = c.start; + cn.finish = c.finish; + if (cn.depth > maxHeight) + maxHeight = cn.depth; + this.idToNode[cn.data.id] = cn; + node.children.push(cn); + nodes.push(cn); + } + } + // finally set height if it's not default + var computedHeight = Math.max(maxHeight + 1, 5) * this._cellHeight + PADDING_Y_BOTTOM; + if (!this._height || (rootData === this._data && this._height > computedHeight)) { + this._height = computedHeight; + } + return root; + }; + StackGraph.prototype.setupSvg = function () { + this.svg = d3.select(this.selector).append('svg').attr("class", "stack-graph"); + this.svg.append("g").attr("class", "stack-graph-highlights"); + this.svg.append("g").attr("class", "stack-graph-body"); + var labels = this.svg.append("g").attr("class", "stack-graph-labels"); + labels.append("rect").attr("class", "stack-graph-label-bg").attr("fill", "#ffffff"); + labels.append("text").attr("text-anchor", "middle") + .attr("transform", "rotate(270)") + .text("Call Stack"); + var axes = this.svg.append("g").attr("class", "stack-graph-axis"); + axes.append("rect").attr("class", "stack-graph-axis-bg").attr("fill", "#ffffff"); + this.svg.append("g").attr("class", "stack-graph-breaks"); + var this_ = this; + var hasClass = function (elt, id) { + if (elt.classList) + return elt.classList.contains(id); + else if (elt.className.baseVal) + return elt.className.baseVal.indexOf(id) > -1; + else + return elt.className.indexOf(id) > -1; + }; + var mouseOpCallback = function (mouseover) { + return function (d, i, x) { + var elt = this; + if (this_._hoverHandler) { + if (hasClass(elt, "stack-graph-highlight") || hasClass(elt, "stack-graph-break")) { + if (mouseover) + tooltip.show(d.summary, elt, "top"); + else + tooltip.hide(); + } + else if (hasClass(elt, "stack-graph-node")) { + this_._hoverHandler(d.data, mouseover); + } + } + }; + }; + this.mouseoverCallback = mouseOpCallback(true); + this.mouseoutCallback = mouseOpCallback(false); + }; + StackGraph.prototype.render = function (anim) { + var _this = this; + if (anim === void 0) { anim = false; } + // set up SVG + if (!this.svg) { + this.setupSvg(); + } + this.svg.attr('width', this._width) + .attr('height', this._height); + var data = this.root ? this.root.descendants() : []; + if (this.zoomed) { + var curr = this.root.data, route = []; + while (curr = curr.parent) + route.push(curr.name); + unzoomData.name = " " + route.join(" ← "); + var unzoomNode = new StackGraphNode(unzoomData); + unzoomNode.start = this.root.start; + unzoomNode.finish = this.root.finish; + unzoomNode.depth = -1; + data.splice(0, 0, unzoomNode); + } + /////////////////////////////////////////////////////////////////// + // update y label position + var gLabel = this.svg.select("g.stack-graph-labels"); + gLabel.select("rect").attr("x", 0) + .attr("y", 0) + .attr("width", PADDING_X_LEFT) + .attr("height", this._height); + gLabel.select("text").attr("dy", PADDING_X_LEFT / 2) + .attr("dx", -this._height / 2); + /////////////////////////////////////////////////////////////////// + // create axes + var xBase = d3.scaleLinear().range([PADDING_X_LEFT, this._width - PADDING_X_RIGHT]); + if (this.root) { + xBase.domain([this.root.start, this.root.finish]); + } + var _a = xBase.domain(), xMin = _a[0], xMax = _a[1]; + var xBaseTo01 = d3.scaleLinear().domain([xMin, xMax]); + var x = fc.scaleDiscontinuous(xBase); + var dcRange = fc.discontinuityRange.apply(fc, this._discontinuities); + x.discontinuityProvider(dcRange); + var xTo01 = fc.scaleDiscontinuous(xBaseTo01); + xTo01.discontinuityProvider(dcRange); + var ticks = d3.ticks(0, 1, X_AXIS_TICKS); + var tickValues = ticks.map(xTo01.invert); + var y = d3.scaleLinear().range([0, this._cellHeight]); + var xAxis = d3.axisBottom(x).tickValues(tickValues); + xAxis.tickFormat(function (x) { return (x / 1000).toFixed(3) + "s"; }); + var xAxisPos = this._height - PADDING_Y_BOTTOM; + this.svg.select("g.stack-graph-axis") + .attr("transform", "translate(0," + xAxisPos + ")") + .call(xAxis); + this.svg.select("rect.stack-graph-axis-bg") + .attr("x", 0).attr("y", 0).attr("width", this._width).attr("height", PADDING_Y_BOTTOM); + var width = function (d) { return x(d.finish) - x(d.start); }; + /////////////////////////////////////////////////////////////////// + // render highlights + var gHigh = this.svg.select("g.stack-graph-highlights"); + var hRect = gHigh.selectAll("rect").data(this._highlights.filter(function (d) { return width(d) > 0; })); + var hEnter = hRect.enter().append("svg:rect").attr("class", "stack-graph-highlight"); + hEnter.on("mouseover", this.mouseoverCallback) + .on("mouseout", this.mouseoutCallback); + hRect.merge(hEnter) + .attr("height", this._height) + .attr("fill", function (d) { return d.color; }) + .attr("title", function (d) { return d.summary; }); + // animate the highlights if requested + var hPos = function (grp) { return grp.attr("width", width) + .attr("transform", function (d) { return "translate(" + x(d.start) + ",0)"; }); }; + if (anim) { + hPos(hRect.transition() + .duration(this._transitionDuration).ease(this._transitionEase)); + } + else { + hPos(hRect); + } + hPos(hEnter); + hRect.exit().remove(); + /////////////////////////////////////////////////////////////////// + // render breaks + var gBreak = this.svg.select("g.stack-graph-breaks"); + var bLine = gBreak.selectAll("line").data(this._highlights.filter(function (d) { return width(d) == 0; })); + var bEnter = bLine.enter().append("svg:line").attr("class", "stack-graph-break"); + bEnter.on("mouseover", this.mouseoverCallback) + .on("mouseout", this.mouseoutCallback); + bLine.merge(bEnter) + .attr("y1", this._height - PADDING_Y_BOTTOM + xAxis.tickSizeInner()) + .attr("y2", 0) + .attr("x1", 0) + .attr("x2", 0) + .attr("stroke-width", 1) + .attr("stroke", "#444444") + .style("stroke-dasharray", "5 5"); + // animate the breaks if requested + var bPos = function (grp) { return grp.attr("transform", function (d) { return "translate(" + x(d.start) + ",0)"; }); }; + if (anim) { + bPos(bLine.transition() + .duration(this._transitionDuration).ease(this._transitionEase)); + } + else { + bPos(bLine); + } + bPos(bEnter); + bLine.exit().remove(); + /////////////////////////////////////////////////////////////////// + // render call stacks + var dx = function (d) { return x(d.start); }; + var dy = function (d) { return _this._height - y(d.depth + (_this.zoomed ? 1 : 0)) - _this._cellHeight - PADDING_Y_BOTTOM; }; + var pos = function (d) { return "translate(" + dx(d) + "," + dy(d) + ")"; }; + var rectClass = function (d) { return d.data === unzoomData ? "stack-graph-rect stack-graph-unzoom" : "stack-graph-rect"; }; + var filteredData = data.filter(function (d) { return width(d) >= MIN_WIDTH; }); + var g = this.svg.select("g.stack-graph-body") + .selectAll("g.stack-graph-node").data(filteredData, function (d) { return d.data.id; }); + var enter = g.enter().append("svg:g"); + enter.append("svg:rect"); + enter.append("foreignObject") + .append("xhtml:div"); + enter.on("click", function (d) { return _this.clickCallback(d); }) + .on("mouseover", this.mouseoverCallback) + .on("mouseout", this.mouseoutCallback); + g.merge(enter) + .attr("height", this._cellHeight) + .attr("name", function (d) { return d.data.name; }) + .attr("class", "stack-graph-node") + .select("rect") + .attr("height", this._cellHeight) + .attr("class", rectClass) + .attr("fill", function (d) { return _this._color(d.data); }); + g.merge(enter) + .select("foreignObject") + .attr("width", width) + .attr("height", this._cellHeight) + .select("div") + .attr("class", "stack-graph-label") + .style("display", function (d) { return width(d) < 35 ? "none" : "block"; }) + .style("color", function (d) { return _this._textColor(d.data); }) + .text(function (d) { return d.data.name; }); + // animate preexisting nodes into new positions + var gPos = function (grp) { return grp.attr("transform", pos) + .attr("width", width) + .select("rect") + .attr("width", width); }; + if (anim) { + gPos(g.transition() + .duration(this._transitionDuration).ease(this._transitionEase)); + } + else { + gPos(g); + } + // don't animate new nodes into position... + gPos(enter); + // but maybe fade them in if this isn't the first render (i.e., if unzooming) + if (anim) { + enter.style("opacity", 0.0) + .transition() + .duration(this._transitionDuration).ease(this._transitionEase) + .style("opacity", 1.0); + } + g.exit().remove(); + }; + return StackGraph; + }()); + d3_stackgraph.StackGraph = StackGraph; + function stackGraph(selector) { + return new StackGraph(selector); + } + d3_stackgraph.stackGraph = stackGraph; +})(d3_stackgraph || (d3_stackgraph = {})); +//# sourceMappingURL=d3-stack-graph.js.map \ No newline at end of file diff --git a/rosette/lib/profile/renderer/report/html/js/d3-stack-graph.ts b/rosette/lib/profile/renderer/report/html/js/d3-stack-graph.ts new file mode 100644 index 00000000..bce06859 --- /dev/null +++ b/rosette/lib/profile/renderer/report/html/js/d3-stack-graph.ts @@ -0,0 +1,460 @@ +declare var d3; +declare var fc; + +namespace d3_stackgraph { + type ClickHandler = (d: StackGraphData) => any; + type HoverHandler = (d: StackGraphData, enter: boolean) => any; + type ColorHandler = (d: StackGraphData) => string; + type D3Selection = any; + + const PADDING_X_LEFT = 30; + const PADDING_X_RIGHT = 20; + const PADDING_Y_BOTTOM = 18; + const X_AXIS_TICKS = 10; + const MIN_WIDTH = 0.5; // px + + const TOOLTIP_KEY = "stackGraphTooltip"; + + class StackGraphNode { + data: StackGraphData; + start: number = 0; + finish: number = 1; + parent: StackGraphNode; + children: StackGraphNode[]; + depth: number = 0; // root has depth 0 + + constructor(data: StackGraphData) { + this.data = data; + this.children = []; + } + + descendants(): StackGraphNode[] { + let nodes: StackGraphNode[] = [this]; + let descs = []; + while (nodes.length > 0) { + let node = nodes.pop(); + descs.push(node); + for (let c of node.children) nodes.push(c); + } + return descs; + } + } + + export interface StackGraphData { + start: number; + finish: number; + parent: StackGraphData; + children: StackGraphData[]; + name: string; + id: string; + } + + let unzoomData: StackGraphData = { + start: 0, finish: 0, parent: null, children: [], name: "", id: "unzoom" + }; + + export interface StackGraphHighlight { + start: number; + finish: number; + color: string; + summary: string; + } + + export class StackGraph { + private _width: number = 960; + private _height: number; + private _cellHeight: number = 18; + private _selection: D3Selection; + private _transitionDuration: number = 750; + private _transitionEase: any = d3.easeCubic; + private _clickHandler: ClickHandler; + private _hoverHandler: HoverHandler; + private _data: StackGraphData = null; + private _highlights: StackGraphHighlight[] = []; + private _color: ColorHandler = (d) => "#00ff00"; + private _textColor: ColorHandler = (d) => "#000000"; + private _discontinuities: number[][] = []; + + private root: StackGraphNode; + private svg: any; + private selector: string; + private zoomed: boolean = false; + private nextId: number = 0; + private idToNode: Map = {}; + private mouseoverCallback: (d: Object, i: number, x: Object[]) => void = null; + private mouseoutCallback: (d: Object, i: number, x: Object[]) => void = null; + + width(): number + width(x: number): StackGraph + width(x?: number): number | StackGraph { + if (typeof x === "undefined") return this._width; + this._width = x; + // this.render(true); + return this; + } + height(): number + height(x: number): StackGraph + height(x?: number): number | StackGraph { + if (typeof x === "undefined") return this._height; + this._height = x; + // this.render(true); + return this; + } + cellHeight(): number + cellHeight(x: number): StackGraph + cellHeight(x?: number): number | StackGraph { + if (typeof x === "undefined") return this._cellHeight; + this._cellHeight = x; + // this.render(true); + return this; + } + clickHandler(): ClickHandler + clickHandler(x: ClickHandler): StackGraph + clickHandler(x?: ClickHandler): ClickHandler | StackGraph { + if (typeof x === "undefined") return this._clickHandler; + this._clickHandler = x; + return this; + } + hoverHandler(): HoverHandler + hoverHandler(x: HoverHandler): StackGraph + hoverHandler(x?: HoverHandler): HoverHandler | StackGraph { + if (typeof x === "undefined") return this._hoverHandler; + this._hoverHandler = x; + return this; + } + data(): StackGraphData + data(x: StackGraphData): StackGraph + data(x?: StackGraphData): StackGraphData | StackGraph { + if (typeof x === "undefined") return this._data; + this._data = x; + if (!this.zoomed) { + this.root = this.partition(x); + } + return this; + } + highlights(): StackGraphHighlight[] + highlights(x: StackGraphHighlight[]): StackGraph + highlights(x?: StackGraphHighlight[]): StackGraphHighlight[] | StackGraph { + if (typeof x === "undefined") return this._highlights; + this._highlights = x; + return this; + } + color(): ColorHandler + color(x: ColorHandler): StackGraph + color(x?: ColorHandler): ColorHandler | StackGraph { + if (typeof x === "undefined") return this._color; + this._color = x; + return this; + } + textColor(): ColorHandler + textColor(x: ColorHandler): StackGraph + textColor(x?: ColorHandler): ColorHandler | StackGraph { + if (typeof x === "undefined") return this._textColor; + this._textColor = x; + return this; + } + discontinuities(): number[][] + discontinuities(x: number[][]): StackGraph + discontinuities(x?: number[][]): number[][] | StackGraph { + if (typeof x === "undefined") return this._discontinuities; + this._discontinuities = x; + return this; + } + + clickCallback(d: StackGraphNode) { + if (!this._clickHandler) return; + + if (d.data === unzoomData) { + this._clickHandler(this._data); + } else if (d.data !== this._data) { + this._clickHandler(d.data); + } + } + + zoom(d: StackGraphData) { + if (d === this._data) { + this.zoomed = false; + this.root = this.partition(this._data); + this.render(true); + } else { + this.zoomed = true; + this.root = this.partition(d); + this.render(true); + } + } + + highlightData(x: StackGraphData[]) { + // map the data to their corresponding nodes; remove those that don't exist + let nodes = x.map(d => this.idToNode[d.id]).filter(d => d); + + let g = this.svg.selectAll("g.stack-graph-node").data(nodes, (d) => d.data.id); + // highlight selected nodes + g.select("rect").classed("stack-graph-node-highlight", true); + // unhighlight all other nodes + g.exit().select("rect").classed("stack-graph-node-highlight", false); + } + + partition(rootData: StackGraphData): StackGraphNode { + this.idToNode = {}; + // first pass: build and layout the hierarchy + let root = new StackGraphNode(rootData); + root.start = rootData.start; + root.finish = rootData.finish; + this.idToNode[rootData.id] = root; + let nodes = [root]; + var maxHeight = 0; + while (nodes.length > 0) { + let node = nodes.pop(); + if (!node.data.id) { + node.data.id = this.nextId.toString(); + this.nextId += 1; + } + for (let c of node.data.children) { + let cn = new StackGraphNode(c); + cn.parent = node; + cn.depth = node.depth + 1; + cn.start = c.start; + cn.finish = c.finish; + if (cn.depth > maxHeight) maxHeight = cn.depth; + this.idToNode[cn.data.id] = cn; + node.children.push(cn); + nodes.push(cn); + } + } + // finally set height if it's not default + let computedHeight = Math.max(maxHeight + 1, 5) * this._cellHeight + PADDING_Y_BOTTOM; + if (!this._height || (rootData === this._data && this._height > computedHeight)) { + this._height = computedHeight; + } + return root; + } + + setupSvg() { + this.svg = d3.select(this.selector).append('svg').attr("class", "stack-graph"); + this.svg.append("g").attr("class", "stack-graph-highlights"); + this.svg.append("g").attr("class", "stack-graph-body"); + let labels = this.svg.append("g").attr("class", "stack-graph-labels"); + labels.append("rect").attr("class", "stack-graph-label-bg").attr("fill", "#ffffff"); + labels.append("text").attr("text-anchor", "middle") + .attr("transform", "rotate(270)") + .text("Call Stack"); + let axes = this.svg.append("g").attr("class", "stack-graph-axis"); + axes.append("rect").attr("class", "stack-graph-axis-bg").attr("fill", "#ffffff"); + this.svg.append("g").attr("class", "stack-graph-breaks"); + + let this_ = this; + let hasClass = (elt, id) => { + if (elt.classList) return elt.classList.contains(id); + else if (elt.className.baseVal) return elt.className.baseVal.indexOf(id) > -1; + else return elt.className.indexOf(id) > -1; + } + let mouseOpCallback = function(mouseover) { + return function(d, i, x) { + let elt = this; + if (this_._hoverHandler) { + if (hasClass(elt, "stack-graph-highlight") || hasClass(elt, "stack-graph-break")) { + if (mouseover) tooltip.show((d).summary, elt, "top"); + else tooltip.hide(); + } else if (hasClass(elt, "stack-graph-node")) { + this_._hoverHandler((d).data, mouseover); + } + } + }; + }; + this.mouseoverCallback = mouseOpCallback(true); + this.mouseoutCallback = mouseOpCallback(false); + } + + render(anim: boolean = false) { + // set up SVG + if (!this.svg) { + this.setupSvg(); + } + this.svg.attr('width', this._width) + .attr('height', this._height); + + let data = this.root ? this.root.descendants() : []; + if (this.zoomed) { + let curr = this.root.data, route = []; + while (curr = curr.parent) route.push(curr.name); + unzoomData.name = " " + route.join(" ← "); + let unzoomNode = new StackGraphNode(unzoomData); + unzoomNode.start = this.root.start; + unzoomNode.finish = this.root.finish; + unzoomNode.depth = -1; + data.splice(0, 0, unzoomNode); + } + + + /////////////////////////////////////////////////////////////////// + // update y label position + + let gLabel = this.svg.select("g.stack-graph-labels"); + gLabel.select("rect").attr("x", 0) + .attr("y", 0) + .attr("width", PADDING_X_LEFT) + .attr("height", this._height); + gLabel.select("text").attr("dy", PADDING_X_LEFT/2) + .attr("dx", -this._height/2); + + + /////////////////////////////////////////////////////////////////// + // create axes + + let xBase = d3.scaleLinear().range([PADDING_X_LEFT, this._width - PADDING_X_RIGHT]); + if (this.root) { + xBase.domain([this.root.start, this.root.finish]); + } + let [xMin, xMax] = xBase.domain(); + let xBaseTo01 = d3.scaleLinear().domain([xMin, xMax]); + let x = fc.scaleDiscontinuous(xBase); + let dcRange = fc.discontinuityRange(...this._discontinuities); + x.discontinuityProvider(dcRange); + let xTo01 = fc.scaleDiscontinuous(xBaseTo01); + xTo01.discontinuityProvider(dcRange); + + let ticks = d3.ticks(0, 1, X_AXIS_TICKS); + let tickValues = ticks.map(xTo01.invert); + + let y = d3.scaleLinear().range([0, this._cellHeight]); + + let xAxis = d3.axisBottom(x).tickValues(tickValues); + xAxis.tickFormat((x) => (x/1000).toFixed(3) + "s"); + + let xAxisPos = this._height - PADDING_Y_BOTTOM; + this.svg.select("g.stack-graph-axis") + .attr("transform", `translate(0,${xAxisPos})`) + .call(xAxis); + this.svg.select("rect.stack-graph-axis-bg") + .attr("x", 0).attr("y", 0).attr("width", this._width).attr("height", PADDING_Y_BOTTOM); + + let width = (d: StackGraphNode | StackGraphHighlight) => x(d.finish) - x(d.start); + + /////////////////////////////////////////////////////////////////// + // render highlights + + let gHigh = this.svg.select("g.stack-graph-highlights"); + let hRect = gHigh.selectAll("rect").data(this._highlights.filter((d) => width(d) > 0)); + let hEnter = hRect.enter().append("svg:rect").attr("class", "stack-graph-highlight"); + hEnter.on("mouseover", this.mouseoverCallback) + .on("mouseout", this.mouseoutCallback); + hRect.merge(hEnter) + .attr("height", this._height) + .attr("fill", (d: StackGraphHighlight) => d.color) + .attr("title", (d: StackGraphHighlight) => d.summary); + + // animate the highlights if requested + let hPos = (grp) => grp.attr("width", width) + .attr("transform", (d) => "translate(" + x(d.start) + ",0)"); + if (anim) { + hPos(hRect.transition() + .duration(this._transitionDuration).ease(this._transitionEase)); + } else { + hPos(hRect); + } + hPos(hEnter); + hRect.exit().remove(); + + /////////////////////////////////////////////////////////////////// + // render breaks + + let gBreak = this.svg.select("g.stack-graph-breaks"); + let bLine = gBreak.selectAll("line").data(this._highlights.filter((d) => width(d) == 0)); + let bEnter = bLine.enter().append("svg:line").attr("class", "stack-graph-break"); + bEnter.on("mouseover", this.mouseoverCallback) + .on("mouseout", this.mouseoutCallback); + bLine.merge(bEnter) + .attr("y1", this._height - PADDING_Y_BOTTOM + xAxis.tickSizeInner()) + .attr("y2", 0) + .attr("x1", 0) + .attr("x2", 0) + .attr("stroke-width", 1) + .attr("stroke", "#444444") + .style("stroke-dasharray", "5 5"); + + // animate the breaks if requested + let bPos = (grp) => grp.attr("transform", (d) => "translate(" + x(d.start) + ",0)"); + if (anim) { + bPos(bLine.transition() + .duration(this._transitionDuration).ease(this._transitionEase)); + } else { + bPos(bLine); + } + bPos(bEnter); + bLine.exit().remove(); + + + /////////////////////////////////////////////////////////////////// + // render call stacks + + let dx = (d: StackGraphNode) => x(d.start); + let dy = (d: StackGraphNode) => this._height - y(d.depth + (this.zoomed ? 1 : 0)) - this._cellHeight - PADDING_Y_BOTTOM; + let pos = (d: StackGraphNode) => `translate(${dx(d)},${dy(d)})`; + let rectClass = (d: StackGraphNode) => d.data === unzoomData ? "stack-graph-rect stack-graph-unzoom" : "stack-graph-rect"; + + let filteredData = data.filter((d) => width(d) >= MIN_WIDTH); + let g = this.svg.select("g.stack-graph-body") + .selectAll("g.stack-graph-node").data(filteredData, (d) => d.data.id); + + let enter = g.enter().append("svg:g"); + enter.append("svg:rect"); + enter.append("foreignObject") + .append("xhtml:div"); + enter.on("click", (d: StackGraphNode) => this.clickCallback(d)) + .on("mouseover", this.mouseoverCallback) + .on("mouseout", this.mouseoutCallback); + + g.merge(enter) + .attr("height", this._cellHeight) + .attr("name", (d: StackGraphNode) => d.data.name) + .attr("class", "stack-graph-node") + .select("rect") + .attr("height", this._cellHeight) + .attr("class", rectClass) + .attr("fill", (d: StackGraphNode) => this._color(d.data)); + + g.merge(enter) + .select("foreignObject") + .attr("width", width) + .attr("height", this._cellHeight) + .select("div") + .attr("class", "stack-graph-label") + .style("display", (d: StackGraphNode) => width(d) < 35 ? "none" : "block") + .style("color", (d: StackGraphNode) => this._textColor(d.data)) + .text((d: StackGraphNode) => d.data.name); + + // animate preexisting nodes into new positions + let gPos = (grp) => grp.attr("transform", pos) + .attr("width", width) + .select("rect") + .attr("width", width); + if (anim) { + gPos(g.transition() + .duration(this._transitionDuration).ease(this._transitionEase)); + } else { + gPos(g); + } + + // don't animate new nodes into position... + gPos(enter); + // but maybe fade them in if this isn't the first render (i.e., if unzooming) + if (anim) { + enter.style("opacity", 0.0) + .transition() + .duration(this._transitionDuration).ease(this._transitionEase) + .style("opacity", 1.0); + } + + g.exit().remove(); + } + + constructor(s: string) { + this.selector = s; + } + + } + + export function stackGraph(selector: string): StackGraph { + return new StackGraph(selector); + } +} \ No newline at end of file diff --git a/rosette/lib/profile/renderer/report/html/js/d3fc-discontinuous-scale.js b/rosette/lib/profile/renderer/report/html/js/d3fc-discontinuous-scale.js new file mode 100644 index 00000000..229b50f6 --- /dev/null +++ b/rosette/lib/profile/renderer/report/html/js/d3fc-discontinuous-scale.js @@ -0,0 +1,645 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : + typeof define === 'function' && define.amd ? define(['exports'], factory) : + (factory((global.fc = global.fc || {}))); + }(this, (function (exports) { 'use strict'; + + var createReboundMethod = (function (target, source, name) { + var method = source[name]; + if (typeof method !== 'function') { + throw new Error('Attempt to rebind ' + name + ' which isn\'t a function on the source object'); + } + return function () { + for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + + var value = method.apply(source, args); + return value === source ? target : value; + }; + }); + + var rebind = (function (target, source) { + for (var _len = arguments.length, names = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { + names[_key - 2] = arguments[_key]; + } + + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = names[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var name = _step.value; + + target[name] = createReboundMethod(target, source, name); + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } + } + + return target; + }); + + var createTransform = function createTransform(transforms) { + return function (name) { + return transforms.reduce(function (name, fn) { + return name && fn(name); + }, name); + }; + }; + + var rebindAll = (function (target, source) { + for (var _len = arguments.length, transforms = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { + transforms[_key - 2] = arguments[_key]; + } + + var transform = createTransform(transforms); + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = Object.keys(source)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var name = _step.value; + + var result = transform(name); + if (result) { + target[result] = createReboundMethod(target, source, name); + } + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } + } + + return target; + }); + + var regexify = (function (strsOrRegexes) { + return strsOrRegexes.map(function (strOrRegex) { + return typeof strOrRegex === 'string' ? new RegExp('^' + strOrRegex + '$') : strOrRegex; + }); + }); + + var exclude = (function () { + for (var _len = arguments.length, exclusions = Array(_len), _key = 0; _key < _len; _key++) { + exclusions[_key] = arguments[_key]; + } + + exclusions = regexify(exclusions); + return function (name) { + return exclusions.every(function (exclusion) { + return !exclusion.test(name); + }) && name; + }; + }); + + var include = (function () { + for (var _len = arguments.length, inclusions = Array(_len), _key = 0; _key < _len; _key++) { + inclusions[_key] = arguments[_key]; + } + + inclusions = regexify(inclusions); + return function (name) { + return inclusions.some(function (inclusion) { + return inclusion.test(name); + }) && name; + }; + }); + + var includeMap = (function (mappings) { + return function (name) { + return mappings[name]; + }; + }); + + var capitalizeFirstLetter = function capitalizeFirstLetter(str) { + return str[0].toUpperCase() + str.slice(1); + }; + + var prefix = (function (prefix) { + return function (name) { + return prefix + capitalizeFirstLetter(name); + }; + }); + + exports.rebind = rebind; + exports.rebindAll = rebindAll; + exports.exclude = exclude; + exports.include = include; + exports.includeMap = includeMap; + exports.prefix = prefix; + + Object.defineProperty(exports, '__esModule', { value: true }); + + }))); + + +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-scale'), require('d3fc-rebind'), require('d3-time')) : + typeof define === 'function' && define.amd ? define(['exports', 'd3-scale', 'd3fc-rebind', 'd3-time'], factory) : + (factory((global.fc = global.fc || {}),global.d3,global.fc,global.d3)); +}(this, (function (exports,d3Scale,d3fcRebind,d3Time) { 'use strict'; + +var identity = function () { + + var identity = {}; + + identity.distance = function (start, end) { + return end - start; + }; + + identity.offset = function (start, offset) { + return start instanceof Date ? new Date(start.getTime() + offset) : start + offset; + }; + + identity.clampUp = function (d) { + return d; + }; + + identity.clampDown = function (d) { + return d; + }; + + identity.copy = function () { + return identity; + }; + + return identity; +}; + +function tickFilter(ticks, discontinuityProvider) { + var discontinuousTicks = []; + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = ticks[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var tick = _step.value; + + var up = discontinuityProvider.clampUp(tick); + var down = discontinuityProvider.clampDown(tick); + if (up === down) { + discontinuousTicks.push(up); + } + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } + } + + return discontinuousTicks; +} + +function discontinuous(adaptedScale) { + var _this = this; + + if (!arguments.length) { + adaptedScale = d3Scale.scaleIdentity(); + } + + var discontinuityProvider = identity(); + + var scale = function scale(value) { + var domain = adaptedScale.domain(); + var range = adaptedScale.range(); + + // The discontinuityProvider is responsible for determine the distance between two points + // along a scale that has discontinuities (i.e. sections that have been removed). + // the scale for the given point 'x' is calculated as the ratio of the discontinuous distance + // over the domain of this axis, versus the discontinuous distance to 'x' + var totalDomainDistance = discontinuityProvider.distance(domain[0], domain[1]); + var distanceToX = discontinuityProvider.distance(domain[0], value); + var ratioToX = distanceToX / totalDomainDistance; + var scaledByRange = ratioToX * (range[1] - range[0]) + range[0]; + return scaledByRange; + }; + + scale.invert = function (x) { + var domain = adaptedScale.domain(); + var range = adaptedScale.range(); + + var ratioToX = (x - range[0]) / (range[1] - range[0]); + var totalDomainDistance = discontinuityProvider.distance(domain[0], domain[1]); + var distanceToX = ratioToX * totalDomainDistance; + return discontinuityProvider.offset(domain[0], distanceToX); + }; + + scale.domain = function () { + if (!arguments.length) { + return adaptedScale.domain(); + } + var newDomain = arguments.length <= 0 ? undefined : arguments[0]; + + // clamp the upper and lower domain values to ensure they + // do not fall within a discontinuity + var domainLower = discontinuityProvider.clampUp(newDomain[0]); + var domainUpper = discontinuityProvider.clampDown(newDomain[1]); + adaptedScale.domain([domainLower, domainUpper]); + return scale; + }; + + scale.nice = function () { + adaptedScale.nice(); + var domain = adaptedScale.domain(); + var domainLower = discontinuityProvider.clampUp(domain[0]); + var domainUpper = discontinuityProvider.clampDown(domain[1]); + adaptedScale.domain([domainLower, domainUpper]); + return scale; + }; + + scale.ticks = function () { + for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + + var ticks = adaptedScale.ticks.apply(_this, args); + return tickFilter(ticks, discontinuityProvider); + }; + + scale.copy = function () { + return discontinuous(adaptedScale.copy()).discontinuityProvider(discontinuityProvider.copy()); + }; + + scale.discontinuityProvider = function () { + if (!arguments.length) { + return discontinuityProvider; + } + discontinuityProvider = arguments.length <= 0 ? undefined : arguments[0]; + return scale; + }; + + d3fcRebind.rebindAll(scale, adaptedScale, d3fcRebind.include('range', 'rangeRound', 'interpolate', 'clamp', 'tickFormat')); + + return scale; +} + +var skipWeekends = function () { + + // the indices returned by date.getDay() + var day = { + sunday: 0, + monday: 1, + saturday: 6 + }; + + var millisPerDay = 24 * 3600 * 1000; + var millisPerWorkWeek = millisPerDay * 5; + var millisPerWeek = millisPerDay * 7; + + var skipWeekends = {}; + + var isWeekend = function isWeekend(date) { + return date.getDay() === 0 || date.getDay() === 6; + }; + + skipWeekends.clampDown = function (date) { + if (date && isWeekend(date)) { + // round the date up to midnight + var newDate = d3Time.timeDay.ceil(date); + // then subtract the required number of days + if (newDate.getDay() === day.sunday) { + return d3Time.timeDay.offset(newDate, -1); + } else if (newDate.getDay() === day.monday) { + return d3Time.timeDay.offset(newDate, -2); + } else { + return newDate; + } + } else { + return date; + } + }; + + skipWeekends.clampUp = function (date) { + if (date && isWeekend(date)) { + // round the date down to midnight + var newDate = d3Time.timeDay.floor(date); + // then add the required number of days + if (newDate.getDay() === day.saturday) { + return d3Time.timeDay.offset(newDate, 2); + } else if (newDate.getDay() === day.sunday) { + return d3Time.timeDay.offset(newDate, 1); + } else { + return newDate; + } + } else { + return date; + } + }; + + // returns the number of included milliseconds (i.e. those which do not fall) + // within discontinuities, along this scale + skipWeekends.distance = function (startDate, endDate) { + startDate = skipWeekends.clampUp(startDate); + endDate = skipWeekends.clampDown(endDate); + + // move the start date to the end of week boundary + var offsetStart = d3Time.timeSaturday.ceil(startDate); + if (endDate < offsetStart) { + return endDate.getTime() - startDate.getTime(); + } + + var msAdded = offsetStart.getTime() - startDate.getTime(); + + // move the end date to the end of week boundary + var offsetEnd = d3Time.timeSaturday.ceil(endDate); + var msRemoved = offsetEnd.getTime() - endDate.getTime(); + + // determine how many weeks there are between these two dates + // round to account for DST transitions + var weeks = Math.round((offsetEnd.getTime() - offsetStart.getTime()) / millisPerWeek); + + return weeks * millisPerWorkWeek + msAdded - msRemoved; + }; + + skipWeekends.offset = function (startDate, ms) { + var date = isWeekend(startDate) ? skipWeekends.clampUp(startDate) : startDate; + + if (ms === 0) { + return date; + } + + var isNegativeOffset = ms < 0; + var isPositiveOffset = ms > 0; + var remainingms = ms; + + // move to the end of week boundary for a postive offset or to the start of a week for a negative offset + var weekBoundary = isNegativeOffset ? d3Time.timeMonday.floor(date) : d3Time.timeSaturday.ceil(date); + remainingms -= weekBoundary.getTime() - date.getTime(); + + // if the distance to the boundary is greater than the number of ms + // simply add the ms to the current date + if (isNegativeOffset && remainingms > 0 || isPositiveOffset && remainingms < 0) { + return new Date(date.getTime() + ms); + } + + // skip the weekend for a positive offset + date = isNegativeOffset ? weekBoundary : d3Time.timeDay.offset(weekBoundary, 2); + + // add all of the complete weeks to the date + var completeWeeks = Math.floor(remainingms / millisPerWorkWeek); + date = d3Time.timeDay.offset(date, completeWeeks * 7); + remainingms -= completeWeeks * millisPerWorkWeek; + + // add the remaining time + date = new Date(date.getTime() + remainingms); + return date; + }; + + skipWeekends.copy = function () { + return skipWeekends; + }; + + return skipWeekends; +}; + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { + return typeof obj; +} : function (obj) { + return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; +}; + + + + + + + + + + + + + + + + + + + + + +var get = function get(object, property, receiver) { + if (object === null) object = Function.prototype; + var desc = Object.getOwnPropertyDescriptor(object, property); + + if (desc === undefined) { + var parent = Object.getPrototypeOf(object); + + if (parent === null) { + return undefined; + } else { + return get(parent, property, receiver); + } + } else if ("value" in desc) { + return desc.value; + } else { + var getter = desc.get; + + if (getter === undefined) { + return undefined; + } + + return getter.call(receiver); + } +}; + + + + + + + + + + + + + + + + + +var set = function set(object, property, value, receiver) { + var desc = Object.getOwnPropertyDescriptor(object, property); + + if (desc === undefined) { + var parent = Object.getPrototypeOf(object); + + if (parent !== null) { + set(parent, property, value, receiver); + } + } else if ("value" in desc && desc.writable) { + desc.value = value; + } else { + var setter = desc.set; + + if (setter !== undefined) { + setter.call(receiver, value); + } + } + + return value; +}; + +var provider = function provider() { + for (var _len = arguments.length, ranges = Array(_len), _key = 0; _key < _len; _key++) { + ranges[_key] = arguments[_key]; + } + + var inRange = function inRange(number, range) { + return number > range[0] && number < range[1]; + }; + + var surroundsRange = function surroundsRange(inner, outer) { + return inner[0] >= outer[0] && inner[1] <= outer[1]; + }; + + var identity = {}; + + identity.distance = function (start, end) { + start = identity.clampUp(start); + end = identity.clampDown(end); + + var surroundedRanges = ranges.filter(function (r) { + return surroundsRange(r, [start, end]); + }); + var rangeSizes = surroundedRanges.map(function (r) { + return r[1] - r[0]; + }); + + return end - start - rangeSizes.reduce(function (total, current) { + return total + current; + }, 0); + }; + + var add = function add(value, offset) { + return value instanceof Date ? new Date(value.getTime() + offset) : value + offset; + }; + + identity.offset = function (location, offset) { + if (offset > 0) { + var _ret = function () { + var currentLocation = identity.clampUp(location); + var offsetRemaining = offset; + while (offsetRemaining > 0) { + var futureRanges = ranges.filter(function (r) { + return r[0] > currentLocation; + }).sort(function (a, b) { + return a[0] - b[0]; + }); + if (futureRanges.length) { + var nextRange = futureRanges[0]; + var delta = nextRange[0] - currentLocation; + if (delta > offsetRemaining) { + currentLocation = add(currentLocation, offsetRemaining); + offsetRemaining = 0; + } else { + currentLocation = nextRange[1]; + offsetRemaining -= delta; + } + } else { + currentLocation = add(currentLocation, offsetRemaining); + offsetRemaining = 0; + } + } + return { + v: currentLocation + }; + }(); + + if ((typeof _ret === "undefined" ? "undefined" : _typeof(_ret)) === "object") return _ret.v; + } else { + var _ret2 = function () { + var currentLocation = identity.clampDown(location); + var offsetRemaining = offset; + while (offsetRemaining < 0) { + var futureRanges = ranges.filter(function (r) { + return r[1] < currentLocation; + }).sort(function (a, b) { + return b[0] - a[0]; + }); + if (futureRanges.length) { + var nextRange = futureRanges[0]; + var delta = nextRange[1] - currentLocation; + if (delta < offsetRemaining) { + currentLocation = add(currentLocation, offsetRemaining); + offsetRemaining = 0; + } else { + currentLocation = nextRange[0]; + offsetRemaining -= delta; + } + } else { + currentLocation = add(currentLocation, offsetRemaining); + offsetRemaining = 0; + } + } + return { + v: currentLocation + }; + }(); + + if ((typeof _ret2 === "undefined" ? "undefined" : _typeof(_ret2)) === "object") return _ret2.v; + } + }; + + identity.clampUp = function (d) { + return ranges.reduce(function (value, range) { + return inRange(value, range) ? range[1] : value; + }, d); + }; + + identity.clampDown = function (d) { + return ranges.reduce(function (value, range) { + return inRange(value, range) ? range[0] : value; + }, d); + }; + + identity.copy = function () { + return identity; + }; + + return identity; +}; + +exports.scaleDiscontinuous = discontinuous; +exports.discontinuitySkipWeekends = skipWeekends; +exports.discontinuityIdentity = identity; +exports.discontinuityRange = provider; + +Object.defineProperty(exports, '__esModule', { value: true }); + +}))); diff --git a/rosette/lib/profile/renderer/report/html/js/data.js b/rosette/lib/profile/renderer/report/html/js/data.js new file mode 100644 index 00000000..6e0373f9 --- /dev/null +++ b/rosette/lib/profile/renderer/report/html/js/data.js @@ -0,0 +1,252 @@ +var data; +(function (data_1) { + var SolverCallType; + (function (SolverCallType) { + SolverCallType[SolverCallType["SOLVE"] = 0] = "SOLVE"; + SolverCallType[SolverCallType["ENCODE"] = 1] = "ENCODE"; + SolverCallType[SolverCallType["FINITIZE"] = 2] = "FINITIZE"; + })(SolverCallType = data_1.SolverCallType || (data_1.SolverCallType = {})); + ; + // buffer messages until document ready + var bufferedMessages = []; + var ready = false; + var currentState = { + metadata: null, + root: null, + current: null, + idToNode: {}, + zeroMetrics: null, + solverCalls: [], + streaming: false + }; + var updateCallbacks = []; + function registerUpdateCallback(cb) { + updateCallbacks.push(cb); + } + data_1.registerUpdateCallback = registerUpdateCallback; + function unregisterUpdateCallback(cb) { + var i = updateCallbacks.indexOf(cb); + if (i > -1) + updateCallbacks.splice(i, 1); + } + data_1.unregisterUpdateCallback = unregisterUpdateCallback; + function readyForData() { + if (!ready) { + ready = true; + receiveData(bufferedMessages); + bufferedMessages = []; + } + } + data_1.readyForData = readyForData; + function diffMetrics(p1, p2) { + var ret = {}; + for (var k in p1) { + if (p2.hasOwnProperty(k)) { + ret[k] = p2[k] - p1[k]; + } + } + return ret; + } + function exclMetrics(incl, children) { + var ret = {}; + for (var k in incl) { + ret[k] = incl[k]; + } + for (var _i = 0, children_1 = children; _i < children_1.length; _i++) { + var c = children_1[_i]; + for (var k in incl) { + ret[k] -= c.incl[k] || 0; + } + } + return ret; + } + var messages; + (function (messages) { + function receiveMetadataMessage(msg) { + delete msg["type"]; + currentState.metadata = msg; + } + messages.receiveMetadataMessage = receiveMetadataMessage; + // update the ProfileState using data from the profile message + function receiveCallgraphMessage(msg) { + var evts = msg["events"]; + if (evts.length == 0) + return; + if (!currentState.zeroMetrics) { + currentState.zeroMetrics = evts[0].metrics; + } + for (var _i = 0, evts_1 = evts; _i < evts_1.length; _i++) { + var e = evts_1[_i]; + if (e.type == "ENTER") { + if (!currentState.current && currentState.root) { + console.error("multiple root procedure calls"); + return; + } + // e has fields: + // id, function, location, inputs, metrics + var dm = diffMetrics(currentState.zeroMetrics, e.metrics); + var node = { + id: e.id, + name: e.function, + callsite: e.callsite, + source: e.source, + start: dm.time, + finish: null, + startMetrics: dm, + finishMetrics: null, + isFinished: false, + inputs: e.inputs, + outputs: {}, + children: [], + parent: currentState.current, + incl: {}, + excl: {}, + score: 0 + }; + if (currentState.current) + currentState.current.children.push(node); + if (!currentState.root) // might be the first call + currentState.root = node; + currentState.current = node; + currentState.idToNode[e.id] = node; + } + else if (e.type == "EXIT") { + if (!currentState.current) { + console.error("unbalanced EXIT event"); + } + // e has fields: + // outputs, metrics + var dm = diffMetrics(currentState.zeroMetrics, e.metrics); + currentState.current.finish = dm.time; + currentState.current.finishMetrics = dm; + currentState.current.outputs = e.outputs; + currentState.current.isFinished = true; + currentState.current.incl = diffMetrics(currentState.current.startMetrics, dm); + currentState.current.excl = exclMetrics(currentState.current.incl, currentState.current.children); + currentState.current = currentState.current.parent; + } + } + // set fake finish times and metrics for unclosed nodes + var fakeFinishMetrics = diffMetrics(currentState.zeroMetrics, evts[evts.length - 1].metrics); + var fakeFinishTime = fakeFinishMetrics.time; + var curr = currentState.current; + while (curr) { + if (!curr.isFinished) { + curr.finish = fakeFinishTime; + curr.finishMetrics = fakeFinishMetrics; + curr.incl = diffMetrics(curr.startMetrics, fakeFinishMetrics); + curr.excl = exclMetrics(curr.incl, curr.children); + } + curr = curr.parent; + } + } + messages.receiveCallgraphMessage = receiveCallgraphMessage; + function receiveSolverCallsMessage(msg) { + var events = msg["events"]; + if (!currentState.zeroMetrics) { + console.error("solver-calls expects profile data first"); + return; + } + var startTime = currentState.zeroMetrics.time; + for (var _i = 0, events_1 = events; _i < events_1.length; _i++) { + var e = events_1[_i]; + if (e.type == "start") { + var typ = e.part == "solver" ? SolverCallType.SOLVE : (e.part == "encode" ? SolverCallType.ENCODE : SolverCallType.FINITIZE); + currentState.solverCalls.push({ + type: typ, + start: e.time - startTime, + finish: undefined, + sat: undefined + }); + } + else if (e.type == "finish") { + if (currentState.solverCalls.length > 0) { + var curr = currentState.solverCalls[currentState.solverCalls.length - 1]; + curr.finish = e.time - startTime; + if (curr.type == SolverCallType.SOLVE) + curr.sat = e.sat; + } + } + } + } + messages.receiveSolverCallsMessage = receiveSolverCallsMessage; + function receiveUnusedTermsMessage(msg) { + var data = msg["data"]; // list of (call-id, #unused) pairs + for (var _i = 0, data_2 = data; _i < data_2.length; _i++) { + var pair = data_2[_i]; + var id = pair[0].toString(), num = pair[1]; + if (currentState.idToNode.hasOwnProperty(id)) { + var node = currentState.idToNode[id]; + node.excl["unused-terms"] = num; + } + } + } + messages.receiveUnusedTermsMessage = receiveUnusedTermsMessage; + })(messages || (messages = {})); + var stream; + (function (stream) { + var webSocket; + function receiveStreamMessage(msg) { + if (msg["event"] == "start") { + currentState.streaming = true; + webSocket = new WebSocket(msg["url"]); + webSocket.onmessage = webSocketMessageCallback; + webSocket.onerror = webSocketErrorCallback; + } + else if (msg["event"] == "finish") { + currentState.streaming = false; + if (webSocket) + webSocket.close(); + } + else { + console.log("unknown stream message:", msg); + } + } + stream.receiveStreamMessage = receiveStreamMessage; + function webSocketMessageCallback(evt) { + var msgs = JSON.parse(evt.data); // will be a list of messages + receiveData(msgs); + } + function webSocketErrorCallback(evt) { + alert("Could not open the WebSocket connection for streaming. This might happen if the profiler is not currently running."); + } + })(stream || (stream = {})); + // hand messages to their handler functions + function receiveMessages(msgs) { + for (var _i = 0, msgs_1 = msgs; _i < msgs_1.length; _i++) { + var msg = msgs_1[_i]; + if (msg.type == "metadata") { + messages.receiveMetadataMessage(msg); + } + else if (msg.type == "callgraph") { + messages.receiveCallgraphMessage(msg); + } + else if (msg.type == "solver-calls") { + messages.receiveSolverCallsMessage(msg); + } + else if (msg.type == "unused-terms") { + messages.receiveUnusedTermsMessage(msg); + } + else if (msg.type === "stream") { + stream.receiveStreamMessage(msg); + } + else { + console.log("unknown message:", msg); + } + } + } + function receiveData(msgs) { + if (ready) { + receiveMessages(msgs); + for (var _i = 0, updateCallbacks_1 = updateCallbacks; _i < updateCallbacks_1.length; _i++) { + var cb = updateCallbacks_1[_i]; + cb(currentState); + } + } + else { + bufferedMessages.push.apply(bufferedMessages, msgs); + } + } + data_1.receiveData = receiveData; +})(data || (data = {})); +//# sourceMappingURL=data.js.map \ No newline at end of file diff --git a/rosette/lib/profile/renderer/report/html/js/data.ts b/rosette/lib/profile/renderer/report/html/js/data.ts new file mode 100644 index 00000000..7c8345bb --- /dev/null +++ b/rosette/lib/profile/renderer/report/html/js/data.ts @@ -0,0 +1,282 @@ +namespace data { + export interface CallNode { + id: string; + name: string; + callsite: string; + source: string; + start: number; // time + finish: number; // time + startMetrics: Map; + finishMetrics: Map; + isFinished: boolean; // have we seen an exit event for this node? + inputs: Map; + outputs: Map; + children: CallNode[]; + parent: CallNode; + incl: Map; + excl: Map; + score: number; + } + + export interface ProfileState { + metadata: Object; + root: CallNode; + current: CallNode; + idToNode: Map; + zeroMetrics: Map; + solverCalls: SolverCall[]; + streaming: boolean; + } + + export interface SolverCall { + type: SolverCallType, + start: number; + finish: number; + sat: boolean; + } + + export enum SolverCallType { + SOLVE, + ENCODE, + FINITIZE + }; + + // events are also a type of message, since they have a type field + interface Message { + type: string + } + + // buffer messages until document ready + var bufferedMessages: Message[] = []; + var ready = false; + var currentState: ProfileState = { + metadata: null, + root: null, + current: null, + idToNode: {}, + zeroMetrics: null, + solverCalls: [], + streaming: false + }; + + // callbacks to invoke when new state data arrives + + type StateUpdateCallback = (msg: ProfileState) => void; + var updateCallbacks: StateUpdateCallback[] = []; + + export function registerUpdateCallback(cb: StateUpdateCallback): void { + updateCallbacks.push(cb); + } + export function unregisterUpdateCallback(cb: StateUpdateCallback): void { + let i = updateCallbacks.indexOf(cb); + if (i > -1) + updateCallbacks.splice(i, 1); + } + + export function readyForData() { + if (!ready) { + ready = true; + receiveData(bufferedMessages); + bufferedMessages = []; + } + } + + function diffMetrics(p1: Map, p2: Map): Map { + let ret: Map = {}; + for (let k in p1) { + if (p2.hasOwnProperty(k)) { + ret[k] = p2[k] - p1[k]; + } + } + return ret; + } + + function exclMetrics(incl: Map, children: CallNode[]): Map { + let ret = {}; + for (let k in incl) { + ret[k] = incl[k]; + } + for (let c of children) { + for (let k in incl) { + ret[k] -= c.incl[k] || 0; + } + } + return ret; + } + + namespace messages { + export function receiveMetadataMessage(msg: Message): void { + delete msg["type"]; + currentState.metadata = msg; + } + + // update the ProfileState using data from the profile message + export function receiveCallgraphMessage(msg: Message): void { + let evts = msg["events"]; + if (evts.length == 0) return; + + if (!currentState.zeroMetrics) { + currentState.zeroMetrics = evts[0].metrics; + } + + for (let e of evts) { + if (e.type == "ENTER") { + if (!currentState.current && currentState.root) { + console.error("multiple root procedure calls"); + return; + } + // e has fields: + // id, function, location, inputs, metrics + let dm = diffMetrics(currentState.zeroMetrics, e.metrics); + let node: CallNode = { + id: e.id, + name: e.function, + callsite: e.callsite, + source: e.source, + start: dm.time, + finish: null, + startMetrics: dm, + finishMetrics: null, + isFinished: false, + inputs: e.inputs, + outputs: {}, + children: [], + parent: currentState.current, + incl: {}, + excl: {}, + score: 0 + }; + if (currentState.current) + currentState.current.children.push(node); + if (!currentState.root) // might be the first call + currentState.root = node; + currentState.current = node; + currentState.idToNode[e.id] = node; + } else if (e.type == "EXIT") { + if (!currentState.current) { + console.error("unbalanced EXIT event"); + } + // e has fields: + // outputs, metrics + let dm = diffMetrics(currentState.zeroMetrics, e.metrics); + currentState.current.finish = dm.time; + currentState.current.finishMetrics = dm; + currentState.current.outputs = e.outputs; + currentState.current.isFinished = true; + currentState.current.incl = diffMetrics(currentState.current.startMetrics, dm); + currentState.current.excl = exclMetrics(currentState.current.incl, currentState.current.children); + currentState.current = currentState.current.parent; + } + } + + // set fake finish times and metrics for unclosed nodes + let fakeFinishMetrics = diffMetrics(currentState.zeroMetrics, evts[evts.length-1].metrics); + let fakeFinishTime = fakeFinishMetrics.time; + let curr = currentState.current; + while (curr) { + if (!curr.isFinished) { + curr.finish = fakeFinishTime; + curr.finishMetrics = fakeFinishMetrics; + curr.incl = diffMetrics(curr.startMetrics, fakeFinishMetrics); + curr.excl = exclMetrics(curr.incl, curr.children); + } + curr = curr.parent; + } + } + + export function receiveSolverCallsMessage(msg: Message): void { + let events = msg["events"]; + if (!currentState.zeroMetrics) { + console.error("solver-calls expects profile data first"); + return; + } + let startTime = currentState.zeroMetrics.time; + for (let e of events) { + if (e.type == "start") { + let typ = e.part == "solver" ? SolverCallType.SOLVE : (e.part == "encode" ? SolverCallType.ENCODE : SolverCallType.FINITIZE); + currentState.solverCalls.push({ + type: typ, + start: e.time - startTime, + finish: undefined, + sat: undefined + }); + } else if (e.type == "finish") { + if (currentState.solverCalls.length > 0) { + let curr = currentState.solverCalls[currentState.solverCalls.length - 1]; + curr.finish = e.time - startTime; + if (curr.type == SolverCallType.SOLVE) + curr.sat = e.sat; + } + } + } + } + + export function receiveUnusedTermsMessage(msg: Message): void { + let data: number[][] = msg["data"]; // list of (call-id, #unused) pairs + for (let pair of data) { + let id = pair[0].toString(), num = pair[1]; + if (currentState.idToNode.hasOwnProperty(id)) { + let node = currentState.idToNode[id]; + node.excl["unused-terms"] = num; + } + } + } + } + + namespace stream { + var webSocket: WebSocket; + + export function receiveStreamMessage(msg: Message): void { + if (msg["event"] == "start") { + currentState.streaming = true; + webSocket = new WebSocket(msg["url"]); + webSocket.onmessage = webSocketMessageCallback; + webSocket.onerror = webSocketErrorCallback; + } else if (msg["event"] == "finish") { + currentState.streaming = false; + if (webSocket) webSocket.close(); + } else { + console.log("unknown stream message:", msg); + } + } + + function webSocketMessageCallback(evt: MessageEvent): void { + let msgs = JSON.parse(evt.data); // will be a list of messages + receiveData(msgs); + } + + function webSocketErrorCallback(evt: Event) { + alert("Could not open the WebSocket connection for streaming. This might happen if the profiler is not currently running."); + } + } + + // hand messages to their handler functions + function receiveMessages(msgs: Message[]): void { + for (let msg of msgs) { + if (msg.type == "metadata") { + messages.receiveMetadataMessage(msg); + } else if (msg.type == "callgraph") { + messages.receiveCallgraphMessage(msg); + } else if (msg.type == "solver-calls") { + messages.receiveSolverCallsMessage(msg); + } else if (msg.type == "unused-terms") { + messages.receiveUnusedTermsMessage(msg); + } else if (msg.type === "stream") { + stream.receiveStreamMessage(msg); + } else { + console.log("unknown message:", msg); + } + } + } + + export function receiveData(msgs: Message[]): void { + if (ready) { + receiveMessages(msgs); + for (let cb of updateCallbacks) { + cb(currentState); + } + } else { + bufferedMessages.push(...msgs); + } + } +} diff --git a/rosette/lib/profile/renderer/report/html/js/profile.js b/rosette/lib/profile/renderer/report/html/js/profile.js new file mode 100644 index 00000000..eb6e982f --- /dev/null +++ b/rosette/lib/profile/renderer/report/html/js/profile.js @@ -0,0 +1,497 @@ +var profile; +(function (profile) { + var calltable; + (function (calltable) { + var scoreColumns = [ + { type: "excl", column: "time", name: "Time (ms)", description: "Total time spent in this function (but not in descendent calls)", score: true }, + { type: "excl", column: "term-count", name: "Term Count", description: "Number of symbolic terms created", score: true }, + { type: "excl", column: "unused-terms", name: "Unused Terms", description: "Number of symbolic terms created that were never used for solving", score: true }, + { type: "excl", column: "union-size", name: "Union Size", description: "Total number of branches in all symbolic unions created", score: true }, + { type: "excl", column: "merge-cases", name: "Merge Cases", description: "Number of branches used during merging", score: true }, + ]; + var DOM_ROW_KEY = "symproRowObject"; + var PRUNE_SCORE_FACTOR = 0.01; // < 1% of max score = pruned + var colorScheme = makeScoreColorScheme(["#000000", "#FD893C", "#D9002C"]); + var tableSorter; + var contextDepth = 0; + var useSignatures = false; + var useCallsites = false; + var pruneSmallRows = true; + var idToRow = {}; + function initCallTable() { + renderTableHeaders(); + analysis.setAggregate(true); + analysis.setColumns(scoreColumns); + analysis.registerAnalysisCallback(receiveData); + } + calltable.initCallTable = initCallTable; + function renderTableHeaders() { + var thead = document.querySelector("#calltable thead"); + var tr = document.createElement("tr"); + makeCell("Function", tr, "th"); + var scoreCell = makeCell("Score", tr, "th"); + scoreCell.className = "sort-default score"; + scoreCell.id = "calltable-score-header"; + var keys = []; + for (var i = 0; i < scoreColumns.length; i++) { + var c = scoreColumns[i]; + var cell = makeCell(c.name, tr, "th"); + if (c.description) { + cell.dataset["title"] = c.description; + cell.addEventListener("mouseover", function (evt) { return tooltip.showWithDelay("", evt.target, "top", 100); }); + cell.addEventListener("mouseout", function (evt) { return tooltip.hide(); }); + } + var checkbox = document.createElement("input"); + checkbox.type = "checkbox"; + checkbox.checked = true; + checkbox.className = "score-checkbox"; + checkbox.value = i.toString(); + checkbox.addEventListener("change", scoreSelectCallback); + checkbox.addEventListener("click", function (evt) { return evt.stopPropagation(); }); // prevent tablesorter from firing + cell.insertAdjacentElement("beforeend", checkbox); + } + thead.insertAdjacentElement("beforeend", tr); + // set up configuration controls + var config = document.getElementById("calltable-config"); + // aggregate checkbox + var agg = config.querySelector("#calltable-aggregate"); + agg.checked = true; + agg.addEventListener("change", configChangeCallback); + // context slider + var ctx = config.querySelector("#calltable-context"); + ctx.value = "0"; + var isIE = !!navigator.userAgent.match(/Trident/g) || !!navigator.userAgent.match(/MSIE/g); + ctx.addEventListener(isIE ? "change" : "input", configChangeCallback); + // context count + var ctxn = config.querySelector("#calltable-context-n"); + ctxn.textContent = "0"; + // filter checkbox + var fil = config.querySelector("#calltable-prune"); + fil.checked = true; + fil.addEventListener("change", configChangeCallback); + // collapse rosette checkbox + var clr = config.querySelector("#calltable-collapse-rosette"); + clr.checked = false; + clr.addEventListener("change", configChangeCallback); + // collapse solver checkbox + var cls = config.querySelector("#calltable-collapse-solver"); + cls.checked = false; + cls.addEventListener("change", configChangeCallback); + // signature checkbox + var sig = config.querySelector("#calltable-signature"); + sig.checked = false; + sig.addEventListener("change", configChangeCallback); + // callsites checkbox + var css = config.querySelector("#calltable-callsites"); + css.checked = false; + css.parentElement.style.display = "none"; + css.addEventListener("change", configChangeCallback); + // score checkbox + var sco = config.querySelector("#calltable-show-scoreboxes"); + sco.checked = false; + sco.addEventListener("change", configChangeCallback); + // more config + var more = config.querySelector("#calltable-config-more"); + more.style.display = "none"; + var moreLink = config.querySelector("#calltable-config-toggle-more"); + moreLink.addEventListener("click", toggleMoreCallback); + // attach event handler for table body + var tbody = document.querySelector("#calltable tbody"); + tbody.addEventListener("mouseover", hoverCallback); + tbody.addEventListener("mouseout", hoverCallback); + tableSorter = new Tablesort(document.getElementById("calltable"), { descending: true }); + } + function configChangeCallback(evt) { + var elt = this; + if (elt.id == "calltable-aggregate") { + var lbl = document.getElementById("calltable-callsites").parentElement; + lbl.style.display = (elt.checked ? "none" : ""); + analysis.setAggregate(elt.checked); + } + else if (elt.id == "calltable-context") { + contextDepth = elt.value == elt.max ? -1 : parseInt(elt.value); + var ctxn = document.getElementById("calltable-context-n"); + ctxn.textContent = contextDepth >= 0 ? elt.value : "∞"; + analysis.setContextDepth(contextDepth); + } + else if (elt.id == "calltable-prune") { + pruneSmallRows = elt.checked; + analysis.refresh(); + } + else if (elt.id == "calltable-collapse-rosette") { + analysis.setCollapseRosette(elt.checked); + } + else if (elt.id == "calltable-collapse-solver") { + stackgraph.setCollapseSolverTime(elt.checked); + analysis.setCollapseSolver(elt.checked); + } + else if (elt.id == "calltable-signature") { + useSignatures = elt.checked; + analysis.setSignatures(elt.checked); + } + else if (elt.id == "calltable-callsites") { + useCallsites = elt.checked; + analysis.refresh(); + } + else if (elt.id == "calltable-show-scoreboxes") { + var boxes = document.querySelectorAll("#calltable .score-checkbox"); + for (var i = 0; i < boxes.length; i++) { + boxes[i].style.display = elt.checked ? "initial" : "none"; + if (!elt.checked) { + scoreColumns[i].score = true; + } + } + if (!elt.checked) { + analysis.setColumns(scoreColumns); + } + } + windowResizeCallback(); + } + function toggleMoreCallback(evt) { + evt.preventDefault(); + var more = document.getElementById("calltable-config-more"); + var elt = this; + if (more.style.display == "none") { + more.style.display = "block"; + this.textContent = "[Less]"; + } + else { + more.style.display = "none"; + this.textContent = "[More]"; + } + } + function hoverCallback(evt) { + if (evt.type == "mouseover") { + var tgt = evt.target; + while (tgt && tgt.tagName != "TR") + tgt = tgt.parentElement; + stackgraph.calltableHoverCallback(tgt[DOM_ROW_KEY].allNodes); + } + else { + stackgraph.calltableHoverCallback([]); + } + } + function scoreSelectCallback(evt) { + var elt = this; + var idx = parseInt(elt.value); + scoreColumns[idx].score = elt.checked; + analysis.setColumns(scoreColumns); + } + function stackgraphHoverCallback(node, enter) { + var rows = document.querySelectorAll("#calltable tbody tr"); + for (var i = 0; i < rows.length; i++) { + var row = rows[i]; + row.className = ""; + } + if (enter) { + var hiRow = idToRow[node.id]; + if (hiRow) { + hiRow.className = "calltable-highlight"; + } + } + } + calltable.stackgraphHoverCallback = stackgraphHoverCallback; + function receiveData(state) { + renderTableRows(state.rows, state.aggregate, state.maxScore); + } + function renderPrettySource(source, cell, iscallsite) { + if (iscallsite === void 0) { iscallsite = false; } + if (source) { + var prettySource = getPrettySource(source); + if (iscallsite) + prettySource = "from " + prettySource; + var sourceSpan = makeCell(prettySource, cell, "span"); + sourceSpan.className = "source"; + sourceSpan.title = source; + } + } + function getPrettySource(source) { + var sourceParts = source.split(":"); + if (sourceParts.length != 3) + return source; + var file = sourceParts[0], line = sourceParts[1], col = sourceParts[2]; + var fileParts = file.split("/"); + return fileParts[fileParts.length - 1] + ":" + line; + } + function renderTableRows(rows, aggregate, maxScore) { + // remove old rows + var tbody = document.querySelector("#calltable tbody"); + while (tbody.firstChild) + tbody.removeChild(tbody.firstChild); + idToRow = {}; + // create new rows + for (var _i = 0, rows_1 = rows; _i < rows_1.length; _i++) { + var r = rows_1[_i]; + var row = document.createElement("tr"); + if (pruneSmallRows && r.score < maxScore * PRUNE_SCORE_FACTOR) { + continue; + } + // render the function name + var nameCell = makeCell(r.function, row); + nameCell.className = "name"; + if (useCallsites && !aggregate) { + renderPrettySource(r.node.callsite, nameCell, true); + } + else { + renderPrettySource(r.node.source, nameCell); + } + if (aggregate) { + var txt = r.allNodes.length > 1 ? formatNum(r.allNodes.length) + " calls" : "1 call"; + var numCallsSpan = makeCell(txt, nameCell, "span"); + numCallsSpan.className = "numcalls"; + } + // maybe render the signature + if (useSignatures) { + var inputs = r.node.inputs["signature"] || []; + var outputs = r.node.outputs["signature"] || []; + var istr = inputs.map(function (s) { return s[0].toUpperCase(); }).join(""); + var ostr = outputs.map(function (s) { return s[0].toUpperCase(); }).join(""); + var span = makeCell(istr + "→" + ostr, nameCell, "span"); + span.className = "signature"; + } + // render the call context if requested + if (contextDepth != 0) { + var contextList = document.createElement("ul"); + contextList.className = "context-list"; + var n = contextDepth, curr = r.node; + if (n == -1) { // if "infinite", collapse recursion + var count = 0; + while (curr = curr.parent) { + if (curr.parent && curr.parent.name == curr.name && curr.parent.source == curr.source) { + count += 1; + } + else { + var name_1 = curr.name + (count > 0 ? " ×" + (count + 1) : ""); + var li = makeCell(name_1, contextList, "li"); + count = 0; + renderPrettySource(curr.source, li); + } + } + } + else { + while (n-- != 0 && (curr = curr.parent)) { + var li = makeCell(curr.name, contextList, "li"); + renderPrettySource(curr.source, li); + } + } + nameCell.insertAdjacentElement("beforeend", contextList); + } + // score cell + var scoreBar = document.createElement("div"); + scoreBar.className = "scorebar"; + scoreBar.style.width = (r.score / scoreColumns.length * 100) + "%"; + scoreBar.style.backgroundColor = colorScheme(r.score); + var scoreBarCell = document.createElement("div"); + scoreBarCell.className = "scorecell-bar"; + scoreBarCell.insertAdjacentElement("beforeend", scoreBar); + var scoreSpan = document.createElement("span"); + scoreSpan.textContent = formatNum(r.score, 1, false, true); + scoreSpan.style.color = colorScheme(r.score); + var scoreSpanCell = document.createElement("div"); + scoreSpanCell.className = "scorecell-score"; + scoreSpanCell.insertAdjacentElement("beforeend", scoreSpan); + var scoreCell = makeCell("", row); + scoreCell.dataset["sort"] = r.score.toFixed(16); + scoreCell.insertAdjacentElement("beforeend", scoreSpanCell); + scoreCell.insertAdjacentElement("beforeend", scoreBarCell); + // data columns + for (var _a = 0, _b = r.columns; _a < _b.length; _a++) { + var k = _b[_a]; + makeCell(formatNum(k, 0), row); + } + // attach row object to the row + row[DOM_ROW_KEY] = r; + // record IDs + for (var _c = 0, _d = r.allNodes; _c < _d.length; _c++) { + var n = _d[_c]; + idToRow[n.id] = row; + } + tbody.insertAdjacentElement("beforeend", row); + } + // refresh the sort + tableSorter.refresh(); + } + function makeCell(str, row, type) { + if (type === void 0) { type = "td"; } + var elt = document.createElement(type); + elt.textContent = str; + row.insertAdjacentElement('beforeend', elt); + return elt; + } + })(calltable || (calltable = {})); + var stackgraph; + (function (stackgraph_1) { + var stackgraph; + var colorScheme = makeScoreColorScheme(["#ffeda0", "#fed976", "#feb24c", "#fd8d3c", "#fc4e2a", "#e31a1c", "#bd0026"]); + var solverHighlightColor = "#E3F2FF"; + var lastWidth = 0; + var useDiscontinuities = false; + function initStackGraph() { + // build stackgraph + var STACK_WIDTH = document.getElementById("stackgraph").clientWidth; + var STACK_HEIGHT = 270; + lastWidth = STACK_WIDTH; + stackgraph = d3_stackgraph.stackGraph("#stackgraph"); + stackgraph.width(STACK_WIDTH).height(STACK_HEIGHT); + stackgraph.clickHandler(clickCallback); + stackgraph.hoverHandler(hoverCallback); + stackgraph.color(nodeColorCallback); + stackgraph.textColor(nodeTextColorCallback); + stackgraph.render(); + analysis.registerAnalysisCallback(receiveData); + analysis.registerZoomCallback(receiveZoom); + } + stackgraph_1.initStackGraph = initStackGraph; + function makeHighlightList(state) { + var ret = []; + for (var _i = 0, _a = state.solverCalls; _i < _a.length; _i++) { + var call = _a[_i]; + var finish = typeof call.finish === "undefined" ? state.root.finish : call.finish; + var dt = formatNum(finish - call.start, 1); + var summary = ""; + if (call.type == data.SolverCallType.SOLVE) { + var sat = typeof call.sat === "undefined" ? " (pending)" : (call.sat ? " (SAT)" : " (UNSAT)"); + summary = "Solver call" + sat + ": " + dt + "ms"; + } + else if (call.type == data.SolverCallType.ENCODE) { + summary = "Solver encoding: " + dt + "ms"; + } + else if (call.type == data.SolverCallType.FINITIZE) { + summary = "Solver finitization: " + dt + "ms"; + } + ret.push({ + start: call.start, + finish: finish, + color: solverHighlightColor, + summary: summary + }); + } + return ret; + } + function makeDiscontinuityList(state) { + var ret = []; + for (var _i = 0, _a = state.solverCalls; _i < _a.length; _i++) { + var call = _a[_i]; + var finish = typeof call.finish === "undefined" ? state.root.finish : call.finish; + ret.push([call.start, finish]); + } + return ret; + } + function receiveData(state) { + if (state.root) + stackgraph.data(state.root); + stackgraph.highlights(makeHighlightList(state)); + var discs = useDiscontinuities ? makeDiscontinuityList(state) : []; + stackgraph.discontinuities(discs); + stackgraph.render(); + } + function receiveZoom(node) { + stackgraph.zoom(node); + } + function clickCallback(node) { + analysis.zoomTo(node); + } + function windowResizeCallback() { + var width = document.getElementById("stackgraph").clientWidth; + if (width != lastWidth) { + stackgraph.width(width).render(); + lastWidth = width; + } + } + stackgraph_1.windowResizeCallback = windowResizeCallback; + function calltableHoverCallback(nodes) { + stackgraph.highlightData(nodes); + } + stackgraph_1.calltableHoverCallback = calltableHoverCallback; + function setCollapseSolverTime(use) { + useDiscontinuities = use; + } + stackgraph_1.setCollapseSolverTime = setCollapseSolverTime; + function hoverCallback(node, enter) { + calltable.stackgraphHoverCallback(node, enter); + } + function nodeColorCallback(node) { + return colorScheme(node.score); + } + function nodeTextColorCallback(node) { + var col = d3.color(colorScheme(node.score)).rgb(); + var yiq = ((col.r * 299) + (col.g * 587) + (col.b * 114)) / 1000; + return (yiq >= 128) ? "#222222" : "#eeeeee"; + } + })(stackgraph || (stackgraph = {})); + function formatNum(v, places, sig, always) { + if (places === void 0) { places = 6; } + if (sig === void 0) { sig = false; } + if (always === void 0) { always = false; } + var pl = sig ? (v < 10 ? 1 : 0) : places; + return (v % 1 == 0 && !always) ? v.toString() : v.toFixed(pl); + } + var metadataSet = false; + function setMetadata(state) { + if (!metadataSet && state.metadata != null) { + document.getElementById("profile-source").textContent = state.metadata["source"]; + document.getElementById("profile-time").textContent = state.metadata["time"]; + document.title = "Profile for " + state.metadata["source"] + " generated at " + state.metadata["time"]; + metadataSet = true; + } + } + var spinnerVisible = false; + function toggleStreamingSpinner(state) { + var elt = document.getElementById("progress"); + if (state.streaming && !spinnerVisible) { + elt.style.display = "block"; + spinnerVisible = true; + } + else if (!state.streaming && spinnerVisible) { + elt.style.display = "none"; + spinnerVisible = false; + } + } + var resizing = false; + function windowResizeCallback() { + if (!resizing) { + resizing = true; + stackgraph.windowResizeCallback(); + window.setTimeout(function () { + resizing = false; + }, 50); + } + } + // make a color scheme reflecting that scores > 4 are "very bad" + function makeScoreColorScheme(colors) { + var cs = d3.interpolateRgbBasis(colors); + return function (x) { + if (x > 4.0) + return colors[colors.length - 1]; + return cs(x / 4.0); + }; + } + function bindHelpEventHandlers() { + var helps = document.querySelectorAll(".help"); + for (var i = 0; i < helps.length; i++) { + var elt = helps[i]; + elt.addEventListener("mouseover", function (evt) { return tooltip.showWithDelay("", evt.target, "top", 100); }); + elt.addEventListener("mouseout", function (evt) { return tooltip.hide(); }); + } + } + function init() { + // set up tooltips + tooltip.init(); + bindHelpEventHandlers(); + // set up analysis + analysis.init(); + // initialize UI components + stackgraph.initStackGraph(); + calltable.initCallTable(); + // set up UI callbacks for data state changes + data.registerUpdateCallback(setMetadata); + data.registerUpdateCallback(toggleStreamingSpinner); + // receive all the data + data.readyForData(); + // set initial widths correctly now that data is rendered + windowResizeCallback(); + window.addEventListener("resize", windowResizeCallback); + } + document.addEventListener("DOMContentLoaded", init); +})(profile || (profile = {})); +//# sourceMappingURL=profile.js.map \ No newline at end of file diff --git a/rosette/lib/profile/renderer/report/html/js/profile.ts b/rosette/lib/profile/renderer/report/html/js/profile.ts new file mode 100644 index 00000000..7c674a9b --- /dev/null +++ b/rosette/lib/profile/renderer/report/html/js/profile.ts @@ -0,0 +1,535 @@ +declare var d3; +declare var Tablesort; + +interface Map { [key: string]: T} + +namespace profile { + import CallNode = data.CallNode; + import ProfileState = data.ProfileState; + + namespace calltable { + const scoreColumns: analysis.AnalysisColumn[] = [ + {type: "excl", column: "time", name: "Time (ms)", description: "Total time spent in this function (but not in descendent calls)", score: true}, + {type: "excl", column: "term-count", name: "Term Count", description: "Number of symbolic terms created", score: true}, + {type: "excl", column: "unused-terms", name: "Unused Terms", description: "Number of symbolic terms created that were never used for solving", score: true}, + {type: "excl", column: "union-size", name: "Union Size", description: "Total number of branches in all symbolic unions created", score: true}, + {type: "excl", column: "merge-cases", name: "Merge Cases", description: "Number of branches used during merging", score: true}, + ]; + + const DOM_ROW_KEY = "symproRowObject"; + + const PRUNE_SCORE_FACTOR = 0.01; // < 1% of max score = pruned + + const colorScheme = makeScoreColorScheme(["#000000", "#FD893C", "#D9002C"]); + + var tableSorter; + var contextDepth = 0; + var useSignatures = false; + var useCallsites = false; + var pruneSmallRows = true; + var idToRow: Map = {}; + + export function initCallTable() { + renderTableHeaders(); + analysis.setAggregate(true); + analysis.setColumns(scoreColumns); + analysis.registerAnalysisCallback(receiveData); + } + + function renderTableHeaders(): void { + let thead = document.querySelector("#calltable thead"); + let tr = document.createElement("tr"); + + makeCell("Function", tr, "th"); + + let scoreCell = makeCell("Score", tr, "th"); + scoreCell.className = "sort-default score"; + scoreCell.id = "calltable-score-header"; + + let keys = []; + for (let i = 0; i < scoreColumns.length; i++) { + let c = scoreColumns[i]; + let cell = makeCell(c.name, tr, "th"); + if (c.description) { + cell.dataset["title"] = c.description; + cell.addEventListener("mouseover", (evt) => tooltip.showWithDelay("", evt.target, "top", 100)); + cell.addEventListener("mouseout", (evt) => tooltip.hide()); + } + let checkbox = document.createElement("input"); + checkbox.type = "checkbox"; + checkbox.checked = true; + checkbox.className = "score-checkbox"; + checkbox.value = i.toString(); + checkbox.addEventListener("change", scoreSelectCallback); + checkbox.addEventListener("click", (evt) => evt.stopPropagation()); // prevent tablesorter from firing + cell.insertAdjacentElement("beforeend", checkbox); + } + + thead.insertAdjacentElement("beforeend", tr); + + // set up configuration controls + let config = document.getElementById("calltable-config")!; + // aggregate checkbox + let agg = config.querySelector("#calltable-aggregate"); + agg.checked = true; + agg.addEventListener("change", configChangeCallback); + // context slider + let ctx = config.querySelector("#calltable-context"); + ctx.value = "0"; + let isIE = !!navigator.userAgent.match(/Trident/g) || !!navigator.userAgent.match(/MSIE/g); + ctx.addEventListener(isIE ? "change" : "input", configChangeCallback); + // context count + let ctxn = config.querySelector("#calltable-context-n"); + ctxn.textContent = "0"; + // filter checkbox + let fil = config.querySelector("#calltable-prune"); + fil.checked = true; + fil.addEventListener("change", configChangeCallback); + // collapse rosette checkbox + let clr = config.querySelector("#calltable-collapse-rosette"); + clr.checked = false; + clr.addEventListener("change", configChangeCallback); + // collapse solver checkbox + let cls = config.querySelector("#calltable-collapse-solver"); + cls.checked = false; + cls.addEventListener("change", configChangeCallback); + // signature checkbox + let sig = config.querySelector("#calltable-signature"); + sig.checked = false; + sig.addEventListener("change", configChangeCallback); + // callsites checkbox + let css = config.querySelector("#calltable-callsites"); + css.checked = false; + css.parentElement.style.display = "none"; + css.addEventListener("change", configChangeCallback); + // score checkbox + let sco = config.querySelector("#calltable-show-scoreboxes"); + sco.checked = false; + sco.addEventListener("change", configChangeCallback); + // more config + let more = config.querySelector("#calltable-config-more"); + more.style.display = "none"; + let moreLink = config.querySelector("#calltable-config-toggle-more"); + moreLink.addEventListener("click", toggleMoreCallback); + + // attach event handler for table body + let tbody = document.querySelector("#calltable tbody"); + tbody.addEventListener("mouseover", hoverCallback); + tbody.addEventListener("mouseout", hoverCallback); + + tableSorter = new Tablesort(document.getElementById("calltable"), { descending: true }); + } + + function configChangeCallback(evt: Event) { + let elt: HTMLInputElement = this; + if (elt.id == "calltable-aggregate") { + let lbl = document.getElementById("calltable-callsites").parentElement; + lbl.style.display = (elt.checked ? "none" : ""); + analysis.setAggregate(elt.checked); + } else if (elt.id == "calltable-context") { + contextDepth = elt.value == elt.max ? -1 : parseInt(elt.value); + let ctxn = document.getElementById("calltable-context-n"); + ctxn.textContent = contextDepth >= 0 ? elt.value : "∞"; + analysis.setContextDepth(contextDepth); + } else if (elt.id == "calltable-prune") { + pruneSmallRows = elt.checked; + analysis.refresh(); + } else if (elt.id == "calltable-collapse-rosette") { + analysis.setCollapseRosette(elt.checked); + } else if (elt.id == "calltable-collapse-solver") { + stackgraph.setCollapseSolverTime(elt.checked); + analysis.setCollapseSolver(elt.checked); + } else if (elt.id == "calltable-signature") { + useSignatures = elt.checked; + analysis.setSignatures(elt.checked); + } else if (elt.id == "calltable-callsites") { + useCallsites = elt.checked; + analysis.refresh(); + } else if (elt.id == "calltable-show-scoreboxes") { + let boxes = >document.querySelectorAll("#calltable .score-checkbox"); + for (let i = 0; i < boxes.length; i++) { + boxes[i].style.display = elt.checked ? "initial" : "none"; + if (!elt.checked) { + scoreColumns[i].score = true; + } + } + if (!elt.checked) { + analysis.setColumns(scoreColumns); + } + } + windowResizeCallback(); + } + + function toggleMoreCallback(evt: Event) { + evt.preventDefault(); + let more = document.getElementById("calltable-config-more"); + let elt: HTMLAnchorElement = this; + if (more.style.display == "none") { + more.style.display = "block"; + this.textContent = "[Less]"; + } else { + more.style.display = "none"; + this.textContent = "[More]"; + } + } + + function hoverCallback(evt: Event) { + if (evt.type == "mouseover") { + let tgt = evt.target; + while (tgt && tgt.tagName != "TR") tgt = tgt.parentElement; + + stackgraph.calltableHoverCallback(tgt[DOM_ROW_KEY].allNodes); + } + else { + stackgraph.calltableHoverCallback([]); + } + } + + function scoreSelectCallback(evt: Event) { + let elt: HTMLInputElement = this; + let idx = parseInt(elt.value); + scoreColumns[idx].score = elt.checked; + analysis.setColumns(scoreColumns); + } + + export function stackgraphHoverCallback(node: CallNode, enter: boolean) { + let rows = document.querySelectorAll("#calltable tbody tr"); + for (let i = 0; i < rows.length; i++) { + let row = rows[i]; + row.className = ""; + } + if (enter) { + let hiRow = idToRow[node.id]; + if (hiRow) { + hiRow.className = "calltable-highlight"; + } + } + } + + function receiveData(state: analysis.ProfileData) { + renderTableRows(state.rows, state.aggregate, state.maxScore); + } + + function renderPrettySource(source: string, cell: HTMLElement, iscallsite=false) { + if (source) { + let prettySource = getPrettySource(source); + if (iscallsite) prettySource = "from " + prettySource; + let sourceSpan = makeCell(prettySource, cell, "span"); + sourceSpan.className = "source"; + sourceSpan.title = source; + } + } + + function getPrettySource(source: string) { + let sourceParts = source.split(":"); + if (sourceParts.length != 3) return source; + let [file, line, col] = sourceParts; + let fileParts = file.split("/"); + return fileParts[fileParts.length-1] + ":" + line; + } + + function renderTableRows(rows: analysis.AnalysisRow[], aggregate: boolean, maxScore: number) { + // remove old rows + let tbody = document.querySelector("#calltable tbody"); + while (tbody.firstChild) tbody.removeChild(tbody.firstChild); + idToRow = {}; + + // create new rows + for (let r of rows) { + let row = document.createElement("tr"); + + if (pruneSmallRows && r.score < maxScore*PRUNE_SCORE_FACTOR) { + continue; + } + + // render the function name + let nameCell = makeCell(r.function, row); + nameCell.className = "name"; + if (useCallsites && !aggregate) { + renderPrettySource(r.node.callsite, nameCell, true); + } else { + renderPrettySource(r.node.source, nameCell); + } + if (aggregate) { + let txt = r.allNodes.length > 1 ? formatNum(r.allNodes.length) + " calls" : "1 call"; + let numCallsSpan = makeCell(txt, nameCell, "span"); + numCallsSpan.className = "numcalls"; + } + + // maybe render the signature + if (useSignatures) { + let inputs: string[] = r.node.inputs["signature"] || []; + let outputs: string[] = r.node.outputs["signature"] || []; + let istr = inputs.map((s) => s[0].toUpperCase()).join(""); + let ostr = outputs.map((s) => s[0].toUpperCase()).join(""); + let span = makeCell(istr + "→" + ostr, nameCell, "span"); + span.className = "signature"; + } + + // render the call context if requested + if (contextDepth != 0) { + let contextList = document.createElement("ul"); + contextList.className = "context-list"; + let n = contextDepth, curr = r.node; + if (n == -1) { // if "infinite", collapse recursion + let count = 0; + while (curr = curr.parent) { + if (curr.parent && curr.parent.name == curr.name && curr.parent.source == curr.source) { + count += 1; + } else { + let name = curr.name + (count > 0 ? " ×" + (count+1) : ""); + let li = makeCell(name, contextList, "li"); + count = 0; + renderPrettySource(curr.source, li); + } + } + } else { + while (n-- != 0 && (curr = curr.parent)) { + let li = makeCell(curr.name, contextList, "li"); + renderPrettySource(curr.source, li); + } + } + nameCell.insertAdjacentElement("beforeend", contextList); + } + + // score cell + let scoreBar = document.createElement("div"); + scoreBar.className = "scorebar"; + scoreBar.style.width = (r.score/scoreColumns.length * 100) + "%"; + scoreBar.style.backgroundColor = colorScheme(r.score); + let scoreBarCell = document.createElement("div"); + scoreBarCell.className = "scorecell-bar"; + scoreBarCell.insertAdjacentElement("beforeend", scoreBar); + let scoreSpan = document.createElement("span"); + scoreSpan.textContent = formatNum(r.score, 1, false, true); + scoreSpan.style.color = colorScheme(r.score); + let scoreSpanCell = document.createElement("div"); + scoreSpanCell.className = "scorecell-score"; + scoreSpanCell.insertAdjacentElement("beforeend", scoreSpan); + let scoreCell = makeCell("", row); + scoreCell.dataset["sort"] = r.score.toFixed(16); + scoreCell.insertAdjacentElement("beforeend", scoreSpanCell); + scoreCell.insertAdjacentElement("beforeend", scoreBarCell); + + // data columns + for (let k of r.columns) { + makeCell(formatNum(k, 0), row); + } + + // attach row object to the row + row[DOM_ROW_KEY] = r; + + // record IDs + for (let n of r.allNodes) { + idToRow[n.id] = row; + } + + tbody.insertAdjacentElement("beforeend", row); + } + + // refresh the sort + tableSorter.refresh(); + } + + function makeCell(str: string, row: HTMLElement, type: string = "td"): HTMLElement { + let elt = document.createElement(type); + elt.textContent = str; + row.insertAdjacentElement('beforeend', elt); + return elt; + } + } + + namespace stackgraph { + var stackgraph: d3_stackgraph.StackGraph; + const colorScheme = makeScoreColorScheme(["#ffeda0","#fed976","#feb24c","#fd8d3c","#fc4e2a","#e31a1c","#bd0026"]); + var solverHighlightColor = "#E3F2FF"; + var lastWidth = 0; + var useDiscontinuities = false; + + export function initStackGraph() { + // build stackgraph + const STACK_WIDTH = document.getElementById("stackgraph")!.clientWidth; + const STACK_HEIGHT = 270; + lastWidth = STACK_WIDTH; + stackgraph = d3_stackgraph.stackGraph("#stackgraph"); + stackgraph.width(STACK_WIDTH).height(STACK_HEIGHT); + stackgraph.clickHandler(clickCallback); + stackgraph.hoverHandler(hoverCallback); + stackgraph.color(nodeColorCallback); + stackgraph.textColor(nodeTextColorCallback); + stackgraph.render(); + + analysis.registerAnalysisCallback(receiveData); + analysis.registerZoomCallback(receiveZoom); + } + + function makeHighlightList(state: analysis.ProfileData) { + let ret = []; + for (let call of state.solverCalls) { + let finish = typeof call.finish === "undefined" ? state.root.finish : call.finish; + let dt = formatNum(finish - call.start, 1); + let summary = ""; + if (call.type == data.SolverCallType.SOLVE) { + let sat = typeof call.sat === "undefined" ? " (pending)" : (call.sat ? " (SAT)" : " (UNSAT)"); + summary = `Solver call${sat}: ${dt}ms`; + } else if (call.type == data.SolverCallType.ENCODE) { + summary = `Solver encoding: ${dt}ms`; + } else if (call.type == data.SolverCallType.FINITIZE) { + summary = `Solver finitization: ${dt}ms`; + } + ret.push({ + start: call.start, + finish: finish, + color: solverHighlightColor, + summary: summary + }); + } + return ret; + } + + function makeDiscontinuityList(state: analysis.ProfileData) { + let ret = [] + for (let call of state.solverCalls) { + let finish = typeof call.finish === "undefined" ? state.root.finish : call.finish; + ret.push([call.start, finish]); + } + return ret; + } + + function receiveData(state: analysis.ProfileData) { + if (state.root) stackgraph.data(state.root); + stackgraph.highlights(makeHighlightList(state)); + let discs = useDiscontinuities ? makeDiscontinuityList(state) : []; + stackgraph.discontinuities(discs); + stackgraph.render(); + } + + function receiveZoom(node: CallNode) { + stackgraph.zoom(node); + } + + function clickCallback(node: CallNode): void { + analysis.zoomTo(node); + } + + export function windowResizeCallback() { // caller should handle debouncing + let width = document.getElementById("stackgraph")!.clientWidth; + if (width != lastWidth) { + stackgraph.width(width).render(); + lastWidth = width; + } + } + + export function calltableHoverCallback(nodes: CallNode[]): void { + stackgraph.highlightData(nodes); + } + + export function setCollapseSolverTime(use: boolean) { + useDiscontinuities = use; + } + + function hoverCallback(node: CallNode, enter: boolean): void { + calltable.stackgraphHoverCallback(node, enter); + } + + function nodeColorCallback(node: CallNode): string { + return colorScheme(node.score); + } + function nodeTextColorCallback(node: CallNode): string { + let col = d3.color(colorScheme(node.score)).rgb(); + let yiq = ((col.r*299) + (col.g*587) + (col.b*114)) / 1000; + return (yiq >= 128) ? "#222222" : "#eeeeee"; + } + } + + + function formatNum(v: number, places: number = 6, sig: boolean = false, always: boolean = false): string { + let pl = sig ? (v < 10 ? 1 : 0) : places; + return (v % 1 == 0 && !always) ? v.toString() : v.toFixed(pl); + } + + + var metadataSet = false; + function setMetadata(state: ProfileState) { + if (!metadataSet && state.metadata != null) { + document.getElementById("profile-source").textContent = state.metadata["source"]; + document.getElementById("profile-time").textContent = state.metadata["time"]; + document.title = "Profile for " + state.metadata["source"] + " generated at " + state.metadata["time"]; + metadataSet = true; + } + } + + + var spinnerVisible = false; + function toggleStreamingSpinner(state: ProfileState) { + let elt = document.getElementById("progress"); + if (state.streaming && !spinnerVisible) { + elt.style.display = "block"; + spinnerVisible = true; + } else if (!state.streaming && spinnerVisible) { + elt.style.display = "none"; + spinnerVisible = false; + } + } + + + var resizing = false; + function windowResizeCallback() { + if (!resizing) { + resizing = true; + stackgraph.windowResizeCallback(); + window.setTimeout(() => { + resizing = false; + }, 50); + } + } + + + // make a color scheme reflecting that scores > 4 are "very bad" + function makeScoreColorScheme(colors: string[]) { + let cs = d3.interpolateRgbBasis(colors); + return function(x) { + if (x > 4.0) return colors[colors.length-1]; + return cs(x/4.0); + }; + } + + + function bindHelpEventHandlers() { + var helps = document.querySelectorAll(".help"); + for (var i = 0; i < helps.length; i++) { + let elt = helps[i]; + elt.addEventListener("mouseover", (evt) => tooltip.showWithDelay("", evt.target, "top", 100)); + elt.addEventListener("mouseout", (evt) => tooltip.hide()); + } + } + + + function init() { + // set up tooltips + tooltip.init(); + bindHelpEventHandlers(); + + // set up analysis + analysis.init(); + + // initialize UI components + stackgraph.initStackGraph(); + calltable.initCallTable(); + + // set up UI callbacks for data state changes + data.registerUpdateCallback(setMetadata); + data.registerUpdateCallback(toggleStreamingSpinner); + + + // receive all the data + data.readyForData(); + + + // set initial widths correctly now that data is rendered + windowResizeCallback(); + window.addEventListener("resize", windowResizeCallback); + } + + document.addEventListener("DOMContentLoaded", init); +} \ No newline at end of file diff --git a/rosette/lib/profile/renderer/report/html/js/tablesort.js b/rosette/lib/profile/renderer/report/html/js/tablesort.js new file mode 100644 index 00000000..ebaa5380 --- /dev/null +++ b/rosette/lib/profile/renderer/report/html/js/tablesort.js @@ -0,0 +1,296 @@ +/*! + * tablesort v4.0.1 (2016-07-23) + * http://tristen.ca/tablesort/demo/ + * Copyright (c) 2016 ; Licensed MIT +*/ +;(function() { + function Tablesort(el, options) { + if (!(this instanceof Tablesort)) return new Tablesort(el, options); + + if (!el || el.tagName !== 'TABLE') { + throw new Error('Element must be a table'); + } + this.init(el, options || {}); + } + + var sortOptions = []; + + var createEvent = function(name) { + var evt; + + if (!window.CustomEvent || typeof window.CustomEvent !== 'function') { + evt = document.createEvent('CustomEvent'); + evt.initCustomEvent(name, false, false, undefined); + } else { + evt = new CustomEvent(name); + } + + return evt; + }; + + var getInnerText = function(el) { + return el.getAttribute('data-sort') || el.textContent || el.innerText || ''; + }; + + // Default sort method if no better sort method is found + var caseInsensitiveSort = function(a, b) { + a = a.toLowerCase(); + b = b.toLowerCase(); + + if (a === b) return 0; + if (a < b) return 1; + + return -1; + }; + + // Stable sort function + // If two elements are equal under the original sort function, + // then there relative order is reversed + var stabilize = function(sort, antiStabilize) { + return function(a, b) { + var unstableResult = sort(a.td, b.td); + + if (unstableResult === 0) { + if (antiStabilize) return b.index - a.index; + return a.index - b.index; + } + + return unstableResult; + }; + }; + + Tablesort.extend = function(name, pattern, sort) { + if (typeof pattern !== 'function' || typeof sort !== 'function') { + throw new Error('Pattern and sort must be a function'); + } + + sortOptions.push({ + name: name, + pattern: pattern, + sort: sort + }); + }; + + Tablesort.prototype = { + + init: function(el, options) { + var that = this, + firstRow, + defaultSort, + i, + cell; + + that.table = el; + that.thead = false; + that.options = options; + + if (el.rows && el.rows.length > 0) { + if (el.tHead && el.tHead.rows.length > 0) { + for (i = 0; i < el.tHead.rows.length; i++) { + if (el.tHead.rows[i].classList.contains("sort-row")) { + firstRow = el.tHead.rows[i]; + break; + } + } + if (!firstRow) { + firstRow = el.tHead.rows[el.tHead.rows.length - 1]; + } + that.thead = true; + } else { + firstRow = el.rows[0]; + } + } + + if (!firstRow) return; + + var onClick = function() { + if (that.current && that.current !== this) { + that.current.classList.remove('sort-up'); + that.current.classList.remove('sort-down'); + } + + that.current = this; + that.sortTable(this); + }; + + // Assume first row is the header and attach a click handler to each. + for (i = 0; i < firstRow.cells.length; i++) { + cell = firstRow.cells[i]; + if (!cell.classList.contains('no-sort')) { + cell.classList.add('sort-header'); + cell.tabindex = 0; + cell.addEventListener('click', onClick, false); + + if (cell.classList.contains('sort-default')) { + defaultSort = cell; + } + } + } + + if (defaultSort) { + that.current = defaultSort; + that.sortTable(defaultSort); + } + }, + + sortTable: function(header, update) { + var that = this, + column = header.cellIndex, + sortFunction = caseInsensitiveSort, + item = '', + items = [], + i = that.thead ? 0 : 1, + sortDir, + sortMethod = header.getAttribute('data-sort-method'), + sortOrder = header.getAttribute('data-sort-order'); + + that.table.dispatchEvent(createEvent('beforeSort')); + + // If updating an existing sort `sortDir` should remain unchanged. + if (update) { + sortDir = header.classList.contains('sort-up') ? 'sort-up' : 'sort-down'; + } else { + if (header.classList.contains('sort-up')) { + sortDir = 'sort-down'; + } else if (header.classList.contains('sort-down')) { + sortDir = 'sort-up'; + } else if (sortOrder === 'asc') { + sortDir = 'sort-down'; + } else if (sortOrder === 'desc') { + sortDir = 'sort-up'; + } else { + sortDir = that.options.descending ? 'sort-up' : 'sort-down'; + } + + header.classList.remove(sortDir === 'sort-down' ? 'sort-up' : 'sort-down'); + header.classList.add(sortDir); + } + + if (that.table.rows.length < 2) return; + + // If we force a sort method, it is not necessary to check rows + if (!sortMethod) { + while (items.length < 3 && i < that.table.tBodies[0].rows.length) { + item = getInnerText(that.table.tBodies[0].rows[i].cells[column]); + item = item.trim(); + + if (item.length > 0) { + items.push(item); + } + + i++; + } + + if (!items) return; + } + + for (i = 0; i < sortOptions.length; i++) { + item = sortOptions[i]; + + if (sortMethod) { + if (item.name === sortMethod) { + sortFunction = item.sort; + break; + } + } else if (items.every(item.pattern)) { + sortFunction = item.sort; + break; + } + } + + that.col = column; + + for (i = 0; i < that.table.tBodies.length; i++) { + var newRows = [], + noSorts = {}, + j, + totalRows = 0, + noSortsSoFar = 0; + + if (that.table.tBodies[i].rows.length < 2) continue; + + for (j = 0; j < that.table.tBodies[i].rows.length; j++) { + item = that.table.tBodies[i].rows[j]; + if (item.classList.contains('no-sort')) { + // keep no-sorts in separate list to be able to insert + // them back at their original position later + noSorts[totalRows] = item; + } else { + // Save the index for stable sorting + newRows.push({ + tr: item, + td: getInnerText(item.cells[that.col]), + index: totalRows + }); + } + totalRows++; + } + // Before we append should we reverse the new array or not? + // If we reverse, the sort needs to be `anti-stable` so that + // the double negatives cancel out + if (sortDir === 'sort-down') { + newRows.sort(stabilize(sortFunction, true)); + newRows.reverse(); + } else { + newRows.sort(stabilize(sortFunction, false)); + } + + // append rows that already exist rather than creating new ones + for (j = 0; j < totalRows; j++) { + if (noSorts[j]) { + // We have a no-sort row for this position, insert it here. + item = noSorts[j]; + noSortsSoFar++; + } else { + item = newRows[j - noSortsSoFar].tr; + } + + // appendChild(x) moves x if already present somewhere else in the DOM + that.table.tBodies[i].appendChild(item); + } + } + + that.table.dispatchEvent(createEvent('afterSort')); + }, + + refresh: function() { + if (this.current !== undefined) { + this.sortTable(this.current, true); + } + } + }; + + if (typeof module !== 'undefined' && module.exports) { + module.exports = Tablesort; + } else { + window.Tablesort = Tablesort; + } +})(); + +// number sort +(function(){ + var cleanNumber = function(i) { + return i.replace(/[^\-?0-9.]/g, ''); + }, + + compareNumber = function(a, b) { + a = parseFloat(a); + b = parseFloat(b); + + a = isNaN(a) ? 0 : a; + b = isNaN(b) ? 0 : b; + + return a - b; + }; + + Tablesort.extend('number', function(item) { + return item.match(/^-?[£\x24Û¢´€]?\d+\s*([,\.]\d{0,2})/) || // Prefixed currency + item.match(/^-?\d+\s*([,\.]\d{0,2})?[£\x24Û¢´€]/) || // Suffixed currency + item.match(/^-?(\d)*-?([,\.]){0,1}-?(\d)+([E,e][\-+][\d]+)?%?$/); // Number + }, function(a, b) { + a = cleanNumber(a); + b = cleanNumber(b); + + return compareNumber(b, a); + }); +}()); \ No newline at end of file diff --git a/rosette/lib/profile/renderer/report/html/js/tooltip.js b/rosette/lib/profile/renderer/report/html/js/tooltip.js new file mode 100644 index 00000000..09ab4f40 --- /dev/null +++ b/rosette/lib/profile/renderer/report/html/js/tooltip.js @@ -0,0 +1,81 @@ +var tooltip; +(function (tooltip) { + var tooltipDiv; + var PADDING = 4; + var willShow = false; + function init() { + tooltipDiv = document.createElement("div"); + tooltipDiv.className = "tooltip"; + tooltipDiv.style.position = "absolute"; + tooltipDiv.style.top = "0px"; + tooltipDiv.style.left = "0px"; + tooltipDiv.style.display = "none"; + document.body.insertAdjacentElement("beforeend", tooltipDiv); + } + tooltip.init = init; + function show(title, elt, side) { + if (side != "top") { + console.error("only 'top' side tooltip supported"); + return; + } + if (title == "") { + title = elt.dataset["title"]; + } + tooltipDiv.innerText = title; + tooltipDiv.style.display = "block"; + var tRect = tooltipDiv.getBoundingClientRect(); + var eRect = getBoundingViewportRect(elt); + var vRect = getViewportRect(); + var x = eRect.left + eRect.width / 2 - tRect.width / 2; + if (x < vRect.left) { + x = vRect.left + PADDING; + } + else if (x + tRect.width > vRect.right) { + x = vRect.right - tRect.width - PADDING; + } + var y = eRect.top - tRect.height - PADDING; + if (y < vRect.top) { + y = vRect.top + PADDING; + } + else if (y + tRect.height > vRect.bottom) { + y = vRect.bottom - tRect.height - PADDING; + } + tooltipDiv.style.transform = "translate(" + x + "px," + y + "px)"; + willShow = false; + } + tooltip.show = show; + function showWithDelay(title, elt, side, delay) { + willShow = true; // make sure user didn't leave the element during the delay + setTimeout(function () { if (willShow) + show(title, elt, side); }, delay); + } + tooltip.showWithDelay = showWithDelay; + function getBoundingViewportRect(elt) { + var rect = elt.getBoundingClientRect(); + return { + top: rect.top + window.pageYOffset, + bottom: rect.bottom + window.pageYOffset, + left: rect.left + window.pageXOffset, + right: rect.right + window.pageXOffset, + width: rect.width, + height: rect.height + }; + } + function getViewportRect() { + var rect = document.documentElement.getBoundingClientRect(); + return { + top: window.pageYOffset, + left: window.pageXOffset, + bottom: window.pageYOffset + rect.height, + right: window.pageXOffset + rect.width, + width: rect.width, + height: rect.height + }; + } + function hide() { + tooltipDiv.style.display = "none"; + willShow = false; + } + tooltip.hide = hide; +})(tooltip || (tooltip = {})); +//# sourceMappingURL=tooltip.js.map \ No newline at end of file diff --git a/rosette/lib/profile/renderer/report/html/js/tooltip.ts b/rosette/lib/profile/renderer/report/html/js/tooltip.ts new file mode 100644 index 00000000..a677179f --- /dev/null +++ b/rosette/lib/profile/renderer/report/html/js/tooltip.ts @@ -0,0 +1,83 @@ +namespace tooltip { + var tooltipDiv: HTMLDivElement; + const PADDING = 4; + var willShow = false; + + export function init() { + tooltipDiv = document.createElement("div"); + tooltipDiv.className = "tooltip"; + tooltipDiv.style.position = "absolute"; + tooltipDiv.style.top = "0px"; + tooltipDiv.style.left = "0px"; + tooltipDiv.style.display = "none"; + document.body.insertAdjacentElement("beforeend", tooltipDiv); + } + + export function show(title: string, elt: HTMLElement, side: string) { + if (side != "top") { + console.error("only 'top' side tooltip supported"); + return; + } + if (title == "") { + title = elt.dataset["title"]; + } + + tooltipDiv.innerText = title; + tooltipDiv.style.display = "block"; + let tRect = tooltipDiv.getBoundingClientRect(); + let eRect = getBoundingViewportRect(elt); + let vRect = getViewportRect(); + + let x = eRect.left + eRect.width/2 - tRect.width/2; + if (x < vRect.left) { + x = vRect.left + PADDING; + } else if (x + tRect.width > vRect.right) { + x = vRect.right - tRect.width - PADDING; + } + + let y = eRect.top - tRect.height - PADDING; + if (y < vRect.top) { + y = vRect.top + PADDING; + } else if (y + tRect.height > vRect.bottom) { + y = vRect.bottom - tRect.height - PADDING; + } + + tooltipDiv.style.transform = `translate(${x}px,${y}px)`; + + willShow = false; + } + + export function showWithDelay(title: string, elt: HTMLElement, side: string, delay: number) { + willShow = true; // make sure user didn't leave the element during the delay + setTimeout(() => { if (willShow) show(title, elt, side) }, delay); + } + + function getBoundingViewportRect(elt: HTMLElement) { + let rect = elt.getBoundingClientRect(); + return { + top: rect.top + window.pageYOffset, + bottom: rect.bottom + window.pageYOffset, + left: rect.left + window.pageXOffset, + right: rect.right + window.pageXOffset, + width: rect.width, + height: rect.height + }; + } + + function getViewportRect() { + let rect = document.documentElement.getBoundingClientRect(); + return { + top: window.pageYOffset, + left: window.pageXOffset, + bottom: window.pageYOffset + rect.height, + right: window.pageXOffset + rect.width, + width: rect.width, + height: rect.height + }; + } + + export function hide() { + tooltipDiv.style.display = "none"; + willShow = false; + } +} \ No newline at end of file diff --git a/rosette/lib/profile/renderer/report/html/js/tsconfig.json b/rosette/lib/profile/renderer/report/html/js/tsconfig.json new file mode 100644 index 00000000..16449541 --- /dev/null +++ b/rosette/lib/profile/renderer/report/html/js/tsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "sourceMap": true + } + } \ No newline at end of file diff --git a/rosette/lib/profile/renderer/report/html/profile.html b/rosette/lib/profile/renderer/report/html/profile.html new file mode 100644 index 00000000..9d8659cc --- /dev/null +++ b/rosette/lib/profile/renderer/report/html/profile.html @@ -0,0 +1,41 @@ +Profile + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/rosette/lib/profile/renderer/report/solver.rkt b/rosette/lib/profile/renderer/report/solver.rkt new file mode 100644 index 00000000..e84c9be0 --- /dev/null +++ b/rosette/lib/profile/renderer/report/solver.rkt @@ -0,0 +1,41 @@ +#lang racket + +(require "generic.rkt" + "../../data.rkt" "../../reporter.rkt") +(provide make-solver-calls-component) + +(define (make-solver-calls-component options) + (profile-solver-calls-component)) + +(struct profile-solver-calls-component () #:transparent + #:methods gen:report-component + [(define (init-component self) + void) + (define (receive-data self events) + (list (hash 'type "solver-calls" + 'events (get-solver-events events))))]) + +(define (get-solver-events events) + ; get the solver start and finish events in order + (define filtered-events + (filter (lambda (e) (or (profile-event-solve-start? e) + (profile-event-solve-finish? e) + (profile-event-encode? e) + (profile-event-finitize? e))) + events)) + + (for/list ([e filtered-events]) + (let ([time (metrics-ref (profile-event-metrics e) 'time)]) + (match e + [(profile-event-solve-start _) + (hash 'part "solver" 'type "start" 'time time)] + [(profile-event-solve-finish _ sat?) + (hash 'part "solver" 'type "finish" 'time time 'sat sat?)] + [(profile-event-encode-start _) + (hash 'part "encode" 'type "start" 'time time)] + [(profile-event-encode-finish _) + (hash 'part "encode" 'type "finish" 'time time)] + [(profile-event-finitize-start _) + (hash 'part "finitize" 'type "start" 'time time)] + [(profile-event-finitize-finish _) + (hash 'part "finitize" 'type "finish" 'time time)])))) diff --git a/rosette/lib/profile/renderer/report/terms.rkt b/rosette/lib/profile/renderer/report/terms.rkt new file mode 100644 index 00000000..7469e988 --- /dev/null +++ b/rosette/lib/profile/renderer/report/terms.rkt @@ -0,0 +1,64 @@ +#lang racket + +(require (only-in rosette/base/core/term expression) + "generic.rkt" + "../../data.rkt" "../../reporter.rkt") +(provide make-terms-component) + +; The terms component analyzes the program's use of symbolic terms. +; Right now, the only analysis is to see which terms never reach the solver. + +(define (make-terms-component options) + (profile-terms-component (make-hasheq) (make-hasheq) '() (mutable-seteq))) + +(struct profile-terms-component (term->term-node term->creator [stack #:mutable] reached-solver) #:transparent + #:methods gen:report-component + [(define (init-component self) + void) + (define (receive-data self events) + (get-term-analysis-messages self events))]) + +; a node in the term graph contains the term itself, its subterms (as nodes), and its creator +(struct term-node (term subterms creator)) + +; return a list of messages relating to terms -- which are unused etc +(define (get-term-analysis-messages cmpt events) + (match-define (profile-terms-component term->term-node term->creator stack reached-solver) cmpt) + + (for ([e (in-list events)]) + (match e + [(profile-event-enter met id loc proc in) + (set! stack (cons id stack))] + [(profile-event-exit met out) + (set! stack (cdr stack))] + [(profile-event-term-new met t) + (define children ; record all the term's children + (match t + [(expression op elts ...) + (for/list ([e elts] #:when (hash-has-key? term->term-node e)) + (hash-ref term->term-node e))] + [_ '()])) + (define node (term-node t children (car stack))) + (hash-set! term->term-node t node) + (hash-set! term->creator t (car stack))] + [(profile-event-solve-encode met lst) + (for* ([assts lst][a assts] #:when (hash-has-key? term->term-node a)) + (define tn (hash-ref term->term-node a)) + (let loop ([tn tn]) + (unless (set-member? reached-solver tn) + (set-add! reached-solver tn) + (for ([tn* (term-node-subterms tn)]) (loop tn*)))))] + [_ (void)])) + + ; update the call stack after processing these events + (set-profile-terms-component-stack! cmpt stack) + + ; determine the sources of terms that did not reach the solver + (define unused-term-sources (make-hasheq)) + (for ([(t tn) (in-hash term->term-node)] #:unless (set-member? reached-solver tn)) + (define cid (term-node-creator tn)) + (hash-set! unused-term-sources cid (add1 (hash-ref unused-term-sources cid 0)))) + + ; emit that message + (list (hash 'type "unused-terms" + 'data (for/list ([(cid n) (in-hash unused-term-sources)]) (list cid n))))) diff --git a/rosette/lib/profile/renderer/report/ws-server.rkt b/rosette/lib/profile/renderer/report/ws-server.rkt new file mode 100644 index 00000000..4ee12441 --- /dev/null +++ b/rosette/lib/profile/renderer/report/ws-server.rkt @@ -0,0 +1,63 @@ +#lang racket/base + +; this is a patched version of net/rfc6455's server.rkt +; to correctly handle keyword arguments in ws-serve + +;; Convenience interface for starting a simple web service. +;; Roughly compatible with net/websocket/server's ws-serve. + +;; Copyright (c) 2013 Tony Garnock-Jones +;; +;; This module is distributed under the GNU Lesser General Public +;; License (LGPL). This means that you can link it into proprietary +;; applications, provided you follow the rules stated in the LGPL. You +;; can also modify this module; if you distribute a modified version, +;; you must distribute it under the terms of the LGPL, which in +;; particular means that you must release the source code for the +;; modified software. See http://www.gnu.org/licenses/lgpl-3.0.txt for +;; more information. + +(require racket/match) +(require web-server/web-server) +(require web-server/http/request-structs) +(require web-server/http/response) +(require web-server/http/response-structs) +(require web-server/dispatchers/dispatch) +(require net/url) +(require net/rfc6455/dispatcher) +(require net/rfc6455/service-mapper) +(require net/rfc6455/conn-api) +(require (except-in net/rfc6455 ws-serve)) + +(provide ws-serve (all-from-out net/rfc6455)) + +(define (transpose xss) (apply map list xss)) + +(define (guard-dispatcher d) + (lambda (conn req) + (with-handlers [(exn:dispatcher? + (lambda (e) + (log-info "Bad WS request, ~a ~a" + (request-method req) + (url->string (request-uri req))) + (output-response/method + conn + (response 400 #"Bad WebSocket request" (current-seconds) #f '() void) + (request-method req))))] + (d conn req)))) + +(define ws-serve + (procedure-rename + (make-keyword-procedure + (lambda (keys vals conn-dispatch . rest) + (define kvs (map list keys vals)) + (define conn-headers-cell (assq '#:conn-headers kvs)) + (define conn-headers (and conn-headers-cell (cadr conn-headers-cell))) + (define dispatcher (make-general-websockets-dispatcher conn-dispatch conn-headers)) + (match-define (list keys1 vals1) (transpose (remq conn-headers-cell kvs))) + (keyword-apply serve + keys1 + vals1 + rest + #:dispatch (guard-dispatcher dispatcher)))) + 'ws-serve)) diff --git a/rosette/lib/profile/renderer/syntax.rkt b/rosette/lib/profile/renderer/syntax.rkt new file mode 100644 index 00000000..712bf5bd --- /dev/null +++ b/rosette/lib/profile/renderer/syntax.rkt @@ -0,0 +1,48 @@ +#lang racket + +(require racket/date racket/path) +(provide make-folder-name syntax-srcloc procedure-name) + +; Helpers to construct filenames +(define initial-date + (match-let ([pad (lambda (x n) (~r x #:min-width n #:pad-string "0"))] + [(date s m h d M y _ _ _ _) (current-date)]) + (string-append (pad y 4) (pad M 2) (pad d 2) (pad h 2) (pad m 2) (pad s 2)))) +(define (syntax-srcfile stx) + (match stx + [(and (? syntax?) (app syntax-source (? path?))) + (let-values ([(base name dir?) (split-path (syntax-source stx))]) + (path->string name))] + [`(submod (file ,path) ,mod) + (let-values ([(base name dir?) (split-path path)]) + (format "~a-~a" name mod))] + [`(file ,path) + (let-values ([(base name dir?) (split-path path)]) + (path->string name))] + [(? path-string?) + (let-values ([(base name dir?) (split-path stx)]) + (path->string name))] + [_ "unknown"])) +(define make-folder-name + (let ([n 0]) + (lambda (source) + (begin0 + (format "~a-~a-~v" (syntax-srcfile source) initial-date n) + (set! n (+ n 1)))))) + +; Get a version of a syntax object's source location that can be rendered +(define (path->pretty-path path) + (path->string (find-relative-path (current-directory) path))) +(define (syntax-srcloc stx) + (cond [(and (syntax? stx) (path? (syntax-source stx))) + (format "~a:~v:~v" (path->pretty-path (syntax-source stx)) (syntax-line stx) (syntax-column stx))] + [(and (list? stx) (= (length stx) 3) (not (eq? (first stx) 'submod))) + (match-let* ([(list src line col) stx] + [name (if (path? src) (path->pretty-path src) (~a src))]) + (format "~a:~v:~v" name line col))] + [stx (~a stx)] + [else stx])) + +; Get the name of a procedure +(define (procedure-name proc) + (~a (or (object-name proc) proc))) \ No newline at end of file diff --git a/rosette/lib/profile/renderer/trace.rkt b/rosette/lib/profile/renderer/trace.rkt new file mode 100644 index 00000000..69722b56 --- /dev/null +++ b/rosette/lib/profile/renderer/trace.rkt @@ -0,0 +1,33 @@ +#lang racket + +(require "../data.rkt" "../record.rkt" "../graph.rkt" "../reporter.rkt" + "renderer.rkt" + "syntax.rkt") + +(provide make-trace-renderer) + +(struct trace-renderer (source name) + #:transparent + #:methods gen:renderer + [(define start-renderer void) + (define (finish-renderer self profile) + (match-define (trace-renderer source name) self) + (render-trace (profile-state->graph profile) source name))]) + +(define (make-trace-renderer source name [options (hash)]) + (trace-renderer source name)) + +(define (render-trace profile source name) + (define (indent n) + (string-join (for/list ([i n]) " ") "")) + (printf "Trace for ~a (~v)\n" name source) + (let rec ([node profile][level 0]) + (define metrics (profile-data-metrics (profile-node-data node))) + (printf "~a* ~a (~v ms, ~v merges, ~v unions, ~v terms)\n" + (indent level) (procedure-name (profile-data-procedure (profile-node-data node))) + (metrics-ref metrics 'time) + (metrics-ref metrics 'merge-count) + (metrics-ref metrics 'union-count) + (metrics-ref metrics 'term-count)) + (for ([c (profile-node-children node)]) + (rec c (add1 level))))) diff --git a/rosette/lib/profile/reporter.rkt b/rosette/lib/profile/reporter.rkt new file mode 100644 index 00000000..58be5e1c --- /dev/null +++ b/rosette/lib/profile/reporter.rkt @@ -0,0 +1,116 @@ +#lang racket + +(require rosette/base/core/reporter "data.rkt") +(provide (struct-out profiler-reporter) make-profiler-reporter + get-current-metrics/call get-call-time + get-sample-event + metrics-ref diff-metrics metrics->hash) + +; The profiler reporter keeps a cumulative count of several metrics, +; as an association list, and reports +; them when requested to insert into a profile node. +; (Performance note: an association list is slightly faster than a hash table +; for workloads that clone the current metrics state a lot, such as MemSynth). +(define (make-profiler-reporter profile) + (profiler-reporter + profile + (map (curryr cons 0) '(term-count merge-count merge-cases union-count union-size)) + #f)) + +(struct profiler-reporter (profile [metrics #:mutable] [finitizing #:mutable]) + #:transparent + #:property prop:procedure + (lambda (self . rest) + (match rest + [(list 'new-term the-term) + (unless (profiler-reporter-finitizing self) + (inc! self 'term-count 1)) + (let* ([new (profile-event-term-new (get-current-metrics/none) the-term)]) + (profile-state-append! (profiler-reporter-profile self) new))] + [(list 'merge merge-cases) + (unless (profiler-reporter-finitizing self) + (inc! self 'merge-count 1) + (inc! self 'merge-cases merge-cases))] + [(list 'new-union union-size) + (unless (profiler-reporter-finitizing self) + (inc! self 'union-count 1) + (inc! self 'union-size union-size))] + [(list 'solve-start) + (let* ([new (profile-event-solve-start (get-current-metrics/event))]) + (profile-state-append! (profiler-reporter-profile self) new))] + [(list 'solve-finish sat?) + (let* ([new (profile-event-solve-finish (get-current-metrics/event) sat?)]) + (profile-state-append! (profiler-reporter-profile self) new))] + [(list 'to-solver lists ...) + (let* ([new (profile-event-solve-encode (get-current-metrics/none) lists)]) + (profile-state-append! (profiler-reporter-profile self) new))] + [(list 'finitize-start) + (set-profiler-reporter-finitizing! self #t) + (let* ([new (profile-event-finitize-start (get-current-metrics/event))]) + (profile-state-append! (profiler-reporter-profile self) new))] + [(list 'finitize-finish) + (set-profiler-reporter-finitizing! self #f) + (let* ([new (profile-event-finitize-finish (get-current-metrics/event))]) + (profile-state-append! (profiler-reporter-profile self) new))] + [(list 'encode-start) + (let* ([new (profile-event-encode-start (get-current-metrics/event))]) + (profile-state-append! (profiler-reporter-profile self) new))] + [(list 'encode-finish) + (let* ([new (profile-event-encode-finish (get-current-metrics/event))]) + (profile-state-append! (profiler-reporter-profile self) new))] + [_ void]))) + + +(define (assoc-inc xs x v) + (let loop ([xs xs]) + (cond [(null? xs) (cons x v)] + [(eq? (caar xs) x) (cons (cons x (+ v (cdar xs))) (cdr xs))] + [else (cons (car xs) (loop (cdr xs)))]))) +(define (assoc-dec xs x v) + (let loop ([xs xs]) + (cond [(null? xs) (cons x v)] + [(eq? (caar xs) x) (cons (cons x (- (cdar xs) v)) (cdr xs))] + [else (cons (car xs) (loop (cdr xs)))]))) + +(define-syntax-rule (inc! reporter key val) + (let ([ht (profiler-reporter-metrics reporter)]) + (set-profiler-reporter-metrics! reporter (assoc-inc ht key val)))) +(define-syntax-rule (dec! reporter key val) + (let ([ht (profiler-reporter-metrics reporter)]) + (set-profiler-reporter-metrics! reporter (assoc-dec ht key val)))) + + +(define (get-current-metrics/event) + (list (cons 'time (current-inexact-milliseconds)))) +(define (get-current-metrics/none) + '()) +(define (get-current-metrics/call reporter) + (cons (cons 'time (current-inexact-milliseconds)) + (profiler-reporter-metrics reporter))) +; shortcut to get time from a get-current-metrics/call instance; +; make sure to update if get-current-metrics/call changes +(define (get-call-time evt) + (cdar (profile-event-metrics evt))) + + +(define (get-sample-event) + (profile-event-sample (get-current-metrics/call (current-reporter)))) + + +;; Abstract out references to metrics in case we decide we need a better data +;; structure at some point. +(define (metrics-ref mets key) + (let ([a (assq key mets)]) + (if a (cdr a) #f))) + + +;; Helper to compute the difference between entry and exit metrics +(define (diff-metrics old new) + (for/list ([k/v new]) + (let ([k (car k/v)][v (cdr k/v)]) + (let ([o (assq k old)]) + (cons k (- v (if o (cdr o) 0))))))) + +;; Convert metrics to a hash for output +(define (metrics->hash m) + (for/hash ([k/v m]) (values (car k/v) (cdr k/v)))) diff --git a/rosette/lib/profile/tool.rkt b/rosette/lib/profile/tool.rkt new file mode 100644 index 00000000..d6c7fdb9 --- /dev/null +++ b/rosette/lib/profile/tool.rkt @@ -0,0 +1,33 @@ +#lang racket + +(require "data.rkt" "record.rkt" "reporter.rkt" + "renderer/renderer.rkt" + "renderer/noop.rkt") +(provide (all-defined-out)) + +; The selected renderer +(define current-renderer (make-parameter make-noop-renderer)) + +; Executes the given thunk and prints the profile data generated during execution. +(define (profile-thunk thunk #:renderer [renderer% (current-renderer)] + #:source [source-stx #f] + #:name [name "Profile"]) + (define profile (make-profile-state)) + (define reporter (make-profiler-reporter profile)) + (define renderer (renderer% source-stx name)) + (start-renderer renderer profile reporter) + (define ret (run-profile-thunk thunk profile reporter)) + (finish-renderer renderer profile) + (apply values ret)) + + +;; TODO: we probably need a version of profile-thunk etc that does +;; the profiling wrt a clean symbolic state (empty assertion stack, term cache etc). + + +; Profile the given form +(define-syntax (profile stx) + (syntax-case stx () + [(_ expr args ...) + (syntax/loc stx + (profile-thunk (thunk expr) #:source #'expr args ...))])) diff --git a/rosette/query/finitize.rkt b/rosette/query/finitize.rkt index bc5cbc41..d91ddc0d 100644 --- a/rosette/query/finitize.rkt +++ b/rosette/query/finitize.rkt @@ -9,6 +9,7 @@ "../base/core/polymorphic.rkt" "../base/core/merge.rkt" "../base/core/union.rkt" + "../base/core/reporter.rkt" (only-in "../solver/solution.rkt" model core sat unsat sat? unsat?) (only-in "../base/core/term.rkt" [operator-unsafe unsafe])) @@ -32,9 +33,13 @@ ; their subterms, to their corresponding BV finitizations. Terms that are already in BV ; finitize to themselves. (define (finitize terms [bw (current-bitwidth)] [env (make-hash)]) + ; lie to the profiler: any term that gets finitized makes it to the solver + ((current-reporter) 'to-solver terms) (parameterize ([current-bitwidth bw]) + ((current-reporter) 'finitize-start) (for ([t terms]) (finitize-any t env)) + ((current-reporter) 'finitize-finish) env)) ; Takes as input a solution and a finitization map diff --git a/rosette/solver/smt/base-solver.rkt b/rosette/solver/smt/base-solver.rkt index 155059bb..0152202b 100644 --- a/rosette/solver/smt/base-solver.rkt +++ b/rosette/solver/smt/base-solver.rkt @@ -7,7 +7,8 @@ (only-in "../../base/core/term.rkt" term term? term-type) (only-in "../../base/core/bool.rkt" @boolean?) (only-in "../../base/core/bitvector.rkt" bitvector? bv?) - (only-in "../../base/core/real.rkt" @integer? @real?)) + (only-in "../../base/core/real.rkt" @integer? @real?) + (only-in "../../base/core/reporter.rkt" current-reporter)) (provide (all-defined-out)) @@ -71,7 +72,9 @@ (server-write server (begin + ((current-reporter) 'encode-start) (encode env asserts mins maxs) + ((current-reporter) 'encode-finish) (push))) (solver-clear-stacks! self) (set-solver-level! self (cons (dict-count env) level))) @@ -91,10 +94,16 @@ (cond [(ormap false? asserts) (unsat)] [else (server-write server - (begin (encode env asserts mins maxs) - (check-sat))) + (begin + ((current-reporter) 'encode-start) + (encode env asserts mins maxs) + ((current-reporter) 'encode-finish) + (check-sat))) + ((current-reporter) 'solve-start) (solver-clear-stacks! self) - (read-solution server env)])) + (define ret (read-solution server env)) + ((current-reporter) 'solve-finish (sat? ret)) + ret])) (define (solver-debug self) (error 'solver-debug "debugging isn't supported by solver ~v" self)) diff --git a/rosette/solver/smt/cmd.rkt b/rosette/solver/smt/cmd.rkt index e81f42d3..b4cc90f4 100644 --- a/rosette/solver/smt/cmd.rkt +++ b/rosette/solver/smt/cmd.rkt @@ -9,6 +9,7 @@ (only-in "../../base/core/bool.rkt" @boolean?) (only-in "../../base/core/bitvector.rkt" bitvector? bv) (only-in "../../base/core/real.rkt" @integer? @real?) + "../../base/core/reporter.rkt" "../solution.rkt") (provide encode encode-for-proof decode) @@ -22,6 +23,7 @@ ; be augmented, if needed, with additional declarations and ; definitions. This procedure will not emit any other commands. (define (encode env asserts mins maxs) + ((current-reporter) 'to-solver asserts mins maxs) (for ([a asserts]) (assert (enc a env))) (for ([m mins]) diff --git a/sdsl/bv/examples/easy.rkt b/sdsl/bv/examples/easy.rkt index d709b3b9..2ad18e00 100644 --- a/sdsl/bv/examples/easy.rkt +++ b/sdsl/bv/examples/easy.rkt @@ -1,5 +1,6 @@ -#lang s-exp "../bv.rkt" +#lang rosette +(require "../bv.rkt") (require "reference.rkt") (provide (all-defined-out)) diff --git a/sdsl/bv/examples/hard.rkt b/sdsl/bv/examples/hard.rkt index 5bc98258..ee7433b2 100644 --- a/sdsl/bv/examples/hard.rkt +++ b/sdsl/bv/examples/hard.rkt @@ -1,5 +1,6 @@ -#lang s-exp "../bv.rkt" +#lang rosette +(require "../bv.rkt") (require "reference.rkt") (provide (all-defined-out)) diff --git a/sdsl/bv/examples/medium.rkt b/sdsl/bv/examples/medium.rkt index f8847f88..52e42ef0 100644 --- a/sdsl/bv/examples/medium.rkt +++ b/sdsl/bv/examples/medium.rkt @@ -1,5 +1,6 @@ -#lang s-exp "../bv.rkt" +#lang rosette +(require "../bv.rkt") (require "reference.rkt") (provide (all-defined-out)) diff --git a/sdsl/bv/examples/reference.rkt b/sdsl/bv/examples/reference.rkt index cedb53a3..5f34a391 100644 --- a/sdsl/bv/examples/reference.rkt +++ b/sdsl/bv/examples/reference.rkt @@ -1,5 +1,6 @@ -#lang s-exp "../bv.rkt" +#lang rosette +(require "../bv.rkt") (provide (all-defined-out)) ; The 25 Hacker's Delight benchmarks from the following paper: diff --git a/sdsl/bv/test/easy.rkt b/sdsl/bv/test/easy.rkt index e6f575bc..21107840 100644 --- a/sdsl/bv/test/easy.rkt +++ b/sdsl/bv/test/easy.rkt @@ -1,5 +1,6 @@ -#lang s-exp "../bv.rkt" +#lang rosette +(require "../bv.rkt") (require rackunit rackunit/text-ui "util.rkt" rosette/lib/roseunit) (require "../examples/reference.rkt") diff --git a/sdsl/bv/test/medium.rkt b/sdsl/bv/test/medium.rkt index b8618433..3a566898 100644 --- a/sdsl/bv/test/medium.rkt +++ b/sdsl/bv/test/medium.rkt @@ -1,5 +1,6 @@ -#lang s-exp "../bv.rkt" +#lang rosette +(require "../bv.rkt") (require rackunit rackunit/text-ui "util.rkt" rosette/lib/roseunit) (require "../examples/reference.rkt") diff --git a/test/all-rosette-tests.rkt b/test/all-rosette-tests.rkt index 983c1d17..3bae022c 100644 --- a/test/all-rosette-tests.rkt +++ b/test/all-rosette-tests.rkt @@ -40,7 +40,8 @@ "query/solve+.rkt" "query/synthax.rkt" "query/debug.rkt" - "query/optimize.rkt") + "query/optimize.rkt" + "profile/test.rkt") (define (run-tests-with-solver solver%) diff --git a/test/profile/benchmarks/exn.rkt b/test/profile/benchmarks/exn.rkt new file mode 100644 index 00000000..9f047e36 --- /dev/null +++ b/test/profile/benchmarks/exn.rkt @@ -0,0 +1,17 @@ +#lang rosette + +; A simple test to check exception handling. When running the symbolic profiler +; in trace mode, the `add1` invocation in `foo` should not be a child of the +; `raise-argument-error` invocation. If it is, then the profiler stack has +; fallen out of sync with the actual stack due to the exception being thrown. + +(define (foo n) + (if (= n 2) + (raise-argument-error 'foo "not 2" n) + (add1 n))) + +(define (bar) + (define-symbolic i integer?) + (foo i)) + +(bar) diff --git a/test/profile/benchmarks/list.rkt b/test/profile/benchmarks/list.rkt new file mode 100644 index 00000000..3cfef784 --- /dev/null +++ b/test/profile/benchmarks/list.rkt @@ -0,0 +1,12 @@ +#lang rosette + +; Micro-benchmarks for various list operations. + +(require rosette/lib/angelic) +(provide (all-defined-out)) + + +; Construct a symbolic list of up to the given length +(define (symbolic-list len) + (define lst (build-list len identity)) + (apply choose* (for/list ([i len]) (take lst i)))) diff --git a/test/profile/benchmarks/update-at.rkt b/test/profile/benchmarks/update-at.rkt new file mode 100644 index 00000000..952d346f --- /dev/null +++ b/test/profile/benchmarks/update-at.rkt @@ -0,0 +1,25 @@ +#lang rosette + +(require "list.rkt") +(provide (all-defined-out)) + + +(define (update-at lst pos val) + (match lst + [(list) lst] + [(list x xs ...) + (if (= pos 0) + (cons val xs) + (cons x (update-at xs (- pos 1) val)))])) + + +; Simple test for update-at +(define (test-update-at lst) + (define-symbolic* idx integer?) + (update-at lst idx -1) + (void)) + +(define lst (build-list 50 identity)) + + +(time (test-update-at lst)) diff --git a/test/profile/output/micro-exn.out b/test/profile/output/micro-exn.out new file mode 100644 index 00000000..a108c5a7 --- /dev/null +++ b/test/profile/output/micro-exn.out @@ -0,0 +1 @@ +(the-profiled-thunk (bar (foo (=) (raise-argument-error) (@add1)))) \ No newline at end of file diff --git a/test/profile/output/micro-update-at.out b/test/profile/output/micro-update-at.out new file mode 100644 index 00000000..8c6e72f1 --- /dev/null +++ b/test/profile/output/micro-update-at.out @@ -0,0 +1 @@ +(the-profiled-thunk (build-list) (test-update-at (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at (=) (@cons) (-) (update-at) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (@cons)) (void))) \ No newline at end of file diff --git a/test/profile/renderer.rkt b/test/profile/renderer.rkt new file mode 100644 index 00000000..1a555dfb --- /dev/null +++ b/test/profile/renderer.rkt @@ -0,0 +1,53 @@ +#lang racket + +(require rackunit + racket/runtime-path + rosette/lib/profile/data + rosette/lib/profile/graph + rosette/lib/profile/tool + rosette/lib/profile/renderer/renderer + rosette/lib/roseunit + (only-in rosette clear-state!)) +(provide regression-test) + + +(define-runtime-path output-path "output") + + +(struct regression-renderer (path) + #:transparent + #:methods gen:renderer + [(define start-renderer void) + (define (finish-renderer self profile) + (check-profile profile (regression-renderer-path self)))]) + + +; Check whether the profile matches the one saved in `path`. +; If `path` doesn't exist, write the output of the new profile there instead of checking. +(define (check-profile profile path) + (define new-graph (profile-state->graph profile)) + (define new-list + (let loop ([graph new-graph]) + (cons (let ([proc (profile-data-procedure (profile-node-data graph))]) + (or (object-name proc) proc)) + (for/list ([c (profile-node-children graph)]) (loop c))))) + (define outpath (build-path output-path (format "~a.out" path))) + (if (file-exists? outpath) + (let ([old-list (call-with-input-file outpath read)]) + (check-equal? new-list old-list)) + (let () + (with-output-to-file outpath (thunk (write new-list))) + (printf "Wrote new output for `~a`: ~v\n" path new-list)))) + + + +(define-syntax-rule (regression-test test-name path code) + (test-suite+ test-name + (let ([renderer% (lambda (source name [options (hash)]) + (regression-renderer path))] + [ns (make-base-namespace)]) + (clear-state!) + (parameterize ([current-namespace ns]) + (profile-thunk + (lambda () code) + #:renderer renderer%))))) diff --git a/test/profile/test.rkt b/test/profile/test.rkt new file mode 100644 index 00000000..5f73235d --- /dev/null +++ b/test/profile/test.rkt @@ -0,0 +1,39 @@ +#lang racket + +(require rosette/lib/profile/compile + rosette/lib/profile/record + racket/runtime-path + "renderer.rkt") + + +(filtering-threshold 0) + + +; Tests must dynamically require their code so that they are seen by the +; profiler compile handler. +(define (run-profile-regression-test path) + (parameterize ([current-compile symbolic-profile-compile-handler]) + (dynamic-require `(file ,(path->string path)) #f))) + + +; Test exception handling +(define-runtime-path exn.rkt "benchmarks/exn.rkt") +(define micro-exn + (regression-test + "Profiler call graph: benchmarks/exn.rkt" + "micro-exn" + (run-profile-regression-test exn.rkt))) + +; Test list update-at +(define-runtime-path update-at.rkt "benchmarks/update-at.rkt") +(define micro-update-at + (regression-test + "Profiler call graph: benchmarks/update-at.rkt" + "micro-update-at" + (run-profile-regression-test update-at.rkt))) + + +(module+ test + (require rackunit/text-ui) + (run-tests micro-exn) + (run-tests micro-update-at))
    +
    +
    +
    + [More] +
    + +
    +
    + + + +
    +
    +