Problem Statement
I was working on one of our instrumentation refactors, and I ended up having dozens of configs (via @apm-js-collab/tracing-hooks) per one source code file. Ultimately, that wasn't the best way of doing things, but it did reveal that Transformer.transform walks the AST once per config, resulting in N traversals of the same AST for a file.
I am curious if this is an observation other APM vendors have made and that if the following optimization would be worth it. Essentially, the idea is that we walk the AST once per file rather than once per config.
Potential Solution
Walk the AST once for all configs. Collect every config's selector up front, traverse the union of them a single time, and at each matched node dispatch to each config whose own selector matches:
const matchers = [] // { state, query, selector } per config
for (const config of this.#configs) {
// ...build query/state as before...
matchers.push({ state, query, selector: esquery.parse(query) })
}
if (matchers.length > 0) {
const combined = esquery.parse(matchers.map(({ query }) => query).join(', '))
esquery.traverse(ast, combined, (node, parent, ancestry) => {
for (const { state, selector } of matchers) {
if (esquery.matches(node, selector, ancestry)) {
injectionCount++
this.#visit(state, node, parent, ancestry)
}
}
})
}
This takes traversals from N down to 1, regardless of how many configs target the file. But I think this is a constant-factor win — total matching work stays O(configs × AST nodes), since at each node we still check it against every config's selector. What we actually save is running esquery's tree-walk machinery once instead of once per config. So I'm curious whether that overhead is meaningful enough in practice to be worth it, or if I'm just working around a config setup I shouldn't have had in the first place.
Problem Statement
I was working on one of our instrumentation refactors, and I ended up having dozens of configs (via
@apm-js-collab/tracing-hooks) per one source code file. Ultimately, that wasn't the best way of doing things, but it did reveal thatTransformer.transformwalks the AST once per config, resulting in N traversals of the same AST for a file.I am curious if this is an observation other APM vendors have made and that if the following optimization would be worth it. Essentially, the idea is that we walk the AST once per file rather than once per config.
Potential Solution
Walk the AST once for all configs. Collect every config's selector up front, traverse the union of them a single time, and at each matched node dispatch to each config whose own selector matches:
This takes traversals from N down to 1, regardless of how many configs target the file. But I think this is a constant-factor win — total matching work stays
O(configs × AST nodes), since at each node we still check it against every config's selector. What we actually save is runningesquery's tree-walk machinery once instead of once per config. So I'm curious whether that overhead is meaningful enough in practice to be worth it, or if I'm just working around a config setup I shouldn't have had in the first place.