resource-timing icon indicating copy to clipboard operation
resource-timing copied to clipboard

Resource direct lookup support

Open sean-roberts opened this issue 3 years ago • 7 comments

Short (bad-version) idea: Have a way to associate a resource with its performance entry via the event handlers for the resource itself. This could be a callback of sorts or an identifier/reference. But something that helps me map this resource to its timing.

When building tooling to analyze the performance of requests, we need to measure a few things - status code, request options, response bodies, etc. In almost all perf tooling, we will also need to pull what information we can from the performance entries. As of right now, that needs to be some level of heavily nuanced fuzzy logic around timing and order (which can be different cross-browser and across the different resource types). This makes things pretty challenging and less reliable than we would like.

as a trivial ("bad version") example:

const img = document.createElement('img'); 
img.addEventListener('load', (event)=>{
    performance.getEntriesByIdentifier(event.target.resourceIdentifier);
})
img.src =  '/icon.jpg'; 
document.body.appendChild(img);

I'm less interested in what it looks like personally but meeting that goal of the direct lookup. I think it's totally acceptable for the entry to not be there yet (invalidating the above example for all usecases) and we can use that identifier in conjunction with the performance observer to associate resources.

sean-roberts avatar Feb 01 '21 17:02 sean-roberts

Why is performance.getEntriesByName(img.src) not good enough in the above example?

npm1 avatar Feb 01 '21 17:02 npm1

In the above example, it would be sufficient if we can guarantee it was the only resource with that name. However, that isn't a good assumption to make. Retries, duplicate loads (like avatars used in a few places), prefetching, POST requests, etc. are all scenarios that make name alone unviable.

What's more, it seems that the resource entries of various entryTypes aren't always added at the same point so you can't apply the same logic to all scenarios. For example, the perf entry for the image resource is there at the point of onload (allowing the example above to work) but for Fetch, it won't be there during the first then() handler so it needs observer logic like, "get me the next resource with this name." These inconsistencies add to the unreliable nature of name alone and now require startTime requirements as well.

sean-roberts avatar Feb 01 '21 19:02 sean-roberts

A resource used in a few places won't trigger multiple entries. It should be cached and reused if it's the same resource. But yea there are cases where there could be multiple entries with the same name. I just don't think the API you are suggesting is feasible. You mention the fetch example. Where would you set the resourceIdentifier in that case? If an img src is later set then would you expect the img resourceIdentifier to change accordingly? Where would you query this for CSS? Etc.

npm1 avatar Feb 01 '21 19:02 npm1

A resource used in a few places won't trigger multiple entries.

For some reason, I missed that understanding around only one entry being put in the entries list (could I trouble you for a link to the spec around the rules deciding that?). But, to be honest, I think that might add to the complexity here. How would I know which resource to attribute to an entry and which should I attribute to another one (ala it's cached)? In that case, I'd expect to be able to identify each resource with its associated performance entry and know if it initiated it or if it was cached/linked to that other. <- I think it's worth deferring that nuance for the time being though to start simple.

I just don't think the API you are suggesting is feasible.

To be clear, I don't have a thought out suggestion for this API - just an example of what that might look like to illustrate the ask/ticket needs. So that very well may be true but I never expected it to be doable out of the gate :)

But thinking about it, IF we went with an identifier key that we'd used to look up the entry (let's keep with the resourceIdentifier name), I'd expect it to be on the instance of the networking APIs that provides the response information so prototypes of XMLHttpRequest and Response provided to fetch. For other resources like the doc, images, scripts, etc. I'm unaware of a precedent that exists to get this information. One opportunity there would be to extend the networking-related events (onload/onerror) of the respective items. IF that were to happen, then I'd expect the resourceIdenfier to change if the img.src changed because it would be a new load/error event instance that included that information. With these few hooks, it would allow API usage that can be inspected to have direct lookup capabilities with a resourceIdentifier.

I am entirely unwed to this idea and open to better alternatives for direct lookups or differentiation capabilities.

sean-roberts avatar Feb 01 '21 22:02 sean-roberts

Dropping this raw, very loosely thought through idea.. perhaps the resource performance entries contain an array of ids for resources which have id fields. So if any resource network request is triggered by an object containing an .id field, that identifier is added to this list if it's serviced by this performance entry. There aren't standard id fields for some object types so I'm unsure of what it would mean to introduce that or if it's a non-standard field that can be leveraged as it's only a reference point that doesn't have side-effects that would break future standardization of an id field.

sean-roberts avatar Feb 07 '21 19:02 sean-roberts

One solution for this problem I've been using was assigning requests IDs and inserting those into no-op URL params. The solution is not pretty and requires some custom request & getEntries link management. e.g.: XHR to POST /foo which could happen multiple times in an app. Solution:

let id = 0;
function request(url, data) {
  id++;
  xhr(`${url}?id=$id}`, data, id);
}

function onResponse(url, id) {
  const entry = performance.getEntriesByName(`${url}?id=$id}`)[0];
  log(url, entry.duration);
}

I believe this could be adapted to resources initiated via regular html tags, e.g.: tracking impression images.

// Assumes page outputs tracking <img> tags with unique ID attribute. It could be in the URL, just need to parse.
document.body.addEventListener('load', (e) => {
  if (e.target.tagName == 'IMG') {
    const url = e.target.src;
    const id = e.target.id;
    const entry = performance.getEntriesByName(`${url}?id=$id}`)[0];
    log(url, entry.duration);
  }
}, /* capture */ true);

marcelduran avatar May 04 '21 19:05 marcelduran

With the increasing usage of Graphql, we are seeing this more often as well. The entry name is the same for dozens of requests on every page load. Since they are all POST requests the only differentiator is the body of the request.

name: "http://my.gql.server/graphql"

I'm currently using the QS string hack posted above, but many applications reject unknown query string entries.

carbonrobot avatar Sep 26 '23 15:09 carbonrobot