Module: dm-internals Synopsis: Profiler Manager API implementation Author: Andy Armstrong, Keith Dennison Copyright: Original Code is Copyright (c) 1995-2004 Functional Objects, Inc. All rights reserved. License: Functional Objects Library Public License Version 1.0 Dual-license: GNU Lesser General Public License Warranty: Distributed WITHOUT WARRANTY OF ANY KIND ///////////////////////////////////////////////////////////////////////// // The exported types ///////////////////////////////////////////////////////////////////////// define constant $default-profiling-interval = 50; define constant $default-snapshot-limit = #f; define constant = limited(, of: ); define class () slot profiling? :: = #f; slot class-profiling? :: = #f; slot profile-interval :: = $default-profiling-interval; slot profile-breakpoints :: = #[]; // Snapshot limiting slot profile-snapshot-count :: = 0; slot profile-snapshot-limit :: false-or() = $default-snapshot-limit; // profile-stack-depth is the maximum depth to which a thread's // stack frames should be traced when taking a snapshot (#f // means no limit. slot profile-stack-depth :: false-or() = #f; // profile-threads is a group of objects which // indicates which threads snapshots should be taken for (#f // means all active threads at the time of the snapshot). slot profile-threads :: false-or() = #f; // profile-last-cpu-time-table maps threads to the last // CPU time seen on that thread. constant slot profile-last-cpu-time-table :: = make(
); // slot remembering the offset of the allocation counter in the // application, so that it can be accessed to determine new allocation. slot %allocation-counter-offset :: false-or() = #f; slot profile-data :: false-or() = #f; end class ; define sealed domain make (subclass()); define sealed domain initialize (); define inline function ensure-profile-data (profile-state :: ) => (profile :: ) profile-state.profile-data | error("Trying to process profiling data when non available") end function ensure-profile-data; define class () slot application-snapshot-skip :: = 0; slot application-last-wall-time :: = 0; slot application-last-page-faults :: = 0; constant slot application-snapshots :: = make(), init-keyword: application-snapshots:; constant slot application-profile-threads :: = make(), init-keyword: profile-threads:; end class ; define sealed domain make (subclass()); define sealed domain initialize (); define class () constant slot wall-time-increment :: , required-init-keyword: wall-time-increment:; constant slot page-faults-increment :: , required-init-keyword: page-faults-increment:; constant slot thread-snapshots :: , required-init-keyword: thread-snapshots:; end class ; define sealed domain make (subclass()); define sealed domain initialize (); define class () constant slot profile-thread :: , required-init-keyword: thread:; constant slot cpu-time-increment :: , required-init-keyword: cpu-time-increment:; constant slot allocation-increment :: , required-init-keyword: allocation-increment:; constant slot allocated-class :: false-or(), required-init-keyword: allocated-class:; constant slot instruction-pointers :: , required-init-keyword: instruction-pointers:; end class ; define sealed domain make (subclass()); define sealed domain initialize (); define method application-thread-snapshot (snapshot :: , thread :: ) => (thread-snapshot :: false-or()) block (return) for (snapshot :: in snapshot.thread-snapshots) if (snapshot.profile-thread == thread) return(snapshot) end end end end method application-thread-snapshot; ////// // Snapshot the application. // define method take-application-snapshot (application :: , #key snapshots-function :: false-or()) => () let path = application.debug-target-access-path; let profile-state = application.application-profile-state; let profile = profile-state.ensure-profile-data; let snapshot-count = profile-state.profile-snapshot-count; if (snapshot-count == 0) let last-wall-time = profile.application-last-wall-time; let last-page-faults = profile.application-last-page-faults; let wall-time = get-process-wall-clock-time(path); let page-faults = get-process-page-fault-count(path); profile.application-last-wall-time := wall-time; profile.application-last-page-faults := page-faults; let snapshots = if (snapshots-function) snapshots-function(application) else let snapshots :: = make(); do-profile-threads (method (thread :: ) let snapshot = take-thread-snapshot(application, thread); add!(snapshots, snapshot) end, application); snapshots end; let (wall-time-increment, page-faults-increment) = if (empty?(profile.application-snapshots)) values(0, 0) else values(wall-time - last-wall-time, page-faults - last-page-faults) end; let snapshot = make(, thread-snapshots: snapshots, wall-time-increment: wall-time-increment, page-faults-increment: page-faults-increment); let snapshot-limit = profile-state.profile-snapshot-limit; let snapshots = profile.application-snapshots; add!(snapshots, snapshot); if (snapshot-limit & snapshots.size > snapshot-limit) prune-application-snapshots(application, snapshots) end; let snapshot-skip = profile.application-snapshot-skip; profile-state.profile-snapshot-count := snapshot-skip else profile-state.profile-snapshot-count := snapshot-count - 1 end; end method take-application-snapshot; define method take-application-single-thread-snapshot (application :: , thread :: , #key allocation :: false-or() = #f, class :: false-or() = #f) => () local method take-single-thread-snapshot (application :: ) => (snapshots :: ) let snapshot = take-thread-snapshot(application, thread, allocation: allocation, class: class); vector(snapshot) end method take-single-thread-snapshot; take-application-snapshot (application, snapshots-function: take-single-thread-snapshot) end method take-application-single-thread-snapshot; define method take-thread-snapshot (application :: , thread :: , #key allocation :: false-or() = #f, class :: false-or() = #f) => (thread-snapshot :: ) let profile-state = application.application-profile-state; let profile = profile-state.ensure-profile-data; let path = application.debug-target-access-path; add-new!(profile.application-profile-threads, thread); // Find the number of frames on the stack so we can allocate a simple // object vector big enough to hold the instruction addresses from // the frames. let number-of-stack-frames :: = number-of-frames-on-stack(path, thread); if (number-of-stack-frames = 0) number-of-stack-frames := 1 end; // Limit the depth to which the stack is traced if required. let depth = profile-state.profile-stack-depth; if (depth & depth < number-of-stack-frames) number-of-stack-frames := depth; end; // Step through the stack frames up to the maximum depth, collecting // the instruction pointer for each frame. let ips :: = make(, size: number-of-stack-frames); let stack-frame = initialize-stack-trace(path, thread); for (index from 0 below number-of-stack-frames) ips[index] := frame-instruction-address(path, stack-frame); stack-frame := previous-frame(path, stack-frame); end; // Measure the CPU time increment let last-cpu-time-table = profile-state.profile-last-cpu-time-table; let last-cpu-time = element(last-cpu-time-table, thread, default: #f); let cpu-time = get-thread-cpu-time(path, thread); let first-snapshot? = last-cpu-time == #f; let cpu-time-increment = if (first-snapshot?) 0 else cpu-time - last-cpu-time end; last-cpu-time-table[thread] := cpu-time; // Measure the allocation increment let allocation-increment = if (first-snapshot?) allocation | 0 else allocation | get-thread-new-allocation(application, thread) end; // Create a snapshot make(, thread: thread, cpu-time-increment: cpu-time-increment, allocated-class: class, allocation-increment: allocation-increment, instruction-pointers: ips) end method take-thread-snapshot; define method prune-application-snapshots (application :: , snapshots :: ) => () let profile-state = application.application-profile-state; let profile = profile-state.ensure-profile-data; let snapshot-skip = profile.application-snapshot-skip; let snapshot-count = profile-state.profile-snapshot-count; let old-size = snapshots.size; let new-size = floor/(old-size, 2); for (new-index :: from 0 below new-size) let old-index = new-index * 2; let first-snapshot = snapshots[old-index]; let second-snapshot = snapshots[old-index + 1]; let wall-time-increment = first-snapshot.wall-time-increment + second-snapshot.wall-time-increment; let page-faults-increment = first-snapshot.page-faults-increment + second-snapshot.page-faults-increment; let thread-snapshots = make(); //---*** Fill this in! error("Application pruning not implemented yet!"); let new-snapshot = make(, thread-snapshots: thread-snapshots, wall-time-increment: wall-time-increment, page-faults-increment: page-faults-increment); snapshots[new-index] := new-snapshot end; snapshots.size := new-size; profile.application-snapshot-skip := ((snapshot-skip + 1) * 2) - 1 end method prune-application-snapshots; define method stop-profiling-thread (application :: , thread :: ) => () let profile-state = application.application-profile-state; let threads = profile-state.profile-threads; if (threads & member?(thread, threads)) profile-state.profile-threads := remove(threads, thread) end end method stop-profiling-thread; define method allocation-counter-offset (application :: ) => (offset :: ) let profile-state = application.application-profile-state; profile-state.%allocation-counter-offset | begin let path = application.debug-target-access-path; let offset-sym = find-symbol(path, "teb_allocation_counter_offset", library: application.application-dylan-runtime-library); let offset = if (offset-sym) let value = read-value(path, offset-sym.remote-symbol-address); as-signed-integer(value) else cerror("Carry on using zero as the counter offset", "Profiler internal error: Cannot find the essential runtime " "variable teb_allocation_counter_offset"); 0 end; profile-state.%allocation-counter-offset := offset end end method allocation-counter-offset; define method get-thread-new-allocation (application :: , thread :: ) => (allocation :: ) let path = application.debug-target-access-path; let offset = application.allocation-counter-offset; block () let thread-teb = dylan-thread-environment-block-address(path, thread); let counter-address = byte-indexed-remote-value(thread-teb, offset); let val = read-value(path, counter-address); write-value(path, counter-address, as-remote-value(0)); as-integer(val); exception () 0 end; end method get-thread-new-allocation; //////// // Call a function on each object that represents a // thread the client is interested in profiling. // define function do-profile-threads (f :: , application :: ) => () let profile-state = application.application-profile-state; let threads = profile-state.profile-threads; local method do-thread (thread :: ) unless (thread.thread-suspended?) f(thread) end end method do-thread; if (threads) do(do-thread, threads) else do-threads(do-thread, application.debug-target-access-path) end; end function do-profile-threads; ///////////////////////////////////////////////////////////////////////// // The exported functions ///////////////////////////////////////////////////////////////////////// define method application-profiling? (application :: ) => (profiling? :: ) let profile-state = application.application-profile-state; profile-state.profiling? end method application-profiling?; define method application-profiling-interval (application :: ) => (interval :: false-or()) let profile-state = application.application-profile-state; if (profile-state.profiling? & ~profile-state.class-profiling? & ~application.application-killed?) profile-state.profile-interval end end method application-profiling-interval; ////// // Turn profiling on // define method start-profiling (application :: , #key reset? :: = #t, snapshot-limit = unsupplied(), interval :: false-or() = #f, class-profiling? :: = #f, stack-depth = unsupplied(), threads = unsupplied()) => () let profile-state = application.application-profile-state; let already-profiling? = profile-state.profiling?; control-profiling(application, reset?: reset?, snapshot-limit: snapshot-limit, interval: interval, class-profiling?: class-profiling?, stack-depth: stack-depth, threads: threads); let breakpoints = profile-state.profile-breakpoints; do(curry(register-debug-point, application), breakpoints); unless (already-profiling? & ~reset?) profile-state.profiling? := #t; inform-profiling-started(application.debug-target-access-path) end end method start-profiling; ////// // Turn profiling off // define method stop-profiling (application :: ) => (); let profile-state = application.application-profile-state; if (profile-state.profiling?) inform-profiling-stopped(application.debug-target-access-path); profile-state.profiling? := #f; end; let breakpoints = profile-state.profile-breakpoints; do(curry(deregister-debug-point, application), breakpoints); profile-state.profile-breakpoints := #[]; if (profile-state.class-profiling?) disable-class-profiling(application) end end method stop-profiling; ////// // Return the data collected since the last reset to the client. // define method profile-data (application :: ) => (data :: ) let data = application.application-profile-state.profile-data; make(, application-snapshots: copy-sequence(data.application-snapshots), profile-threads: copy-sequence(data.application-profile-threads)) end method profile-data; define method reset-profile-data (application :: ) => () let profile-state = application.application-profile-state; profile-state.profile-data := make(); end method reset-profile-data; ////// // Select the data which is collected by the profiler manager // define method control-profiling (application :: , #key reset? :: = #f, snapshot-limit = unsupplied(), interval :: false-or() = #f, class-profiling? :: = #f, stack-depth = unsupplied(), threads = unsupplied()) => () assert(~(interval & class-profiling?), "Whoops, interval and class-profiling? specified together!"); let profile-state = application.application-profile-state; if (~profile-state.profile-data | reset?) reset-profile-data(application) end; case class-profiling? => if (profile-state.profiling?) inform-profiling-stopped(application.debug-target-access-path); profile-state.profiling? := #f; end; unless (enable-class-profiling(application)) cerror("Continue anyway", "Failed to enable class profiling") end; debug-message("Class profiling enabled"); interval => profile-state.profile-interval := interval; profile-state.profile-breakpoints := #[]; end; if (supplied?(snapshot-limit)) profile-state.profile-snapshot-limit := snapshot-limit end; if (supplied?(stack-depth)) profile-state.profile-stack-depth := stack-depth; end; if (supplied?(threads)) if (threads) let new-threads :: = make(); do(method(thread) add!(new-threads, thread) end, threads); profile-state.profile-threads := new-threads; else profile-state.profile-threads := #f; end; end; end method control-profiling; /// Class-based allocation define constant $class-breakpoint-class-offset = 2; define constant $class-breakpoint-size-offset = 4; define method find-dylan-library-symbol (application :: , name :: ) => (symbol :: false-or()) let path = application.debug-target-access-path; let dylan-library = application.application-dylan-library; find-symbol(path, name, library: dylan-library) end method find-dylan-library-symbol; // Profile breakpoint define class () end class ; define sealed domain make (subclass()); define sealed domain initialize (); define method handle-debug-point-event (application :: , breakpoint :: , thread :: ) => (stop? :: ) let profile-state = application.application-profile-state; let threads = profile-state.profile-threads; if (~threads | member?(thread, threads)) let path = application.debug-target-access-path; let size-address = calculate-stack-address(path, thread, $class-breakpoint-size-offset); let class-address = calculate-stack-address(path, thread, $class-breakpoint-class-offset); let (allocation, class) = block () values(as-integer(read-value(path, size-address)), read-value(path, class-address)) exception () 0 end; take-application-single-thread-snapshot (application, thread, allocation: allocation, class: class) end; #f; end method handle-debug-point-event; // Activate class profiling define method enable-class-profiling (application :: ) => (enabled? :: ) let symbol = application.class-breakpoint-primitive.runtime-symbol; if (symbol) let profile-state = application.application-profile-state; runtime-class-profiling-enabled?(application) := #t; profile-state.class-profiling? := #t; profile-state.profile-breakpoints := vector(make(, address: symbol.remote-symbol-address, callback: always(#f))); #t else debug-message("Failed to start class profiling") end end method enable-class-profiling; define method disable-class-profiling (application :: ) => () let profile-state = application.application-profile-state; runtime-class-profiling-enabled?(application) := #f; profile-state.class-profiling? := #f; end method disable-class-profiling; define method runtime-class-profiling-enabled?-setter (enabled? :: , application :: ) => (enabled? :: ) let variable-sym = find-dylan-library-symbol (application, mangle-in-context ("*class-profiling-enabled?*", $dylan-extensions)); let (boolean-true, boolean-false) = dylan-runtime-boolean-markers(application); if (variable-sym) let profile-state = application.application-profile-state; let path = application.debug-target-access-path; let value = if (enabled?) boolean-true else boolean-false end; write-value(path, variable-sym.remote-symbol-address, value) end; enabled? end method runtime-class-profiling-enabled?-setter; // Termination of application allocation profiling may need to wait until // a class-breakpoint has been cleared define class () end class; define method handle-debug-point-event (application :: , breakpoint :: , thread :: ) => (stop? :: ) let result = next-method(); stop-profiling(application); result end method handle-debug-point-event; define constant C-interactor = curry(C-setup-interactor, ); define constant stop-profiling-after-interaction = curry(C-setup-interactor, ); // Handling of class breakpoints in multi-threaded applications requires // that a breakpoint-pending primitive be called as a spy on an interactive thread // immediately; then the set/clear breakpoint primitives will be run as regular // interactions when the application continues; this is to enable synchronization // with regular application threads that may already be in the allocation breakpointing code. // Set a remote class breakpoint define method set-application-class-breakpoint (application :: , thread :: , class :: false-or()) => (transaction) let object-class? = // If the class is #f or , pass argument '1' to // the runtime -- all classes will break to debugger if (class) let object-class = lookup-static-object(application, "", "dylan"); class = object-class else #t end; debugger-message("Setting class breakpoint in stopped application"); run-spy-on-thread(application, thread, application.C-spy.primitive-class-breakpoint-pending); C-interactor(application, thread, application.C-spy.primitive-set-class-breakpoint, if (object-class?) as-remote-value(1) else class end, as-remote-value(5)) end method set-application-class-breakpoint; // Clear a remote class breakpoint define method clear-application-class-breakpoint (application :: , thread :: , class :: false-or(), #key stop-profile?) => (transaction) let object-class? = // If the class is #f or , pass argument '1' to // the runtime -- all classes will break to debugger if (class) let object-class = lookup-static-object(application, "", "dylan"); class = object-class else #t end; let invoker = case stop-profile? => stop-profiling-after-interaction; otherwise => C-interactor; end; debugger-message("Clearing class breakpoint in stopped application"); run-spy-on-thread(application, thread, application.C-spy.primitive-class-breakpoint-pending); invoker(application, thread, application.C-spy.primitive-clear-class-breakpoint, if (object-class?) as-remote-value(1) else class end) end method clear-application-class-breakpoint; // Clear all remote class breakpoints define method clear-application-class-breakpoints (application :: , thread :: ) => (transaction) debugger-message("Clearing all class breakpoints in stopped application"); run-spy-on-thread(application, thread, application.C-spy.primitive-class-breakpoint-pending); C-interactor(application, thread, application.C-spy.primitive-clear-class-breakpoint, as-remote-value(0)) end method clear-application-class-breakpoints;