Module: environment-reports Author: Andy Armstrong Synopsis: Profile report generator 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 /// Profile reports define abstract class () constant slot report-qualify-names? :: = #f, init-keyword: qualify-names?:; //---*** Not currently used... // slot report-profile-results :: false-or() = #f; //---*** Not currently used... // constant slot report-include-source? :: = #t, // init-keyword: include-source?:; end class ; /*---*** Not currently used... define abstract class () end class ; */ define class () end class ; install-report(#"raw-profile", "Raw profile", , edition: #"enhanced"); define class () end class ; install-report(#"profile-summary", "Profile summary", , edition: #"enhanced"); define class () //---*** Not currently used! // constant slot report-cut-off-percentage :: = 3.0, // init-keyword: cut-off-percentage:; end class ; /*---*** Not yet finished! install-report(#"profile-call-history", "Profile call history", , edition: #"enhanced"); */ /*---*** Not yet implemented! define class () end class ; install-report(#"full-profile", "Full profile", , edition: #"enhanced"); */ /// Raw profile define method write-report-as (stream :: , report :: , _format == #"text") => () let project = report.report-project; let application = project.project-application; let profile = project.project-last-profile; let snapshot-index :: = 1; let elapsed-wall-time :: = 0; let elapsed-page-faults :: = 0; let total-allocation :: = 0; let thread-totals :: = make(); format(stream, "Profile results for %s\n\n", application.application-filename); format(stream, " Snapshots: %d\n", profile.application-total-snapshots); format(stream, " Wall time: %d\n", profile.application-total-wall-time); format(stream, " Page faults: %d\n", profile.application-total-page-faults); new-line(stream); do-application-profile-snapshots (method (snapshot :: ) format(stream, "%s\n", $report-separator); let wall-time = snapshot.application-snapshot-wall-time; let page-faults = snapshot.application-snapshot-page-faults; increment!(elapsed-wall-time, wall-time); increment!(elapsed-page-faults, page-faults); format(stream, "Snapshot %d\n", snapshot-index); format(stream, " Wall time: %s [total %s]\n", integer-to-string(wall-time, size: 7, fill: ' '), integer-to-string(elapsed-wall-time, size: 7, fill: ' ')); format(stream, " Page faults: %s [total %s]\n", integer-to-string(page-faults, size: 7, fill: ' '), integer-to-string(elapsed-page-faults, size: 7, fill: ' ')); new-line(stream); do-application-snapshot-thread-snapshots (method (snapshot :: ) let thread = snapshot.thread-snapshot-thread; format(stream, "Snapshot: %s\n", profile-object-name(report, thread)); let cpu-time = snapshot.thread-snapshot-cpu-time; let allocation = snapshot.thread-snapshot-allocation; let class = snapshot.thread-snapshot-allocated-class; let thread-elapsed-cpu-time = element(thread-totals, #"cpu", default: 0); let thread-total-allocation = element(thread-totals, #"allocation", default: 0); element(thread-totals, #"cpu") := thread-elapsed-cpu-time + cpu-time; element(thread-totals, #"allocation") := thread-total-allocation + allocation; increment!(total-allocation, allocation); format(stream, " CPU time: %s [thread total %s]\n", integer-to-string(cpu-time, size: 7, fill: ' '), integer-to-string(thread-elapsed-cpu-time, size: 7, fill: ' ')); format(stream, " Allocation: %s [thread total %s] [total %s]\n", integer-to-string(allocation, size: 7, fill: ' '), integer-to-string(thread-total-allocation, size: 7, fill: ' '), integer-to-string(total-allocation, size: 7, fill: ' ')); if (class) format(stream, " Class: %s\n", profile-object-name(report, class)) end; new-line(stream); let frame-index :: = 1; do-thread-snapshot-functions (method (form :: , location :: false-or()) let name = profile-object-name(report, form); format(stream, "%s. %s\n", integer-to-string(frame-index, size: 5, fill: ' '), name); increment!(frame-index) end, application, snapshot); format(stream, "\n\n"); end, snapshot); increment!(snapshot-index) end, profile) end method write-report-as; /* define method write-report-as (stream :: , report :: , _format == #"html") => () let title = format-to-string("%s Bug Report", release-product-name()); with-html-output (stream, title) for (section in $profile-report-sections) let section-title = section[0]; let section-keyword = section[1]; write-html(stream, #"h2", section-title, #"/h2", '\n'); write-html(stream, '\n', #"p", '\n'); write-html-profile-report-section(stream, report, section-keyword) end end end method write-report-as; */ /// Profile summary define abstract class () end class ; define sealed domain make (subclass()); define sealed domain initialize (); define class () constant slot info-threads :: , required-init-keyword: threads:; constant slot info-count :: = 0, required-init-keyword: count:; constant slot info-wall-time :: , required-init-keyword: wall-time:; constant slot info-page-faults :: , required-init-keyword: page-faults:; end class ; define class () constant slot info-thread :: , required-init-keyword: thread:; constant slot info-objects :: = make(); slot info-count :: = 0; slot info-cpu-time :: = 0; slot info-allocation :: = 0; end class ; define abstract class () slot info-count :: = 0; slot info-cpu-time :: = 0; slot info-allocation :: = 0; end class ; define class () constant slot info-function :: , required-init-keyword: function:; end class ; define class () constant slot info-class :: , required-init-keyword: class:; end class ; define method process-profile-summary (project :: , profile :: , #key type :: = #"function", show-foreign-functions? :: = #f) => (summary :: ) let application = project.project-application; let threads :: = make(); let total-count :: = 0; let total-wall-time :: = 0; let total-page-faults :: = 0; do-application-profile-threads (method (thread :: ) let thread-info = make(, thread: thread); add!(threads, thread-info); let objects = thread-info.info-objects; do-thread-profile-snapshots (method (application-snapshot :: , thread-snapshot :: ) let cpu-time = thread-snapshot.thread-snapshot-cpu-time; let wall-time = application-snapshot.application-snapshot-wall-time; let allocation = thread-snapshot.thread-snapshot-allocation; let page-faults = application-snapshot.application-snapshot-page-faults; increment!(total-wall-time, wall-time); increment!(total-page-faults, page-faults); increment!(thread-info.info-count); increment!(thread-info.info-cpu-time, cpu-time); increment!(thread-info.info-allocation, allocation); select (type) #"function" => if (cpu-time >= 0 | allocation >= 0) block (return) do-thread-snapshot-functions (method (form :: , location :: false-or()) unless (hidden-function? (project, form, show-foreign-functions?: show-foreign-functions?)) let info = element(objects, form, default: #f) | begin objects[form] := make(, function: form) end; increment!(info.info-count); increment!(info.info-cpu-time, cpu-time); increment!(info.info-allocation, allocation); return() end end, application, thread-snapshot) end end; #"class" => let class = thread-snapshot.thread-snapshot-allocated-class; if (class) let info = element(objects, class, default: #f) | begin objects[class] := make(, class: class) end; increment!(info.info-count); increment!(info.info-cpu-time, cpu-time); increment!(info.info-allocation, allocation); end; end end, application, profile, thread) end, profile); make(, threads: as(, threads), total-count: total-count, wall-time: total-wall-time, page-faults: total-page-faults) end method process-profile-summary; define method write-report-as (stream :: , report :: , _format == #"text") => () let project = report.report-project; let profile = project.project-last-profile; let info = process-profile-summary(project, profile); let count = 20; format(stream, "Profile summary\n\n"); format(stream, " Totals:\n\n"); format(stream, " Wall time: %d\n", info.info-wall-time); format(stream, " Page faults: %d\n", info.info-page-faults); for (thread-info :: in info.info-threads) let thread = thread-info.info-thread; format(stream, "%s\n", $report-separator); format(stream, "%s\n", profile-object-name(report, thread)); format(stream, " Totals:\n\n"); format(stream, " Samples: %d\n", thread-info.info-count); format(stream, " CPU time: %d\n", thread-info.info-cpu-time); format(stream, " Allocation: %d\n", thread-info.info-allocation); let functions = as(, thread-info.info-objects); local method print-summary (title :: , getter :: ) => () format(stream, "\n %s:\n\n", title); let functions = sort(functions, test: method (f1 :: , f2 :: ) f1.getter > f2.getter end); for (function-info :: in functions, index from 1 to count) format(stream, " %s. %s [%s] %s\n", integer-to-string(index, size: 5, fill: ' '), integer-to-string(function-info.getter, size: 7, fill: ' '), integer-to-string(function-info.info-count, size: 5, fill: ' '), profile-object-name(report, function-info.info-function)) end end method print-summary; print-summary("Exclusive stack summary", info-cpu-time); print-summary("Exclusive allocation summary", info-allocation); end end method write-report-as; /* define method write-report-as (stream :: , report :: , _format == #"html") => () let title = format-to-string("%s Bug Report", release-product-name()); with-html-output (stream, title) for (section in $profile-report-sections) let section-title = section[0]; let section-keyword = section[1]; write-html(stream, #"h2", section-title, #"/h2", '\n'); write-html(stream, '\n', #"p", '\n'); write-html-profile-report-section(stream, report, section-keyword) end end end method write-report-as; */ /// Time line report define method write-report-as (stream :: , report :: , _format == #"text") => () let project = report.report-project; let application = project.project-application; let profile = project.project-last-profile; let thread-index :: = 1; do-application-profile-threads (method (thread :: ) format(stream, "%s\n", $report-separator); format(stream, "%s\n\n", profile-object-name(report, thread)); write-thread-report(stream, report, thread, _format); increment!(thread-index) end, profile) end method write-report-as; /* define method write-report-as (stream :: , report :: , _format == #"html") => () let title = format-to-string("%s Bug Report", release-product-name()); with-html-output (stream, title) for (section in $profile-report-sections) let section-title = section[0]; let section-keyword = section[1]; write-html(stream, #"h2", section-title, #"/h2", '\n'); write-html(stream, '\n', #"p", '\n'); write-html-profile-report-section(stream, report, section-keyword) end end end method write-report-as; */ define class () sealed constant slot call-history-root-references :: , required-init-keyword: root-references:; sealed constant slot call-history-total-cpu-time :: , required-init-keyword: total-cpu-time:; sealed constant slot call-history-total-wall-time :: , required-init-keyword: total-wall-time:; end class ; define sealed domain make (subclass()); define sealed domain initialize (); define class () sealed constant slot profile-frame-frame :: , required-init-keyword: frame:; sealed constant slot profile-frame-references :: = make(); sealed constant slot profile-frame-start-cpu-time :: = 0, init-keyword: start-cpu-time:; sealed constant slot profile-frame-start-wall-time :: = 0, init-keyword: start-wall-time:; sealed slot profile-frame-cpu-time :: = 0, init-keyword: cpu-time:; sealed slot profile-frame-wall-time :: = 0, init-keyword: wall-time:; sealed slot profile-frame-allocation :: = 0, init-keyword: allocation:; end class ; define sealed domain make (subclass()); define sealed domain initialize (); define class () sealed constant slot profile-frame-allocated-class :: , required-init-keyword: allocated-class:; sealed constant slot profile-frame-source-location :: false-or(), required-init-keyword: source-location:; end class ; define sealed domain make (subclass()); define sealed domain initialize (); define method process-profile-call-history (project :: , profile :: , thread :: , #key show-foreign-functions? :: = #f) => (history :: ) let profile = project.project-last-profile; let application = project.project-application; let stack :: = make(); let elapsed-cpu-time :: = 0; let elapsed-wall-time :: = 0; let root-references :: = make(); do-application-profile-snapshots (method (snapshot :: ) let thread-snapshot = application-snapshot-thread-snapshot(snapshot, thread); let wall-time = snapshot.application-snapshot-wall-time; let page-faults = snapshot.application-snapshot-page-faults; increment!(elapsed-wall-time, wall-time); if (thread-snapshot) let cpu-time = thread-snapshot.thread-snapshot-cpu-time; let allocation = thread-snapshot.thread-snapshot-allocation; let class = thread-snapshot.thread-snapshot-allocated-class; let frames = thread-snapshot-frame-snapshots(application, thread-snapshot); let stack-index :: = stack.size - 1; let frame-index :: = frames.size - 1; increment!(elapsed-cpu-time, cpu-time); // Increment the counts for all shared items on the stack block (return) while (stack-index >= 0 & frame-index >= 0) let frame = frames[frame-index]; let info :: = stack[stack-index]; if (info.profile-frame-frame ~== frame) return() end; increment!(info.profile-frame-cpu-time, cpu-time); increment!(info.profile-frame-allocation, allocation); increment!(info.profile-frame-wall-time, wall-time); decrement!(frame-index); decrement!(stack-index) end end; // Remove any obsolete frames from the stack while (stack-index >= 0) pop(stack); decrement!(stack-index) end; let top-of-stack = ~empty?(stack) & stack[0]; // Now pop on the new ones while (frame-index >= 0) let frame = frames[frame-index]; let info = make(, frame: frame, start-wall-time: elapsed-wall-time, start-cpu-time: elapsed-cpu-time, cpu-time: cpu-time, wall-time: wall-time, allocation: allocation); if (top-of-stack) add!(top-of-stack.profile-frame-references, info) else add!(root-references, info) end; top-of-stack := info; push(stack, info); decrement!(frame-index) end; // Add any allocated class to the first non-internal call on // the stack. if (class & top-of-stack) block (return) for (index :: from 0, function-call :: in stack) if (index > 0) let frame = function-call.profile-frame-frame; unless (hidden-function? (project, frame.frame-snapshot-function, show-foreign-functions?: show-foreign-functions?)) add!(function-call.profile-frame-references, make(, allocated-class: class, source-location: frame.frame-snapshot-source-location)); return() end end end end end else for (function-call :: in stack) increment!(function-call.profile-frame-wall-time, wall-time) end end end, profile); make(, root-references: as(, root-references), total-cpu-time: elapsed-cpu-time, total-wall-time: elapsed-wall-time) end method process-profile-call-history; define method write-thread-report (stream :: , report :: , thread :: , _format == #"text") => () error("Not yet implemented!") end method write-thread-report; /// Full profile /* define method write-report-as (stream :: , report :: , _format == #"text") => () format(stream, "%s\n", $profile-report-first-line); for (section in $profile-report-sections) let section-title = section[0]; let section-keyword = section[1]; format(stream, "%s\n", $report-separator); format(stream, "%s:\n\n", as-uppercase(section-title)); write-profile-report-section(stream, report, section-keyword) end; format(stream, "%s\n", $report-separator); format(stream, "%s\n", $profile-report-last-line) end method write-report-as; define method write-report-as (stream :: , report :: , _format == #"html") => () let title = format-to-string("%s Bug Report", release-product-name()); with-html-output (stream, title) for (section in $profile-report-sections) let section-title = section[0]; let section-keyword = section[1]; write-html(stream, #"h2", section-title, #"/h2", '\n'); write-html(stream, '\n', #"p", '\n'); write-html-profile-report-section(stream, report, section-keyword) end end end method write-report-as; /// Profile processing define constant $profile-results = make(, weak?: #t); define class () constant slot application-profile :: , required-init-keyword: profile:; constant slot thread-profile-results :: , required-init-keyword: results:; end class ; define sealed domain make (subclass()); define sealed domain initialize (); define class () end class ; define sealed domain make (subclass()); define sealed domain initialize (); define method profile-results (project :: , profile :: ) => (results :: ) element($profile-results, profile, default: #f) | begin let results = process-profile-results(project, profile); $profile-results[profile] := results end end method profile-results; define method process-profile-results (project :: , profile :: ) => (results :: ) let thread-profile-results = make(); do-application-profile-threads (method (thread :: ) add!(thread-profile-results, process-thread-profile-results(project, thread)) end, profile); make(, profile: profile, results: thread-profile-results) end method process-profile-results; define method process-thread-profile-results (project :: , thread :: ) => (results :: ) make() end method process-thread-profile-results; */ /// Utilities define method profile-object-name (report :: , object :: ) => (name :: ) let project = report.report-project; let qualify-names? = report.report-qualify-names?; environment-object-display-name (project, object, #f, qualify-names?: qualify-names?) end method profile-object-name; define method hidden-function? (project :: , function :: , #key show-foreign-functions? :: = #f) => (hidden? :: ) instance?(function, ) | (~show-foreign-functions? & instance?(function, )) end method hidden-function?; /* define method thread-profile-total-value (application :: , thread :: , type :: ) => (total-value :: ) let total-value :: = 0; do-thread-profile-snapshots (method (application-snapshot :: , thread-snapshot :: ) let value = thread-snapshot-value(application, snapshot, type); increment!(total-value, value) end, application, profile, thread); total-value end method thread-profile-total-value; */