4 # Copyright (C) 2003 Zoran Vasiljevic, Archiware GmbH. All Rights Reserved.
6 # See the file "license.terms" for information on usage and redistribution of
7 # this file, and for a DISCLAIMER OF ALL WARRANTIES.
8 # ----------------------------------------------------------------------------
10 # User level commands:
12 # ttrace::eval top-level wrapper (ttrace-savvy eval)
13 # ttrace::enable activates registered Tcl command traces
14 # ttrace::disable terminates tracing of Tcl commands
15 # ttrace::isenabled returns true if ttrace is enabled
16 # ttrace::cleanup bring the interp to a pristine state
17 # ttrace::update update interp to the latest trace epoch
18 # ttrace::config setup some configuration options
19 # ttrace::getscript returns a script for initializing interps
21 # Commands used for/from trace callbacks:
23 # ttrace::atenable register callback to be done at trace enable
24 # ttrace::atdisable register callback to be done at trace disable
25 # ttrace::addtrace register user-defined tracer callback
26 # ttrace::addscript register user-defined script generator
27 # ttrace::addresolver register user-defined command resolver
28 # ttrace::addcleanup register user-defined cleanup procedures
29 # ttrace::addentry adds one entry into the named trace store
30 # ttrace::getentry returns the entry value from the named store
31 # ttrace::delentry removes the entry from the named store
32 # ttrace::getentries returns all entries from the named store
33 # ttrace::preload register procedures to be preloaded always
38 # o. [namespace forget] is still not implemented
39 # o. [namespace origin cmd] breaks if cmd is not already defined
41 # I left this deliberately. I didn't want to override the [namespace]
42 # command in order to avoid potential slowdown.
45 namespace eval ttrace {
47 # Setup some compatibility wrappers
48 if {[info commands nsv_set] != ""} {
50 variable mutex ns_mutex
51 variable elock [$mutex create traceepochmutex]
52 # Import the underlying API; faster than recomputing
53 interp alias {} [namespace current]::_array {} nsv_array
54 interp alias {} [namespace current]::_incr {} nsv_incr
55 interp alias {} [namespace current]::_lappend {} nsv_lappend
56 interp alias {} [namespace current]::_names {} nsv_names
57 interp alias {} [namespace current]::_set {} nsv_set
58 interp alias {} [namespace current]::_unset {} nsv_unset
60 variable tvers [package require Thread]
62 variable mutex thread::mutex
63 variable elock [$mutex create]
64 # Import the underlying API; faster than recomputing
65 interp alias {} [namespace current]::_array {} tsv::array
66 interp alias {} [namespace current]::_incr {} tsv::incr
67 interp alias {} [namespace current]::_lappend {} tsv::lappend
68 interp alias {} [namespace current]::_names {} tsv::names
69 interp alias {} [namespace current]::_set {} tsv::set
70 interp alias {} [namespace current]::_unset {} tsv::unset
72 error "requires NaviServer/AOLserver or Tcl threading extension"
75 # Keep in sync with the Thread package
76 package provide Ttrace 2.7.2
79 variable resolvers "" ; # List of registered resolvers
80 variable tracers "" ; # List of registered cmd tracers
81 variable scripts "" ; # List of registered script makers
82 variable enables "" ; # List of trace-enable callbacks
83 variable disables "" ; # List of trace-disable callbacks
84 variable preloads "" ; # List of procedure names to preload
85 variable enabled 0 ; # True if trace is enabled
86 variable config ; # Array with config options
88 variable epoch -1 ; # The initialization epoch
89 variable cleancnt 0 ; # Counter of registered cleaners
91 # Package private namespaces
92 namespace eval resolve "" ; # Commands for resolving commands
93 namespace eval trace "" ; # Commands registered for tracing
94 namespace eval enable "" ; # Commands invoked at trace enable
95 namespace eval disable "" ; # Commands invoked at trace disable
96 namespace eval script "" ; # Commands for generating scripts
99 namespace export unknown
101 # Initialize ttrace shared state
102 if {[_array exists ttrace] == 0} {
103 _set ttrace lastepoch $epoch
104 _set ttrace epochlist ""
107 # Initially, allow creation of epochs
108 set config(-doepochs) 1
110 proc eval {cmd args} {
112 set code [catch {uplevel 1 [concat $cmd $args]} result]
115 if {[llength [info commands ns_ictl]]} {
116 ns_ictl save [getscript]
119 package require Ttrace
125 -errorinfo $::errorInfo -errorcode $::errorCode $result
130 if {[llength $args] == 0} {
132 } elseif {[llength $args] == 1} {
133 set opt [lindex $args 0]
136 set opt [lindex $args 0]
137 set val [lindex $args 1]
138 set config($opt) $val
151 if {$config(-doepochs) != 0} {
152 variable epoch [_newepoch]
154 set nsp [namespace current]
155 foreach enabler $enables {
158 foreach trace $tracers {
159 if {[info commands $trace] != ""} {
160 trace add execution $trace leave ${nsp}::trace::_$trace
173 set nsp [namespace current]
174 foreach disabler $disables {
177 foreach trace $tracers {
178 if {[info commands $trace] != ""} {
179 trace remove execution $trace leave ${nsp}::trace::_$trace
189 proc update {{from -1}} {
191 variable epoch [_set ttrace lastepoch]
193 if {[lsearch [_set ttrace epochlist] $from] == -1} {
194 error "no such epoch: $from"
205 append script [_serializensp] \n
206 append script "::namespace eval [namespace current] {" \n
207 append script "::namespace export unknown" \n
208 append script "_useepoch $epoch" \n
210 foreach cmd $preloads {
211 append script [_serializeproc $cmd] \n
213 foreach maker $scripts {
214 append script [script::_$maker]
219 proc cleanup {args} {
220 foreach cmd [info commands resolve::cleaner_*] {
227 if {[lsearch $preloads $cmd] == -1} {
228 lappend preloads $cmd
232 proc atenable {cmd arglist body} {
234 if {[lsearch $enables $cmd] == -1} {
236 set cmd [namespace current]::enable::_$cmd
237 proc $cmd $arglist $body
242 proc atdisable {cmd arglist body} {
244 if {[lsearch $disables $cmd] == -1} {
245 lappend disables $cmd
246 set cmd [namespace current]::disable::_$cmd
247 proc $cmd $arglist $body
252 proc addtrace {cmd arglist body} {
254 if {[lsearch $tracers $cmd] == -1} {
256 set tracer [namespace current]::trace::_$cmd
257 proc $tracer $arglist $body
259 trace add execution $cmd leave $tracer
265 proc addscript {cmd body} {
267 if {[lsearch $scripts $cmd] == -1} {
269 set cmd [namespace current]::script::_$cmd
275 proc addresolver {cmd arglist body} {
277 if {[lsearch $resolvers $cmd] == -1} {
278 lappend resolvers $cmd
279 set cmd [namespace current]::resolve::$cmd
280 proc $cmd $arglist $body
285 proc addcleanup {body} {
287 set cmd [namespace current]::resolve::cleaner_[incr cleancnt]
292 proc addentry {cmd var val} {
294 _set ${epoch}-$cmd $var $val
297 proc delentry {cmd var} {
301 catch {_unset ${epoch}-$cmd $var}
306 proc getentry {cmd var} {
310 if {[catch {_set ${epoch}-$cmd $var} val]} {
318 proc getentries {cmd {pattern *}} {
320 _array names ${epoch}-$cmd $pattern
323 proc unknown {args} {
324 set cmd [lindex $args 0]
325 if {[uplevel ttrace::_resolve [list $cmd]]} {
326 set c [catch {uplevel $cmd [lrange $args 1 end]} r]
328 set c [catch {::eval ::tcl::unknown $args} r]
330 return -code $c -errorcode $::errorCode -errorinfo $::errorInfo $r
333 proc _resolve {cmd} {
335 foreach resolver $resolvers {
336 if {[uplevel [info comm resolve::$resolver] [list $cmd]]} {
344 if {[info commands ns_thread] == ""} {
351 proc _getthreads {} {
352 if {[info commands ns_thread] == ""} {
353 return [thread::names]
355 foreach entry [ns_info threads] {
356 lappend threads [lindex $entry 2]
366 set old [_set ttrace lastepoch]
367 set new [_incr ttrace lastepoch]
368 _lappend ttrace $new [_getthread]
373 _lappend ttrace epochlist $new
378 proc _copyepoch {old new} {
379 foreach var [_names $old-*] {
380 set cmd [lindex [split $var -] 1]
381 _array reset $new-$cmd [_array get $var]
386 set tlist [_getthreads]
388 foreach epoch [_set ttrace epochlist] {
389 if {[_dropepoch $epoch $tlist] == 0} {
395 _set ttrace epochlist $elist
398 proc _dropepoch {epoch threads} {
399 set self [_getthread]
400 foreach tid [_set ttrace $epoch] {
401 if {$tid != $self && [lsearch $threads $tid] >= 0} {
405 if {[info exists alive]} {
406 _set ttrace $epoch $alive
409 foreach var [_names $epoch-*] {
416 proc _useepoch {epoch} {
419 if {[lsearch [_set ttrace $epoch] $tid] == -1} {
420 _lappend ttrace $epoch $tid
425 proc _serializeproc {cmd} {
426 set dargs [info args $cmd]
427 set pbody [info body $cmd]
430 if {![info default $cmd $arg def]} {
433 lappend pargs [list $arg $def]
436 set nsp [namespace qual $cmd]
440 append res [list ::namespace eval $nsp] " {" \n
441 append res [list ::proc [namespace tail $cmd] $pargs $pbody] \n
445 proc _serializensp {{nsp ""} {result _}} {
448 set nsp [namespace current]
450 append res [list ::namespace eval $nsp] " {" \n
451 foreach var [info vars ${nsp}::*] {
452 set vname [namespace tail $var]
453 if {[array exists $var] == 0} {
454 append res [list ::variable $vname [set $var]] \n
456 append res [list ::variable $vname] \n
457 append res [list ::array set $vname [array get $var]] \n
460 foreach cmd [info procs ${nsp}::*] {
461 append res [_serializeproc $cmd] \n
464 foreach nn [namespace children $nsp] {
465 _serializensp $nn res
472 # The code below is ment to be run once during the application start. It
473 # provides implementation of tracing callbacks for some Tcl commands. Users
474 # can supply their own tracer implementations on-the-fly.
476 # The code below will create traces for the following Tcl commands:
477 # "namespace", "variable", "load", "proc" and "rename"
479 # Also, the Tcl object extension XOTcl 1.1.0 is handled and all XOTcl related
480 # things, like classes and objects are traced (many thanks to Gustaf Neumann
481 # from XOTcl for his kind help and support).
487 # Register the "load" trace. This will create the following key/value pair
488 # in the "load" store:
490 # --- key ---- --- value ---
491 # <path_of_loaded_image> <name_of_the_init_proc>
493 # We normally need only the name_of_the_init_proc for being able to load
494 # the package in other interpreters, but we store the path to the image
498 ttrace::addtrace load {cmdline code args} {
502 set image [lindex $cmdline 1]
503 set initp [lindex $cmdline 2]
505 foreach pkg [info loaded] {
506 if {[lindex $pkg 0] == $image} {
507 set initp [lindex $pkg 1]
511 ttrace::addentry load $image $initp
514 ttrace::addscript load {
516 foreach entry [ttrace::getentries load] {
517 set initp [ttrace::getentry load $entry]
518 append res "::load {} $initp" \n
524 # Register the "namespace" trace. This will create the following key/value
525 # entry in "namespace" store:
527 # --- key ---- --- value ---
528 # ::fully::qualified::namespace 1
530 # It will also fill the "proc" store for procedures and commands imported
531 # in this namespace with following:
533 # --- key ---- --- value ---
534 # ::fully::qualified::proc [list <ns> "" ""]
536 # The <ns> is the name of the namespace where the command or procedure is
540 ttrace::addtrace namespace {cmdline code args} {
544 set nop [lindex $cmdline 1]
545 set cns [uplevel namespace current]
551 set nsp [lindex $cmdline 2]
552 if {![string match "::*" $nsp]} {
555 ttrace::addentry namespace $nsp 1
558 # - parse import arguments (skip opt "-force")
559 set opts [lrange $cmdline 2 end]
560 if {[string match "-fo*" [lindex $opts 0]]} {
561 set opts [lrange $cmdline 3 end]
563 # - register all imported procs and commands
565 if {![string match "::*" [::namespace qual $opt]]} {
568 # - first import procs
569 foreach entry [ttrace::getentries proc $opt] {
570 set cmd ${cns}::[::namespace tail $entry]
571 set nsp [::namespace qual $entry]
573 set entry [list 0 $nsp "" ""]
574 ttrace::addentry proc $cmd $entry
577 # - then import commands
578 foreach entry [info commands $opt] {
579 set cmd ${cns}::[::namespace tail $entry]
580 set nsp [::namespace qual $entry]
581 if {[info exists done($cmd)] == 0} {
582 set entry [list 0 $nsp "" ""]
583 ttrace::addentry proc $cmd $entry
591 ttrace::addscript namespace {
593 foreach entry [ttrace::getentries namespace] {
594 append res "::namespace eval $entry {}" \n
600 # Register the "variable" trace. This will create the following key/value
601 # entry in the "variable" store:
603 # --- key ---- --- value ---
604 # ::fully::qualified::variable 1
606 # The variable value itself is ignored at the time of
607 # trace/collection. Instead, we take the real value at the time of script
611 ttrace::addtrace variable {cmdline code args} {
615 set opts [lrange $cmdline 1 end]
616 if {[llength $opts]} {
617 set cns [uplevel namespace current]
621 foreach {var val} $opts {
622 if {![string match "::*" $var]} {
625 ttrace::addentry variable $var 1
630 ttrace::addscript variable {
632 foreach entry [ttrace::getentries variable] {
633 set cns [namespace qual $entry]
634 set var [namespace tail $entry]
635 append res "::namespace eval $cns {" \n
636 append res "::variable $var"
637 if {[array exists $entry]} {
638 append res "\n::array set $var [list [array get $entry]]" \n
639 } elseif {[info exists $entry]} {
640 append res " [list [set $entry]]" \n
651 # Register the "rename" trace. It will create the following key/value pair
654 # --- key ---- --- value ---
655 # ::fully::qualified::old ::fully::qualified::new
657 # The "new" value may be empty, for commands that have been deleted. In
658 # such cases we also remove any traced procedure definitions.
661 ttrace::addtrace rename {cmdline code args} {
665 set cns [uplevel namespace current]
669 set old [lindex $cmdline 1]
670 if {![string match "::*" $old]} {
673 set new [lindex $cmdline 2]
675 if {![string match "::*" $new]} {
678 ttrace::addentry rename $old $new
680 ttrace::delentry proc $old
684 ttrace::addscript rename {
686 foreach old [ttrace::getentries rename] {
687 set new [ttrace::getentry rename $old]
688 append res "::rename $old {$new}" \n
694 # Register the "proc" trace. This will create the following key/value pair
695 # in the "proc" store:
697 # --- key ---- --- value ---
698 # ::fully::qualified::proc [list <epoch> <ns> <arglist> <body>]
700 # The <epoch> chages anytime one (re)defines a proc. The <ns> is the
701 # namespace where the command was imported from. If empty, the <arglist>
702 # and <body> will hold the actual procedure definition. See the
703 # "namespace" tracer implementation also.
706 ttrace::addtrace proc {cmdline code args} {
710 set cns [uplevel namespace current]
714 set cmd [lindex $cmdline 1]
715 if {![string match "::*" $cmd]} {
718 set dargs [info args $cmd]
719 set pbody [info body $cmd]
722 if {![info default $cmd $arg def]} {
725 lappend pargs [list $arg $def]
728 set pdef [ttrace::getentry proc $cmd]
730 set epoch -1 ; # never traced before
732 set epoch [lindex $pdef 0]
734 ttrace::addentry proc $cmd [list [incr epoch] "" $pargs $pbody]
737 ttrace::addscript proc {
739 if {[info command ::tcl::unknown] == ""} {
740 rename ::unknown ::tcl::unknown
741 namespace import -force ::ttrace::unknown
743 if {[info command ::tcl::info] == ""} {
744 rename ::info ::tcl::info
747 set cmd [lindex $args 0]
748 set hit [lsearch -glob {commands procs args default body} $cmd*]
750 if {[catch {uplevel ::tcl::info $args}]} {
751 uplevel ttrace::_resolve [list [lindex $args 1]]
753 return [uplevel ::tcl::info $args]
756 return [uplevel ::tcl::info $args]
758 set cns [uplevel namespace current]
762 set pat [lindex $args 1]
763 if {![string match "::*" $pat]} {
766 set fns [ttrace::getentries proc $pat]
767 if {[string match $cmd* commands]} {
768 set fns [concat $fns [ttrace::getentries xotcl $pat]]
771 if {$cns != [namespace qual $entry]} {
774 set lazy([namespace tail $entry]) 1
777 foreach entry [uplevel ::tcl::info $args] {
786 # Register procedure resolver. This will try to resolve the command in the
787 # current namespace first, and if not found, in global namespace. It also
788 # handles commands imported from other namespaces.
791 ttrace::addresolver resolveprocs {cmd {export 0}} {
792 set cns [uplevel namespace current]
793 set name [namespace tail $cmd]
797 if {![string match "::*" $cmd]} {
798 set ncmd ${cns}::$cmd
804 set pdef [ttrace::getentry proc $ncmd]
806 set pdef [ttrace::getentry proc $gcmd]
814 set epoch [lindex $pdef 0]
815 set pnsp [lindex $pdef 1]
817 set nsp [namespace qual $cmd]
821 set cmd ${pnsp}::$name
822 if {[resolveprocs $cmd 1] == 0 && [info commands $cmd] == ""} {
825 namespace eval $nsp "namespace import -force $cmd"
827 uplevel 0 [list ::proc $cmd [lindex $pdef 2] [lindex $pdef 3]]
829 set nsp [namespace qual $cmd]
833 namespace eval $nsp "namespace export $name"
837 set resolveproc($cmd) $epoch
842 # For XOTcl, the entire item introspection/tracing is delegated to XOTcl
843 # itself. The xotcl store is filled with this:
845 # --- key ---- --- value ---
846 # ::fully::qualified::item <body>
848 # The <body> is the script used to generate the entire item (class,
849 # object). Note that we do not fill in this during code tracing. It is
850 # done during the script generation. In this step, only the placeholder is
853 # NOTE: we assume all XOTcl commands are imported in global namespace
856 ttrace::atenable XOTclEnabler {args} {
857 if {[info commands ::xotcl::Class] == ""} {
860 if {[info commands ::xotcl::_creator] == ""} {
861 ::xotcl::Class create ::xotcl::_creator -instproc create {args} {
863 if {![string match ::xotcl::_* $result]} {
864 ttrace::addentry xotcl $result ""
869 ::xotcl::Class instmixin ::xotcl::_creator
872 ttrace::atdisable XOTclDisabler {args} {
873 if { [info commands ::xotcl::Class] == ""
874 || [info commands ::xotcl::_creator] == ""} {
877 ::xotcl::Class instmixin ""
878 ::xotcl::_creator destroy
881 set resolver [ttrace::addresolver resolveclasses {classname} {
882 set cns [uplevel namespace current]
883 set script [ttrace::getentry xotcl $classname]
885 set name [namespace tail $classname]
887 set script [ttrace::getentry xotcl ::$name]
889 set script [ttrace::getentry xotcl ${cns}::$name]
891 set script [ttrace::getentry xotcl ::$name]
898 uplevel [list namespace eval $cns $script]
902 ttrace::addscript xotcl [subst -nocommands {
903 if {![catch {Serializer new} ss]} {
904 foreach entry [ttrace::getentries xotcl] {
905 if {[ttrace::getentry xotcl \$entry] == ""} {
906 ttrace::addentry xotcl \$entry [\$ss serialize \$entry]
910 return {::xotcl::Class proc __unknown name {$resolver \$name}}
915 # Register callback to be called on cleanup. This will trash lazily loaded
916 # procs which have changed since.
921 foreach cmd [array names resolveproc] {
922 set def [ttrace::getentry proc $cmd]
924 set new [lindex $def 0]
925 set old $resolveproc($cmd)
926 if {[info command $cmd] != "" && $new != $old} {
927 catch {rename $cmd ""}
941 # indent-tabs-mode: nil