Merge pull request #12984 from atom/mb-benchmark-mode

Introducing Atom benchmarks
This commit is contained in:
Antonio Scandurra 2016-10-14 20:25:52 +02:00 committed by GitHub
commit c6db4df6c0
18 changed files with 326 additions and 30 deletions

View File

@ -28,7 +28,7 @@ while getopts ":wtfvh-:" opt; do
REDIRECT_STDERR=1
EXPECT_OUTPUT=1
;;
foreground|test)
foreground|benchmark|benchmark-test|test)
EXPECT_OUTPUT=1
;;
esac

View File

@ -0,0 +1,73 @@
/** @babel */
import Chart from 'chart.js'
import glob from 'glob'
import fs from 'fs-plus'
import path from 'path'
export default async function ({test, benchmarkPaths}) {
document.body.style.backgroundColor = '#ffffff'
document.body.style.overflow = 'auto'
let paths = []
for (const benchmarkPath of benchmarkPaths) {
if (fs.isDirectorySync(benchmarkPath)) {
paths = paths.concat(glob.sync(path.join(benchmarkPath, '**', '*.bench.js')))
} else {
paths.push(benchmarkPath)
}
}
while (paths.length > 0) {
const benchmark = require(paths.shift())({test})
let results
if (benchmark instanceof Promise) {
results = await benchmark
} else {
results = benchmark
}
const dataByBenchmarkName = {}
for (const {name, duration, x} of results) {
dataByBenchmarkName[name] = dataByBenchmarkName[name] || {points: []}
dataByBenchmarkName[name].points.push({x, y: duration})
}
const benchmarkContainer = document.createElement('div')
document.body.appendChild(benchmarkContainer)
for (const key in dataByBenchmarkName) {
const data = dataByBenchmarkName[key]
if (data.points.length > 1) {
const canvas = document.createElement('canvas')
benchmarkContainer.appendChild(canvas)
const chart = new Chart(canvas, {
type: 'line',
data: {
datasets: [{label: key, fill: false, data: data.points}]
},
options: {
showLines: false,
scales: {xAxes: [{type: 'linear', position: 'bottom'}]}
}
})
const textualOutput = `${key}:\n\n` + data.points.map((p) => `${p.x}\t${p.y}`).join('\n')
console.log(textualOutput)
} else {
const title = document.createElement('h2')
title.textContent = key
benchmarkContainer.appendChild(title)
const duration = document.createElement('p')
duration.textContent = `${data.points[0].y}ms`
benchmarkContainer.appendChild(duration)
const textualOutput = `${key}: ${data.points[0].y}`
console.log(textualOutput)
}
global.atom.reset()
}
}
return 0
}

View File

@ -0,0 +1,19 @@
/** @babel */
import fs from 'fs'
import temp from 'temp'
import {TextEditor, TextBuffer} from 'atom'
export default function ({test}) {
const text = 'Lorem ipsum dolor sit amet\n'.repeat(test ? 10 : 500000)
const t0 = window.performance.now()
const buffer = new TextBuffer(text)
const editor = new TextEditor({buffer, largeFileMode: true})
editor.element.style.height = "600px"
document.body.appendChild(editor.element)
const t1 = window.performance.now()
editor.element.remove()
editor.destroy()
return [{name: 'Opening and rendering a large file', duration: t1 - t0}]
}

View File

@ -147,6 +147,7 @@
{ label: 'Open In Dev Mode…', command: 'application:open-dev' }
{ label: 'Reload Window', command: 'window:reload' }
{ label: 'Run Package Specs', command: 'window:run-package-specs' }
{ label: 'Run Benchmarks', command: 'window:run-benchmarks' }
{ label: 'Toggle Developer Tools', command: 'window:toggle-dev-tools' }
]
}

View File

@ -20,6 +20,7 @@
"babel-core": "5.8.38",
"cached-run-in-this-context": "0.4.1",
"chai": "3.5.0",
"chart.js": "^2.3.0",
"clear-cut": "^2.0.1",
"coffee-script": "1.11.1",
"color": "^0.7.3",
@ -32,6 +33,7 @@
"fstream": "0.1.24",
"fuzzaldrin": "^2.1",
"git-utils": "^4.1.2",
"glob": "^7.1.1",
"grim": "1.5.0",
"jasmine-json": "~0.0",
"jasmine-tagged": "^1.1.4",
@ -161,8 +163,6 @@
"test": "node script/test"
},
"standard": {
"ignore": [],
"parser": "babel-eslint",
"globals": [
"atom",
"afterEach",

View File

@ -5,14 +5,16 @@ SET WAIT=
SET PSARGS=%*
FOR %%a IN (%*) DO (
IF /I "%%a"=="-f" SET EXPECT_OUTPUT=YES
IF /I "%%a"=="--foreground" SET EXPECT_OUTPUT=YES
IF /I "%%a"=="-h" SET EXPECT_OUTPUT=YES
IF /I "%%a"=="--help" SET EXPECT_OUTPUT=YES
IF /I "%%a"=="-t" SET EXPECT_OUTPUT=YES
IF /I "%%a"=="--test" SET EXPECT_OUTPUT=YES
IF /I "%%a"=="-v" SET EXPECT_OUTPUT=YES
IF /I "%%a"=="--version" SET EXPECT_OUTPUT=YES
IF /I "%%a"=="-f" SET EXPECT_OUTPUT=YES
IF /I "%%a"=="--foreground" SET EXPECT_OUTPUT=YES
IF /I "%%a"=="-h" SET EXPECT_OUTPUT=YES
IF /I "%%a"=="--help" SET EXPECT_OUTPUT=YES
IF /I "%%a"=="-t" SET EXPECT_OUTPUT=YES
IF /I "%%a"=="--test" SET EXPECT_OUTPUT=YES
IF /I "%%a"=="--benchmark" SET EXPECT_OUTPUT=YES
IF /I "%%a"=="--benchmark-test" SET EXPECT_OUTPUT=YES
IF /I "%%a"=="-v" SET EXPECT_OUTPUT=YES
IF /I "%%a"=="--version" SET EXPECT_OUTPUT=YES
IF /I "%%a"=="-w" (
SET EXPECT_OUTPUT=YES
SET WAIT=YES

View File

@ -12,6 +12,7 @@ const includePathInPackagedApp = require('./include-path-in-packaged-app')
module.exports = function () {
console.log(`Copying assets to ${CONFIG.intermediateAppPath}`);
let srcPaths = [
path.join(CONFIG.repositoryRootPath, 'benchmarks', 'benchmark-runner.js'),
path.join(CONFIG.repositoryRootPath, 'dot-atom'),
path.join(CONFIG.repositoryRootPath, 'exports'),
path.join(CONFIG.repositoryRootPath, 'node_modules'),

View File

@ -25,6 +25,7 @@ module.exports = function () {
function getPathsToTranspile () {
let paths = []
paths = paths.concat(glob.sync(path.join(CONFIG.intermediateAppPath, 'benchmarks', '**', '*.js')))
paths = paths.concat(glob.sync(path.join(CONFIG.intermediateAppPath, 'exports', '**', '*.js')))
paths = paths.concat(glob.sync(path.join(CONFIG.intermediateAppPath, 'src', '**', '*.js')))
for (let packageName of Object.keys(CONFIG.appMetadata.packageDependencies)) {

View File

@ -4,7 +4,6 @@
"dependencies": {
"async": "2.0.1",
"babel-core": "5.8.38",
"babel-eslint": "6.1.2",
"coffeelint": "1.15.7",
"colors": "1.1.2",
"csslint": "1.0.2",
@ -24,7 +23,7 @@
"runas": "3.1.1",
"season": "5.3.0",
"semver": "5.3.0",
"standard": "6.0.0",
"standard": "8.4.0",
"sync-request": "3.0.1",
"tello": "1.0.5",
"webdriverio": "2.4.5",

View File

@ -83,9 +83,19 @@ for (let packageName in CONFIG.appMetadata.packageDependencies) {
})
}
function runBenchmarkTests (callback) {
const benchmarksPath = path.join(CONFIG.repositoryRootPath, 'benchmarks')
const testArguments = ['--benchmark-test', benchmarksPath]
console.log('Executing benchmark tests'.bold.green)
const cp = childProcess.spawn(executablePath, testArguments, {stdio: 'inherit'})
cp.on('error', error => { callback(error) })
cp.on('close', exitCode => { callback(null, exitCode) })
}
let testSuitesToRun
if (process.platform === 'darwin') {
testSuitesToRun = [runCoreMainProcessTests, runCoreRenderProcessTests].concat(packageTestSuites)
testSuitesToRun = [runCoreMainProcessTests, runCoreRenderProcessTests, runBenchmarkTests].concat(packageTestSuites)
} else {
testSuitesToRun = [runCoreMainProcessTests]
}

View File

@ -0,0 +1,111 @@
/** @babel */
import {remote} from 'electron'
import path from 'path'
import ipcHelpers from './ipc-helpers'
import util from 'util'
export default async function () {
const {getWindowLoadSettings} = require('./window-load-settings-helpers')
const {test, headless, resourcePath, benchmarkPaths} = getWindowLoadSettings()
try {
const Clipboard = require('../src/clipboard')
const ApplicationDelegate = require('../src/application-delegate')
const AtomEnvironment = require('../src/atom-environment')
const TextEditor = require('../src/text-editor')
const exportsPath = path.join(resourcePath, 'exports')
require('module').globalPaths.push(exportsPath) // Add 'exports' to module search path.
process.env.NODE_PATH = exportsPath // Set NODE_PATH env variable since tasks may need it.
document.title = 'Benchmarks'
// Allow `document.title` to be assigned in benchmarks without actually changing the window title.
let documentTitle = null
Object.defineProperty(document, 'title', {
get () { return documentTitle },
set (title) { documentTitle = title }
})
window.addEventListener('keydown', (event) => {
// Reload: cmd-r / ctrl-r
if ((event.metaKey || event.ctrlKey) && event.keyCode === 82) {
ipcHelpers.call('window-method', 'reload')
}
// Toggle Dev Tools: cmd-alt-i (Mac) / ctrl-shift-i (Linux/Windows)
if (event.keyCode === 73) {
const isDarwin = process.platform === 'darwin'
if ((isDarwin && event.metaKey && event.altKey) || (!isDarwin && event.ctrlKey && event.shiftKey)) {
ipcHelpers.call('window-method', 'toggleDevTools')
}
}
// Close: cmd-w / ctrl-w
if ((event.metaKey || event.ctrlKey) && event.keyCode === 87) {
ipcHelpers.call('window-method', 'close')
}
// Copy: cmd-c / ctrl-c
if ((event.metaKey || event.ctrlKey) && event.keyCode === 67) {
ipcHelpers.call('window-method', 'copy')
}
}, true)
const clipboard = new Clipboard()
TextEditor.setClipboard(clipboard)
const applicationDelegate = new ApplicationDelegate()
global.atom = new AtomEnvironment({
applicationDelegate,
window,
document,
clipboard,
configDirPath: process.env.ATOM_HOME,
enablePersistence: false
})
// Prevent benchmarks from modifying application menus
global.atom.menu.sendToBrowserProcess = function () { }
if (headless) {
Object.defineProperties(process, {
stdout: { value: remote.process.stdout },
stderr: { value: remote.process.stderr }
})
console.log = function (...args) {
const formatted = util.format(...args)
process.stdout.write(formatted + '\n')
}
console.warn = function (...args) {
const formatted = util.format(...args)
process.stderr.write(formatted + '\n')
}
console.error = function (...args) {
const formatted = util.format(...args)
process.stderr.write(formatted + '\n')
}
} else {
remote.getCurrentWindow().show()
}
const benchmarkRunner = require('../benchmarks/benchmark-runner')
const statusCode = await benchmarkRunner({test, benchmarkPaths})
if (headless) {
exitWithStatusCode(statusCode)
}
} catch (error) {
if (headless) {
console.error(error.stack || error)
exitWithStatusCode(1)
} else {
ipcHelpers.call('window-method', 'openDevTools')
throw error
}
}
}
function exitWithStatusCode (statusCode) {
remote.app.emit('will-quit')
remote.process.exit(statusCode)
}

View File

@ -42,7 +42,7 @@ class AtomApplication
# take a few seconds to trigger 'error' event, it could be a bug of node
# or atom-shell, before it's fixed we check the existence of socketPath to
# speedup startup.
if (process.platform isnt 'win32' and not fs.existsSync options.socketPath) or options.test
if (process.platform isnt 'win32' and not fs.existsSync options.socketPath) or options.test or options.benchmark or options.benchmarkTest
new AtomApplication(options).initialize(options)
return
@ -64,7 +64,7 @@ class AtomApplication
constructor: (options) ->
{@resourcePath, @devResourcePath, @version, @devMode, @safeMode, @socketPath, @logFile, @setPortable, @userDataDir} = options
@socketPath = null if options.test
@socketPath = null if options.test or options.benchmark or options.benchmarkTest
@pidsToOpenWindows = {}
@windows = []
@ -86,7 +86,9 @@ class AtomApplication
@config.onDidChange 'core.useCustomTitleBar', @promptForRestart.bind(this)
@autoUpdateManager = new AutoUpdateManager(@version, options.test, @resourcePath, @config)
@autoUpdateManager = new AutoUpdateManager(
@version, options.test or options.benchmark or options.benchmarkTest, @resourcePath, @config
)
@applicationMenu = new ApplicationMenu(@version, @autoUpdateManager)
@atomProtocolHandler = new AtomProtocolHandler(@resourcePath, @safeMode)
@ -103,23 +105,41 @@ class AtomApplication
Promise.all(windowsClosePromises).then(=> @disposable.dispose())
launch: (options) ->
if options.pathsToOpen?.length > 0 or options.urlsToOpen?.length > 0 or options.test
if options.pathsToOpen?.length > 0 or options.urlsToOpen?.length > 0 or options.test or options.benchmark or options.benchmarkTest
@openWithOptions(options)
else
@loadState(options) or @openPath(options)
openWithOptions: ({initialPaths, pathsToOpen, executedFrom, urlsToOpen, test, pidToKillWhenClosed, devMode, safeMode, newWindow, logFile, profileStartup, timeout, clearWindowState, addToLastWindow, env}) ->
openWithOptions: (options) ->
{
initialPaths, pathsToOpen, executedFrom, urlsToOpen, benchmark,
benchmarkTest, test, pidToKillWhenClosed, devMode, safeMode, newWindow,
logFile, profileStartup, timeout, clearWindowState, addToLastWindow, env
} = options
app.focus()
if test
@runTests({headless: true, devMode, @resourcePath, executedFrom, pathsToOpen, logFile, timeout, env})
@runTests({
headless: true, devMode, @resourcePath, executedFrom, pathsToOpen,
logFile, timeout, env
})
else if benchmark or benchmarkTest
@runBenchmarks({headless: true, test: benchmarkTest, @resourcePath, executedFrom, pathsToOpen, timeout, env})
else if pathsToOpen.length > 0
@openPaths({initialPaths, pathsToOpen, executedFrom, pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, clearWindowState, addToLastWindow, env})
@openPaths({
initialPaths, pathsToOpen, executedFrom, pidToKillWhenClosed, newWindow,
devMode, safeMode, profileStartup, clearWindowState, addToLastWindow, env
})
else if urlsToOpen.length > 0
@openUrl({urlToOpen, devMode, safeMode, env}) for urlToOpen in urlsToOpen
for urlToOpen in urlsToOpen
@openUrl({urlToOpen, devMode, safeMode, env})
else
# Always open a editor window if this is the first instance of Atom.
@openPath({initialPaths, pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, clearWindowState, addToLastWindow, env})
@openPath({
initialPaths, pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup,
clearWindowState, addToLastWindow, env
})
# Public: Removes the {AtomWindow} from the global window list.
removeWindow: (window) ->
@ -280,6 +300,9 @@ class AtomApplication
@disposable.add ipcHelpers.on ipcMain, 'run-package-specs', (event, packageSpecPath) =>
@runTests({resourcePath: @devResourcePath, pathsToOpen: [packageSpecPath], headless: false})
@disposable.add ipcHelpers.on ipcMain, 'run-benchmarks', (event, benchmarksPath) =>
@runBenchmarks({resourcePath: @devResourcePath, pathsToOpen: [benchmarksPath], headless: false, test: false})
@disposable.add ipcHelpers.on ipcMain, 'command', (event, command) =>
@emit(command)
@ -651,6 +674,29 @@ class AtomApplication
safeMode ?= false
new AtomWindow(this, @fileRecoveryService, {windowInitializationScript, resourcePath, headless, isSpec, devMode, testRunnerPath, legacyTestRunnerPath, testPaths, logFile, safeMode, env})
runBenchmarks: ({headless, test, resourcePath, executedFrom, pathsToOpen, env}) ->
if resourcePath isnt @resourcePath and not fs.existsSync(resourcePath)
resourcePath = @resourcePath
try
windowInitializationScript = require.resolve(path.resolve(@devResourcePath, 'src', 'initialize-benchmark-window'))
catch error
windowInitializationScript = require.resolve(path.resolve(__dirname, '..', '..', 'src', 'initialize-benchmark-window'))
benchmarkPaths = []
if pathsToOpen?
for pathToOpen in pathsToOpen
benchmarkPaths.push(path.resolve(executedFrom, fs.normalize(pathToOpen)))
if benchmarkPaths.length is 0
process.stderr.write 'Error: Specify at least one benchmark path.\n\n'
process.exit(1)
devMode = true
isSpec = true
safeMode = false
new AtomWindow(this, @fileRecoveryService, {windowInitializationScript, resourcePath, headless, test, isSpec, devMode, benchmarkPaths, safeMode, env})
resolveTestRunnerPath: (testPath) ->
FindParentDir ?= require 'find-parent-dir'

View File

@ -19,7 +19,7 @@ if (args.resourcePath) {
const stableResourcePath = path.dirname(path.dirname(__dirname))
const defaultRepositoryPath = path.join(electron.app.getPath('home'), 'github', 'atom')
if (args.dev || args.test) {
if (args.dev || args.test || args.benchmark || args.benchmarkTest) {
if (process.env.ATOM_DEV_RESOURCE_PATH) {
resourcePath = process.env.ATOM_DEV_RESOURCE_PATH
} else if (fs.statSyncNoException(defaultRepositoryPath)) {

View File

@ -45,6 +45,8 @@ module.exports = function parseCommandLine (processArgs) {
'portable',
'Set portable mode. Copies the ~/.atom folder to be a sibling of the installed Atom location if a .atom folder is not already there.'
)
options.boolean('benchmark').describe('benchmark', 'Open a new window that runs the specified benchmarks.')
options.boolean('benchmark-test').describe('benchmark--test', 'Run a faster version of the benchmarks in headless mode.')
options.alias('t', 'test').boolean('t').describe('t', 'Run the specified specs and exit with error code on failures.')
options.alias('m', 'main-process').boolean('m').describe('m', 'Run the specified specs in the main process.')
options.string('timeout').describe(
@ -78,6 +80,8 @@ module.exports = function parseCommandLine (processArgs) {
const addToLastWindow = args['add']
const safeMode = args['safe']
const pathsToOpen = args._
const benchmark = args['benchmark']
const benchmarkTest = args['benchmark-test']
const test = args['test']
const mainProcess = args['main-process']
const timeout = args['timeout']
@ -132,10 +136,29 @@ module.exports = function parseCommandLine (processArgs) {
devResourcePath = normalizeDriveLetterName(devResourcePath)
return {
resourcePath, devResourcePath, pathsToOpen, urlsToOpen, executedFrom, test,
version, pidToKillWhenClosed, devMode, safeMode, newWindow, logFile, socketPath,
userDataDir, profileStartup, timeout, setPortable, clearWindowState,
addToLastWindow, mainProcess, env: process.env
resourcePath,
devResourcePath,
pathsToOpen,
urlsToOpen,
executedFrom,
test,
version,
pidToKillWhenClosed,
devMode,
safeMode,
newWindow,
logFile,
socketPath,
userDataDir,
profileStartup,
timeout,
setPortable,
clearWindowState,
addToLastWindow,
mainProcess,
benchmark,
benchmarkTest,
env: process.env
}
}

View File

@ -57,7 +57,7 @@ module.exports = function start (resourcePath, startTime) {
if (args.userDataDir != null) {
app.setPath('userData', args.userDataDir)
} else if (args.test) {
} else if (args.test || args.benchmark || args.benchmarkTest) {
app.setPath('userData', temp.mkdirSync('atom-test-data'))
}

View File

@ -45,7 +45,7 @@ class NativeCompileCache {
Module.prototype._compile = function (content, filename) {
let moduleSelf = this
// remove shebang
content = content.replace(/^\#\!.*/, '')
content = content.replace(/^#!.*/, '')
function require (path) {
return moduleSelf.require(path)
}

View File

@ -54,6 +54,7 @@ module.exports = ({commandRegistry, commandInstaller, config, notificationManage
'application:open-your-stylesheet': -> ipcRenderer.send('command', 'application:open-your-stylesheet')
'application:open-license': -> @getModel().openLicense()
'window:run-package-specs': -> @runPackageSpecs()
'window:run-benchmarks': -> @runBenchmarks()
'window:focus-next-pane': -> @getModel().activateNextPane()
'window:focus-previous-pane': -> @getModel().activatePreviousPane()
'window:focus-pane-above': -> @focusPaneViewAbove()

View File

@ -142,4 +142,13 @@ class WorkspaceElement extends HTMLElement
ipcRenderer.send('run-package-specs', specPath)
runBenchmarks: ->
if activePath = @workspace.getActivePaneItem()?.getPath?()
[projectPath] = @project.relativizePath(activePath)
else
[projectPath] = @project.getPaths()
if projectPath
ipcRenderer.send('run-benchmarks', path.join(projectPath, 'benchmarks'))
module.exports = WorkspaceElement = document.registerElement 'atom-workspace', prototype: WorkspaceElement.prototype