RE: [Resource Timing] Unified Timing v3

Thank you James for putting the time into formulating this proposal. As per action items from the last conference call, we have been actively evaluating this proposal.

You are right that the current specification for Navigation, Resource and User Timings has a drawback that all APIs are exposed on window.performance. Your goal with the unified model is to attempt to give structure to the APIs and make them future proof - which are excellent goals. However, as we have evaluated this proposal, we have found some major drawbacks:s


1.      Generalized vs. specialized.

The proposal attempts to create a single interface that all Timings must use for data collection, storage and retrieval. At first glance, the unified interface looked like a very good idea. However, we are concerned that this single interface constrains current and future Timings to be generalized and risks losing, or making it difficult, to have access to functionality specific to that Timing.



For example, we lose the ability to do custom filtering of data querying. With the Unified Timing API, there is no way to do custom UA-based filtering of the data, which can do the filtering more efficiently than script, as we limit ourselves to just getEntriesByType() and getEntriesByName(). Let's consider a fictional future Widget Timings API. Imagine a future WidgetEntry that fires 1000s of times a second, yet one of the common ways of interacting with it is to only get Widgets of a specific "size".  Instead of having to return 1000s of objects back via getEntriesByType(), it would be nice to allow for getWidgetsBySize(500) or getWidgetsWithSizeBetween(50, 100). The generalized API has made this task difficult by requiring that the complete results be returned by getEntriesByType() by iterating over the entire set, at a loss of efficiency. Further, if the WidgetEntry only has "duration" and "size" attributes, but is logistically linked to a "parent" Widget and we don't put that "parent attribute" as part of its WidgetEntry, there would be no way to loop over all of the widgets to find children of a specific parent. The generalized API has made this task difficult. A specialized API would allow us to do something as simple as getWidgetsByParent(foo).



While this is just one fictional example we're portraying, we think a generalized interface will introduce these types of complexities and limitations.



2.       Single, circular buffer.

This model is heavily structured around a single circular buffer. However, this has a few downsides.



a.       Items stored in the buffer. Navigation Timing is a singleton and Developer Timing is a pay per use model; do either of these need to be stored in a buffer? What if Graphics Timing stores frames per second data; does this go in the same buffer?


b.       Circular buffer and fullness. The buffer is defined as a circular buffer, which is different from the capped/limited buffer discussed in Resource Timing.  The advantages of a circular buffer is that there is no need to monitor the "fullness" of it, but there are disadvantages.  Since the items aren't stable within the buffer, sequential querying of the interface can lead to inconsistent results if the developer isn't aware of the circular behavior.  Consider this naive way of using the interface:

var theFirstResource = getEntriesByType(PERF_RESOURCE)[0];
// the 151st resource is downloaded, and kicks the first entry out since it's a circular buffer
var theSecondResource = getEntriesByType(PERF_RESOURCE)[1]; // INCORRECT -- this is actually the 3rd downloaded resource as the buffer wrapped

On the other hand, with the capped buffer in Resource Timing, no external events modify the state or order of "current" items in the array, unless the user explicitly clears the buffer (clearResourceTimings()).



c.        clearBuffer() clears the entire buffer.  What if I'm not only interested clearing Marks and not Resource Timing or Navigation Timing?

d.       Default buffer size of 150 was designed for the estimated average number of resources on a future page. With Unified Timing, this buffer size requirements will limit memory growth of all types. Consider the case of Widgets Timing, where Widgets are added 1000 times every second.  While Widgets are not enabled by default per your suggestion, developers may want to enable them because they're very common.  The circular buffer will mean previous Navigation and Resource Timing data is lost immediately. This forces the user agent to either specify a large buffer size, which is a waste of memory, or the developer must increase the buffer size in the <head>, which is a poor performance practice, or the developer must let data be lost, which is a poor experience. None of these options are attractive.

3.      Additional concerns:

a.       Unified Timing's getEntriesByName() could have collisions amongst the various types.  For example, ResourceTiming is keyed by an URL.  If we add a future timing type that is keyed by URL, there would be a collision.  Developers querying the interface would have to be careful to check the returned timing type.

b.       Disabling new Timing types by default. Considering adding script to the <head> is a poor performance practice and analytics libraries will want timing enabled by default, disabling Timings by default is not the best approach.

c.        The "onentryadded" event isn't filtered by type, so if we have future high-frequency timing events, the event could introduce overhead if script is just looking for a specific subsets of events.  Dealing with the various timing types will require lots of "if typeof" statements.

d.       The Unified APIs make querying the data a bit more verbose. Consider querying the results of NavigationTiming, ResourceTiming and UserTiming:

performance.timing

performance.getResourceTiming()

performance.getMarks()


The Unified APIs is a bit longer to read:
performance.getEntriesByType(performance.PERF_NAVIGATION)[0];

performance.getEntriesByType(performance.PERF_RESOURCE);
performance.getEntriesByType(performance.PERF_DEVELOPER_MARK);

Since the Unified API might kick timings out of the buffer, just querying for navigationStart changes from:
var navStart = performance.timing.navigationStart;

           To:
              var navs = performance.getEntriesByType(performance.PERF_NAVIGATION);
              if (navs[0]) {
                     var navStart = navs[0].navigationStart;
              }

I think we need to step back and try to understand what kind of an API a web developer would find useful that allows for specialized methods and at the same time gives a consistent structure. For example, a hierarchy under the window.performance namespace may preserve both structure and specialization of the API. Eg., performance.navigation, performance.resource, performance.developer, performance.graphics would contain all APIs for Navigation Timing, Resource Timing, Developer Timing and Graphics Timing, respectively.

Let's spend some time in tomorrow's call brainstorming how this API would look like.

Thanks,
Jatinder

Received on Wednesday, 15 June 2011 02:09:13 UTC