node icon indicating copy to clipboard operation
node copied to clipboard

Apply source maps to profiles

Open mbrevda opened this issue 2 years ago • 2 comments

What is the problem this feature will solve?

When debugging bundled/compiled/minified code, it's difficult to read a profile as locations and many function names point to the compiled code.

What is the feature you are proposing to solve the problem?

Apply source maps to profiles (perhaps when --enable-source-maps is passed).

What alternatives have you considered?

post-augmentation (via the source-map package), or other tools (such as speedscope) which claim to be able to apply sourcemaps (but dont always work).

mbrevda avatar Nov 24 '23 10:11 mbrevda

There has been no activity on this feature request for 5 months. To help maintain relevant open issues, please add the https://github.com/nodejs/node/labels/never-stale label or close this issue if it should be closed. If not, the issue will be automatically closed 6 months after the last non-automated comment. For more information on how the project manages feature requests, please consult the feature request management document.

github-actions[bot] avatar May 23 '24 01:05 github-actions[bot]

maintainers: can we get some sort of signal here so that this issue doesn't stay open unnecessarily?

mbrevda avatar May 23 '24 08:05 mbrevda

@mbrevda, I'm having similar a issue did you find any workarounds or post augmentation tools for this?

brunoargolo avatar Jul 09 '24 19:07 brunoargolo

Here's a script I'm using to augment post profile

import {readFile} from 'fs/promises';
import {SourceMapConsumer} from 'source-map';
import {join} from 'path';

const sourceMap = {};
const loadSourceMap = async (codePath, filepath) => {
  const sourceFile = await readFile(codePath + '/' + filepath, 'utf-8');
  const mapFileUrlLine = sourceFile
    .split('\n')
    .reverse()
    .find((line) => line.startsWith('//# sourceMappingURL='));

  if (!mapFileUrlLine) {
    console.warning('could not find sourceMappingURL line for', filepath);
    return false;
  }

  const mapFileUrl = mapFileUrlLine.replace('//# sourceMappingURL=', '');
  const mapFile = await readFile(codePath + '/' + mapFileUrl, 'utf-8');
  if (!mapFile) {
    console.warning(`could not find source map file ${mapFile} for`, filepath);
    return false;
  }
  return await new SourceMapConsumer(mapFile);
};

const remapProfile = async (projectDir, trace) => {
  const codePath = projectDir + '/dist';

  const mappedNodes = [];
  for (const [index, node] of trace.nodes.entries()) {
    mappedNodes.push(node);
    const {url, lineNumber, columnNumber, functionName} = node.callFrame;
    if (!url || !lineNumber) continue;

    const {protocol, pathname} = new URL(url);
    if (!pathname.startsWith(codePath)) continue;
    const filepath = pathname.replace(codePath + '/', '');

    if (sourceMap[filepath] === false) continue; // we already tried to load this source map and failed
    if (!(filepath in sourceMap)) {
      sourceMap[filepath] = await loadSourceMap(codePath, filepath);
    }
    if (!sourceMap[filepath]) continue;

    const {source, line, column, name} = sourceMap[
      filepath
    ].originalPositionFor({
      line: lineNumber,
      column: columnNumber,
    });
    if (!source && !line && !column && !name) continue;

    const mappedUrl = source ? `${protocol}//` + join(codePath, source) : url;

    // use original function name if available
    let mappedFunctionName = functionName;
    if (name) {
      // if we have a name, but no function name, use the name
      if (!functionName) {
        mappedFunctionName = name;
      } else {
        // if we have both, use both
        mappedFunctionName = `${name} (${functionName})`;
      }
    }

    mappedNodes[index] = {
      ...mappedNodes[index],
      callFrame: {
        ...(mappedNodes[index]?.callFrame || {}),
        url: mappedUrl,
        lineNumber: line,
        columnNumber: column,
        functionName: mappedFunctionName,
      },
    };
  }
  return {...trace, nodes: mappedNodes};
};

export default async (projectDir, profile) => {
  return await remapProfile(projectDir, profile);
};

mbrevda avatar Jul 10 '24 10:07 mbrevda

There has been no activity on this feature request for 5 months. To help maintain relevant open issues, please add the https://github.com/nodejs/node/labels/never-stale label or close this issue if it should be closed. If not, the issue will be automatically closed 6 months after the last non-automated comment. For more information on how the project manages feature requests, please consult the feature request management document.

github-actions[bot] avatar Jan 07 '25 01:01 github-actions[bot]

I think this is important and should be kept open until implemented.

mbrevda avatar Jan 07 '25 10:01 mbrevda