Skip to content

Commit

Permalink
fix: improve speed 15x by using @indutny/breakpad
Browse files Browse the repository at this point in the history
Before:
```
$ npm test
Test Suites: 2 passed, 2 total
Tests:       7 passed, 7 total
Snapshots:   4 passed, 4 total
Time:        48.887 s
```

After:
```
$ npm test
Test Suites: 2 passed, 2 total
Tests:       7 passed, 7 total
Snapshots:   4 passed, 4 total
Time:        3.186 s
```
  • Loading branch information
indutny committed Jun 17, 2024
1 parent 9d86593 commit 46377f2
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 41 deletions.
89 changes: 54 additions & 35 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const fs = require('fs')
const stream = require('stream')
const { promisify } = require('util')

const breakpad = require('parse-breakpad')
const { symbolicateFrames } = require('@indutny/breakpad');
const got = require('got')
const mkdirp = require('mkdirp')
const yargs = require('yargs')
Expand All @@ -14,63 +14,81 @@ const symbolicate = async (options) => {
const {force, file} = options
const cacheDirectory = path.join(__dirname, 'cache', 'breakpad_symbols')

const symbolCache = new Map
const dumpText = await fs.promises.readFile(file, 'utf8')
const images = binaryImages(dumpText)
const electronImage = images.find(v => /electron/i.test(v.library))
if (electronImage) console.error(`Found Electron ${electronImage.version}`)
else console.error('No Electron image found')

let result = []
const lines = dumpText.split(/\r?\n/);

for (const line of dumpText.split(/\r?\n/)) {
const linesByImage = new Map();
for (const [lineIndex, line] of lines.entries()) {
const parsedLine = parseAddressLine(line)
if (parsedLine) {
const library = parsedLine.libraryBaseName || parsedLine.libraryId
const image = images.find(i => i.library === library || path.basename(i.path) === library)
if (image) {
const offset = parsedLine.address - image.startAddress
const res = await symbolicateOne({
image,
offset
})
if (res) {
result.push(line.substr(0, parsedLine.replace.from) + res.func.name + line.substr(parsedLine.replace.from + parsedLine.replace.length))
continue
}
if (!parsedLine) {
continue;
}

const library = parsedLine.libraryBaseName || parsedLine.libraryId
const image = images.find(i => i.library === library || i.basename === library)
if (!image) {
continue;
}

const offset = parsedLine.address - image.startAddress
const imageKey = `${image.debugId}/${image.basename}`;

let entry = linesByImage.get(imageKey);
if (!entry) {
entry = { image, group: [] };
linesByImage.set(imageKey, entry);
}
entry.group.push({
image,
offset,
lineIndex,
parsedLine,
});
}

for (const { image, group } of linesByImage.values()) {
const { debugId, basename: moduleBasename, extname: moduleExtname } = image
const suffix = moduleExtname === '.pdb' ? '1' : '0';
const stream = await getSymbolFile(debugId.replace(/-/g, '') + suffix, moduleBasename)
if (!stream) {
continue;
}

const frames = group.map(({ offset }) => offset);
const symbolicated = await symbolicateFrames(stream, frames);

for (const [index, symbol] of symbolicated.entries()) {
if (!symbol) {
continue;
}

const { lineIndex, parsedLine } = group[index];
const line = lines[lineIndex];
lines[lineIndex] = line.substr(0, parsedLine.replace.from) + symbol.name + line.substr(parsedLine.replace.from + parsedLine.replace.length);
}
result.push(line)
}

return result.join('\n')
return lines.join('\n')

async function getSymbolFile(moduleId, moduleName) {
const pdb = moduleName.replace(/^\//, '')
const symbolFileName = pdb.replace(/(\.pdb)?$/, '.sym')
const symbolPath = path.join(cacheDirectory, pdb, moduleId, symbolFileName)
if (fs.existsSync(symbolPath) && !force) {
return breakpad.parse(fs.createReadStream(symbolPath))
return fs.createReadStream(symbolPath)
}
if (!fs.existsSync(symbolPath) && (!fs.existsSync(path.dirname(symbolPath)) || force)) {
for (const baseUrl of SYMBOL_BASE_URLS) {
if (await fetchSymbol(cacheDirectory, baseUrl, pdb, moduleId, symbolFileName))
return breakpad.parse(fs.createReadStream(symbolPath))
return fs.createReadStream(symbolPath)
}
}
}

async function symbolicateOne({image, offset}) {
const { debugId, path: modulePath } = image
if (!symbolCache.has(debugId)) {
const suffix = path.extname(modulePath) === '.pdb' ? '1' : '0';
const parsed = await getSymbolFile(debugId.replace(/-/g, '') + suffix, path.basename(modulePath))
symbolCache.set(debugId, parsed)
}
const parsed = symbolCache.get(debugId)
if (parsed)
return parsed.lookup(offset)
}
}

const binaryImages = (dumpText) => {
Expand All @@ -86,15 +104,16 @@ const binaryImages = (dumpText) => {
let m
const images = []
while (m = re.exec(dumpText)) {
const [, startAddress, endAddress, plus, library, version, debugId, path] = m
const [, startAddress, endAddress, plus, library, version, debugId, modulePath] = m
const image = {
startAddress: parseInt(startAddress, 16),
endAddress: parseInt(endAddress, 16),
plus,
library,
version,
debugId,
path
basename: path.basename(modulePath),
extname: path.extname(modulePath),
}
const existing = images.find(v => v.library === image.library)
if (existing) {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@
],
"license": "ISC",
"dependencies": {
"@indutny/breakpad": "^1.2.0",
"got": "^11.8.2",
"mkdirp": "^1.0.4",
"parse-breakpad": "^0.1.0",
"yargs": "^17.0.1"
},
"engines": {
Expand Down
17 changes: 12 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,18 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==

"@indutny/breakpad-parser-wasm@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@indutny/breakpad-parser-wasm/-/breakpad-parser-wasm-1.0.0.tgz#bc91c12a6d6a11a4c5366e8a46a3e317f3a7b54f"
integrity sha512-v+/bvaPaz60GJB0vd2trXLu254KWdSB7LIcI2VljZ2eq8Ij478PK+5T7HUPt8uf7TK/5lFfhQZsBdpisGdYwaQ==

"@indutny/breakpad@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@indutny/breakpad/-/breakpad-1.2.0.tgz#17d19f1210536bd2ed72da91e60a17ba5efc8be8"
integrity sha512-JjG4Y82p710old/xO9jCcFCjoDwR03IWauCuwWD2P5sXQDRzxz3S4CKZAYRDhU7avxf502RMvCQSvI9WLMDQsg==
dependencies:
"@indutny/breakpad-parser-wasm" "^1.0.0"

"@istanbuljs/load-nyc-config@^1.0.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced"
Expand Down Expand Up @@ -2076,11 +2088,6 @@ p-try@^2.0.0:
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==

parse-breakpad@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/parse-breakpad/-/parse-breakpad-0.1.0.tgz#6d3c80c856d35550b6adea374b93ec41d758ee8b"
integrity sha512-xF9F9+LnMzPhXZSExrvyhzormvlR0Co9LJ9Un8SPDMiMNm3PqIwfYiB4UcqG50yc9g42H2Fndpx/5gfNqdgowQ==

parse-json@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd"
Expand Down

0 comments on commit 46377f2

Please sign in to comment.