Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add process profiler for debugging #6171

Merged
merged 10 commits into from
Jan 31, 2020
82 changes: 82 additions & 0 deletions packages/server/__snapshots__/process_profiler_spec.ts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
exports['lib/util/process_profiler ._aggregateGroups aggregates groups as expected 1'] = [
{
"group": "electron-shared",
"processCount": 4,
"pids": "99991, 99990, 99992, 99993",
"cpuPercent": 80,
"memRssMb": 40,
"meanCpuPercent": 80,
"meanMemRssMb": 40,
"maxMemRssMb": 40
},
{
"group": "other",
"processCount": 2,
"pids": "66666, 88888",
"cpuPercent": 40,
"memRssMb": 20,
"meanCpuPercent": 40,
"meanMemRssMb": 20,
"maxMemRssMb": 20
},
{
"group": "plugin",
"processCount": 2,
"pids": "22222, 22223",
"cpuPercent": 40,
"memRssMb": 20,
"meanCpuPercent": 40,
"meanMemRssMb": 20,
"maxMemRssMb": 20
},
{
"group": "browser",
"processCount": 2,
"pids": "11111, 11112",
"cpuPercent": 40,
"memRssMb": 20,
"meanCpuPercent": 40,
"meanMemRssMb": 20,
"maxMemRssMb": 20
},
{
"group": "ffmpeg",
"processCount": 1,
"pids": "33333",
"cpuPercent": 20,
"memRssMb": 10,
"meanCpuPercent": 20,
"meanMemRssMb": 10,
"maxMemRssMb": 10
},
{
"group": "desktop-gui",
"processCount": 1,
"pids": "77777",
"cpuPercent": 20,
"memRssMb": 10,
"meanCpuPercent": 20,
"meanMemRssMb": 10,
"maxMemRssMb": 10
},
{
"group": "cypress",
"processCount": 1,
"pids": "111111111",
"cpuPercent": 20,
"memRssMb": 10,
"meanCpuPercent": 20,
"meanMemRssMb": 10,
"maxMemRssMb": 10
},
{
"group": "TOTAL",
"processCount": 13,
"pids": "-",
"cpuPercent": 260,
"memRssMb": 130,
"meanCpuPercent": 260,
"meanMemRssMb": 130,
"maxMemRssMb": 130
}
]
12 changes: 11 additions & 1 deletion packages/server/index.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,30 @@
// override tty if we're being forced to
require('./lib/util/tty').override()

if (process.env.CY_NET_PROFILE && process.env.CYPRESS_ENV) {
const electronApp = require('./lib/util/electron-app')

// are we in the main node process or the electron process?
const isRunningElectron = electronApp.isRunning()

if (process.env.CY_NET_PROFILE && isRunningElectron) {
const netProfiler = require('./lib/util/net_profiler')()

process.stdout.write(`Network profiler writing to ${netProfiler.logPath}\n`)
}

process.env.UV_THREADPOOL_SIZE = 128

require('graceful-fs').gracefulify(require('fs'))
// if running in production mode (CYPRESS_ENV)
// all transpile should have been done already
// and these calls should do nothing
require('@packages/ts/register')
require('@packages/coffee/register')

if (isRunningElectron) {
require('./lib/util/process_profiler').start()
}

require && require.extensions && delete require.extensions['.litcoffee']
require && require.extensions && delete require.extensions['.coffee.md']

Expand Down
11 changes: 10 additions & 1 deletion packages/server/lib/browsers/electron.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ ELECTRON_DEBUG_EVENTS = [
'unresponsive'
]

instance = null

tryToCall = (win, method) ->
try
if not win.isDestroyed()
Expand Down Expand Up @@ -72,6 +74,10 @@ module.exports = {
_win.on "close", ->
if not child.isDestroyed()
child.destroy()

## add this pid to list of pids
tryToCall child, ->
instance?.pid?.push(child.webContents.getOSProcessId())
}

_.defaultsDeep({}, options, defaults)
Expand Down Expand Up @@ -253,9 +259,12 @@ module.exports = {

events.emit("exit")

return _.extend events, {
instance = _.extend events, {
pid: [tryToCall(win, -> win.webContents.getOSProcessId())]
browserWindow: win
kill: -> tryToCall(win, "destroy")
removeAllListeners: -> tryToCall(win, "removeAllListeners")
}

return instance
}
13 changes: 13 additions & 0 deletions packages/server/lib/browsers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,17 @@ module.exports = {

close: kill,

_setInstance (_instance) {
// for testing
instance = _instance
},

// note: does not guarantee that `browser` is still running
// note: electron will return a list of pids for each webContent
getBrowserInstance () {
return instance
},

getAllBrowsersWith (nameOrPath) {
debug('getAllBrowsersWith %o', { nameOrPath })
if (nameOrPath) {
Expand Down Expand Up @@ -160,6 +171,8 @@ module.exports = {
// TODO: bind to process.exit here
// or move this functionality into cypress-core-launder

i.browser = browser

instance = i

// TODO: normalizing opening and closing / exiting
Expand Down
4 changes: 2 additions & 2 deletions packages/server/lib/cypress.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ const exitErr = (err) => {

module.exports = {
isCurrentlyRunningElectron () {
return !!(process.versions && process.versions.electron)
return require('./util/electron-app').isRunning()
},

runElectron (mode, options) {
Expand Down Expand Up @@ -170,7 +170,7 @@ module.exports = {
// to force retina screens to not
// upsample their images when offscreen
// rendering
require('./util/electron_app').scale()
require('./util/electron-app').scale()
}

// make sure we have the appData folder
Expand Down
4 changes: 2 additions & 2 deletions packages/server/lib/modes/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const newlines = require('../util/newlines')
const terminal = require('../util/terminal')
const specsUtil = require('../util/specs')
const humanTime = require('../util/human_time')
const electronApp = require('../util/electron_app')
const electronApp = require('../util/electron-app')
const settings = require('../util/settings')
const chromePolicyCheck = require('../util/chrome_policy_check')

Expand Down Expand Up @@ -1395,7 +1395,7 @@ module.exports = {

run (options) {
return electronApp
.ready()
.waitForReady()
.then(() => {
return this.ready(options)
})
Expand Down
8 changes: 8 additions & 0 deletions packages/server/lib/plugins/index.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ register = (event, callback) ->
registeredEvents[event] = callback

module.exports = {
## for testing
_setPluginsProcess: (_pluginsProcess) ->
pluginsProcess = _pluginsProcess

getPluginPid: () ->
if pluginsProcess
return pluginsProcess.pid

registerHandler: (handler) ->
handlers.push(handler)

Expand Down
14 changes: 0 additions & 14 deletions packages/server/lib/project.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ class Project extends EE {
this.spec = null
this.browser = null
this.server = null
this.memoryCheck = null
this.automation = null
this.getConfig = this.getConfig.bind(this)

Expand All @@ -78,15 +77,6 @@ class Project extends EE {
debug('project options %o', options)
this.options = options

if (process.env.CYPRESS_MEMORY) {
const logMemory = () => {
// eslint-disable-next-line no-console
return console.log('memory info', process.memoryUsage())
}

this.memoryCheck = setInterval(logMemory, 1000)
}

this.onWarning = options.onWarning

return this.getConfig(options)
Expand Down Expand Up @@ -205,10 +195,6 @@ class Project extends EE {
close () {
debug('closing project instance %s', this.projectRoot)

if (this.memoryCheck) {
clearInterval(this.memoryCheck)
}

this.cfg = null
this.spec = null
this.browser = null
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
const debug = require('debug')('cypress:server:electron_app')

const scale = () => {
try {
const { app } = require('electron')
Expand All @@ -10,7 +8,9 @@ const scale = () => {
}
}

const ready = () => {
const waitForReady = () => {
const debug = require('debug')('cypress:server:electron-app')

const Promise = require('bluebird')
const { app } = require('electron')

Expand All @@ -21,20 +21,27 @@ const ready = () => {
debug('all BrowserWindows closed, not exiting')
})

const waitForReady = () => {
const onReadyEvent = () => {
return new Promise((resolve) => {
app.on('ready', resolve)
})
}

return Promise.any([
waitForReady(),
onReadyEvent(),
Promise.delay(500),
])
}

const isRunning = () => {
// are we in the electron or the node process?
return Boolean(process.versions && process.versions.electron)
}

module.exports = {
scale,

ready,
waitForReady,

isRunning,
}
Loading