Interface | Description |
---|---|
PFProfiler.Delegate |
Class | Description |
---|---|
PFProfiler | |
PFProfiler.LRUMap<U,V> | |
PFProfilerBootstrap | |
PFProfilerMixin | |
PFProfilerRequestHandler | |
PFStatsChecker | |
PFStatsNode | |
PFStatsNode.DurationCount |
ERProfiling is a framework designed to help understand how your app is functioning and why it's slow. If you've used any of the commercial profiling tools, they are great for understanding certain classes of problems; however, they often have trouble correlating specific profiling statistics with what changes you should make to the actual code. ERProfiling intends to change that. ERProfiling is designed around WebObjects and it takes a page-based approach to profiling your app. The intent is to allow you to look at the statistics for individual pages and actions in your application to understand why they perform the way they do.
When ERProfiling is enabled, every page will have a profiling bar at the bottom with statistics about the page:
There are several links on the profiler bar: "SQL," "D2W," "take," "invoke," "append," "all three," and "all." Each of these links will take you to a visual representation of the profiling stats for that particular segment of your page.
The "SQL" link by default takes you to a tree view of your database operations on the page:
[221.35ms / 100%] request: /cgi-bin/WebObjects/MDTask.woa +-[206.36ms / 93%] appendToResponse: HomePage | +-[179.52ms / 81%] appendToResponse: HomePage | | +-[179.49ms / 81%] appendToResponse: HomePage | | | +-[179.24ms / 81%] appendToResponse: HomePage | | | | +-[179.20ms / 81%] appendToResponse: WODynamicGroup | | | | | +-[179.19ms / 81%] appendToResponse: WOComponentReference | | | | | | +-[178.55ms / 81%] appendToResponse: HeaderFooter | | | | | | | +-[177.75ms / 80%] appendToResponse: HeaderFooter | | | | | | | | +-[177.74ms / 80%] appendToResponse: HeaderFooter | | | | | | | | | +-[177.44ms / 80%] appendToResponse: HeaderFooter | | | | | | | | | | +-[177.40ms / 80%] appendToResponse: WODynamicGroup | | | | | | | | | | | +-[177.37ms / 80%] appendToResponse: ERXWOConditional | | | | | | | | | | | | +-[10.75ms / 5%] appendToResponse: ERXWOConditional | | | | | | | | | | | | | +-[10.65ms / 5%] appendToResponse: ERXWOConditional | | | | | | | | | | | | | | +-[ 7.39ms / 3%] appendToResponse: ERXWOForm | | | | | | | | | | | | | | | +-[ 7.18ms / 3%] appendToResponse: PopUpButton | | | | | | | | | | | | | | | | +-[ 7.12ms / 3%] appendToResponse: PopUpButton | | | | | | | | | | | | | | | | | +-[ 5.09ms / 2%] SQL (select): entity=Company, qualifier=((billable = (java.lang.Boolean)'true') and (hidden = (java.lang.Boolean)'false')) | | | | | | | | | | | | +-[15.22ms / 7%] appendToResponse: ERXWOConditional | | | | | | | | | | | | | +-[15.15ms / 7%] appendToResponse: ERXWOConditional | | | | | | | | | | | | | | +-[11.48ms / 5%] appendToResponse: WOComponentReference | | | | | | | | | | | | | | | +-[ 9.28ms / 4%] appendToResponse: ViewHistoryList | | | | | | | | | | | | | | | | +-[ 9.27ms / 4%] appendToResponse: ViewHistoryList | | | | | | | | | | | | | | | | | +-[ 9.17ms / 4%] appendToResponse: ViewHistoryList | | | | | | | | | | | | | | | | | | +-[ 8.59ms / 4%] appendToResponse: WOGenericContainer | | | | | | | | | | | | | | | | | | | +-[ 8.22ms / 4%] appendToResponse: ERXWORepetition | | | | | | | | | | | | | | | | | | | | +-[ 4.17ms / 2%] SQL (select): entity=ViewHistory, qualifier=((id = 1036874)), {rows=2} | | | | | | | | | | | | | | | | | | | | | +-[ 3.01ms / 1%] SQL (evaluate): SELECT t0."favorite", t0."id", t0."name", t0."viewDate", t0."viewedEntityID", t0."viewedEntityName", t0."viewerID" FROM "ViewHistory" t0 WHERE t0."id" = 1036874 | | | | | | | | | | | | +-[142.18ms / 64%] appendToResponse: ERXWOComponentContent | | | | | | | | | | | | | +-[142.17ms / 64%] appendToResponse: WODynamicGroup | | | | | | | | | | | | | | +-[142.14ms / 64%] appendToResponse: WOComponentReference | | | | | | | | | | | | | | | +-[141.26ms / 64%] appendToResponse: ERXTabPanel | | | | | | | | | | | | | | | | +-[141.25ms / 64%] appendToResponse: ERXTabPanel | | | | | | | | | | | | | | | | | +-[141.24ms / 64%] appendToResponse: ERXTabPanel | | | | | | | | | | | | | | | | | | +-[141.04ms / 64%] appendToResponse: WODynamicGroup | | | | | | | | | | | | | | | | | | | +-[140.97ms / 64%] appendToResponse: ERXWOConditional | | | | | | | | | | | | | | | | | | | | +-[139.26ms / 63%] appendToResponse: ERXUniquingWrapper | | | | | | | | | | | | | | | | | | | | | +-[139.24ms / 63%] appendToResponse: ERXUniquingWrapper | | | | | | | | | | | | | | | | | | | | | | +-[139.23ms / 63%] appendToResponse: WOComponentContent | | | | | | | | | | | | | | | | | | | | | | | +-[139.22ms / 63%] appendToResponse: WODynamicGroup | | | | | | | | | | | | | | | | | | | | | | | | +-[138.61ms / 63%] appendToResponse: ERXEqualConditional | | | | | | | | | | | | | | | | | | | | | | | | | +-[97.62ms / 44%] appendToResponse: WOComponentReference | | | | | | | | | | | | | | | | | | | | | | | | | | +-[ 1.39ms / 1%] SQL (evaluate): UPDATE "ViewHistory" SET "viewDate" = TIMESTAMP '2010-01-19 13:44:36.235' WHERE ("id" = 1036874 AND "favorite" = 'false' AND "name" = 'DBManager Fixes and Support' AND "viewDate" = TIMESTAMP '2010-01-19 13:22:02.024' AND "viewedEntityID" = 'T1030353' AND "viewedEntityName" = 'Task' AND "viewerID" = 1000008) | | | | | | | | | | | | | | | | | | | | | | | | | | +-[40.10ms / 18%] appendToResponse: WorkOnTaskAssignmentForm | | | | | | | | | | | | | | | | | | | | | | | | | | | +-[39.98ms / 18%] appendToResponse: WorkOnTaskAssignmentForm | | | | | | | | | | | | | | | | | | | | | | | | | | | | +-[39.98ms / 18%] appendToResponse: WorkOnTaskAssignmentForm | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +-[39.75ms / 18%] appendToResponse: WorkOnTaskAssignmentForm | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +-[38.75ms / 18%] appendToResponse: WODynamicGroup | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +-[34.86ms / 16%] appendToResponse: ERXWOConditional | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +-[34.40ms / 16%] appendToResponse: ERXWOForm | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +-[ 3.42ms / 2%] appendToResponse: PopUpButton | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +-[ 3.38ms / 2%] appendToResponse: PopUpButton | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +-[ 2.01ms / 1%] SQL (select): entity=WorkType, qualifier=(hidden = (java.lang.Boolean)'false')
This view shows you each of your database operations along with the path-to-request of where the operation was performed. Each parent node in the tree is a component or D2W key from your page (organized hierarchically according to the structure of the page). Notice there are two nodes for some of the statements.
Each node is structure in the form "+-[exection time in milliseconds / percentage cost relative to the entire request] name of operation (optional operation subgroup): context description, {optional key-value pairs of additional metadata}". For example, "[ 4.17ms / 2%] SQL (select): entity=ViewHistory, qualifier=((id = 1036874)), {rows=2}" says that this was a SQL "select" operation that took 4.17 milliseconds to run, constituting 2% of the full request time and it was a fetch against the ViewHistory entity with the qualifier id=1036874 and it resulted in a fetch of 2 rows. Note that all times and percentages shown in this view are cumulative (meaning, the sum of the particular node along with the cost of its children noes).
For every operation you will see the EOF view of the statement (entity name and qualifier). If that EOF operation resulted in a hit to your database, you will see the actual SQL that was executed in its child node. The "evaluate" node for SQL shows only the cost of evaluating the query in the database. The node that has the {rows=x} reflects the cost of actually reading that data out of the database and converting them into EO's.
Notice that some nodes are gray. These nodes are ones that were within 90% of the cost of their parent nodes, which attempts to make inexpensive "middleman" components less prominent. For example, a conditional inside of a container is often not the issue -- it's the really expensive custom querying component 5 layers down that is actually contributing to the cost of the branch. Graying attempts to capture that concept.
The "D2W" link shows a tree view of D2W valueForKey evalutes that occurred on your page along with the path-to-request of the components where the evaluations occurred. Currently this does not show the D2W rule that actually fired, but will likely be added. The structure of the tree is identical to that of the SQL views, but will show the name of the key that was evaluated for each node.
The "take" link shows only the part of the request that was takeValuesFromRequest. The "invoke" link shows only the part of the request that was in invokeAction. The "append" link shows only the part of the request that was in appendToResponse. The "all three" link shows all three phases of the request-response loop together.
The "all" link shows everything that happened on the page. This can be huge.
If you click the "heat off" link, you will turn on Heat Maps for subsequent requests. Heat Maps attempt to visually represent the cost of the appendToResponse phase of your page. Once you turn on heat maps, future requests will have colored boxes drawn around expensive areas of the page. The closer the border colors on your page are to red, the more expensive they were. The coloring is non-linearly weighted towards red to skew the more expensive operations towards red. The colors drop the bottom 1% of cost, so divs that don't cost at least 1% of the appendToResponse time will not be outlined. Outlines are cumulative, so expensive inner divs will cause outer divs to also appear to be expensive. Note that this only shows you cost in the appendToResponse phase of your request, though the percentage is computed against the full request time. This exact calculation is subject to change in the future as we refine ways to view this more effectively.
Currently there is only one type of automatic analysis that occurs in ERProfiling. If you have a query that repeats more than ten times on the same page, ERProfiling will flag your profiling bar with a red notice "profiler errors [SQL]". As other analyses are added, the types of those errors would be listed in brackets. When you see this error, if you click on the SQL link, you will see that some nodes of the tree are colored red and will have a message like "repeated 20 times in this request". Note that this feature only works if you are using an EOF plugin that supports bind variables (where the SQL for two queries of the same family will be identical).
Profiling data currently is limited to a 30 page stack, similar to the backtrack cache. So if you perform 30 requests, the next request will drop the profiling stats from the first request (so you can't go back and view the profiling tree). I have also not done any testing with Ajax operations -- it might be a little busted. This will be corrected in the future.
Not all of the features of ERProfiling are exposed with UI (yet). There are some tricks you can perform by modifying the profiling tree URLs. A typical profiler URL looks like "http://localhost:5789/cgi-bin/WebObjects/MDTask.woa/profiler/tree?id=d651678e-2eea-4322-bacd-81cd1568f51e&filter=SQL". ERProfiling registers a custom request handler bound to "profiler."
Copyright © 2002 – 2024 Project Wonder.