Created
February 18, 2016 22:58
-
-
Save nojvek/1213471a84775421058e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
fs = require 'fs' | |
readline = require 'readline' | |
path = require 'path' | |
c = console | |
### | |
# Helpers | |
### | |
parseCliForPath = -> | |
# parse cli args | |
argv = process.argv | |
if argv.length < 3 | |
c.log "Usage: coffee chromeProfileConverter.coffee trace.csv" | |
process.exit(1) | |
traceCSVFilePath = argv[2] | |
if not fs.existsSync(traceCSVFilePath) | |
c.log traceCSVFilePath, "doesn't exist" | |
process.exit(1) | |
return traceCSVFilePath | |
parseCSV = (traceCSVFilePath) -> | |
objects = [] | |
lines = fs.readFileSync(traceCSVFilePath, 'utf-8').trim().split(/\r?\n/).map((line) -> line.split(", ")) | |
headers = lines[0].map (str) -> str.toLowerCase().replace(/\s+|#/g, "") | |
for i in [1...lines.length] | |
object = {} | |
for key, j in headers | |
object[key] = lines[i][j] | |
if key is "stack" #divide stack into | |
object["callStack"] = object[key].split("/") | |
objects.push object | |
return objects | |
filterSamples = (samples) -> | |
#samples = samples.splice(0,1000) | |
filteredSamples = [] | |
dllUsageMap = {js: 0} | |
for sample in samples | |
# filter out "n/a" | |
if sample.stack is "n/a" | |
continue | |
filteredCallStack = [] | |
# WPA uses / for both paths and stack separators :() | |
# use state machine to parse out proper script paths | |
seenScriptDll = false | |
scriptCall = "" | |
for call in sample.callStack | |
# leave only js behind | |
match = call.match(/^(\w+\.)(dll|sys|exe)/i) | |
if match | |
dllName = match[0] | |
if not dllUsageMap[dllName] | |
dllUsageMap[dllName] = 1 | |
else | |
dllUsageMap[dllName]++ | |
if dllName is "F12Script2.dll" | |
seenScriptDll = true | |
else | |
if call is "[Root]" | |
filteredCallStack.push(call) | |
if seenScriptDll | |
scriptCall += "/" + call | |
if call.match(/\.js!/) | |
seenScriptDll = false | |
if not seenScriptDll and scriptCall | |
#c.log scriptCall | |
dllUsageMap.js++ | |
filteredCallStack.push(scriptCall) | |
scriptCall = "" | |
sample.callStack = filteredCallStack | |
delete sample.stack | |
filteredSamples.push(sample) | |
# show dll usage map | |
dllTuples = [] | |
for dllName,usageCount of dllUsageMap | |
dllTuples.push([dllName, usageCount]) | |
dllTuples = dllTuples.sort((a, b) -> b[1] - a[1]) | |
c.log "DLL Usage\n", dllTuples | |
return filteredSamples | |
convertToChromeCpuProfile = (samples) -> | |
sampleIdCount = 1 | |
callPathNodeMap = {} | |
timestamps = [] | |
sampleNodeIds = [] | |
parseCall = (node) -> | |
call = node.functionName | |
match = call.match(/^(.*)!(.*)\-(\d+):(\d+)/) | |
if match | |
node.url = match[1] | |
node.functionName = match[2] | |
node.lineNumber = match[3] | |
node.columnNumber = match[4] | |
getProfileNode = (callStack) -> | |
callPath = "" | |
#callStack = callStack.slice(0,10) | |
stackLen = callStack.length | |
for call,i in callStack | |
parentCallPath = callPath | |
callPath += "/" + call | |
if callPath of callPathNodeMap | |
node = callPathNodeMap[callPath] | |
else | |
node = | |
id: sampleIdCount++ | |
hitCount: 0 | |
functionName: call | |
url: "" | |
lineNumber: 0 | |
columnNumber: 0 | |
children:[] | |
parseCall(node) | |
callPathNodeMap[callPath] = node | |
if parentCallPath | |
parentNode = callPathNodeMap[parentCallPath] | |
parentNode.children.push(node) | |
# increase hitCount if at the tip of callStack | |
if (i + 1) == stackLen | |
node.hitCount++ | |
return node | |
for sample in samples | |
timestamps.push(sample.timestamp * 1000000) #seconds | |
node = getProfileNode(sample.callStack) | |
sampleNodeIds.push(node.id) | |
chromeCpuProfile = | |
head: callPathNodeMap["/[Root]"] | |
samples: sampleNodeIds | |
timestamps: timestamps | |
#c.log callPathNodeMap | |
return chromeCpuProfile | |
### | |
# Main | |
### | |
traceCSVFilePath = parseCliForPath() | |
# change outputFilePath extension | |
outputFilePath = path.parse(traceCSVFilePath) | |
outputFilePath.ext = ".cpuprofile" | |
outputFilePath = outputFilePath.dir + "/" + outputFilePath.name + outputFilePath.ext | |
samples = parseCSV(traceCSVFilePath) | |
c.log "Parsed", samples.length, "samples" | |
samples = filterSamples(samples.splice(0,samples.length)) | |
c.log "Filtered samples", samples.length | |
#process.exit() | |
c.log "Generating profile tree" | |
chromeCpuProfile = convertToChromeCpuProfile(samples) | |
c.log "Creating json str" | |
#jsonStr = JSON.stringify(chromeCpuProfile) | |
jsonStr = JSON.stringify(chromeCpuProfile, null, '\t') | |
c.log "Writing to", outputFilePath | |
fs.writeFileSync(outputFilePath, jsonStr) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment