Electron-136 (Optimize memory by reloading the app when the user is idle) (#335)

- Refresh app when memory exceeds the threshold and if user is inactive
- Add logic to reload app when memory exceeds threshold and if user is inactive
- Add reload threshold
- Fix unit test
This commit is contained in:
Kiran Niranjan 2018-04-11 13:06:46 +00:00 committed by Vishwas Shashidhar
parent 2e25c97bec
commit 2de86c8003
9 changed files with 111 additions and 4 deletions

View File

@ -6,6 +6,7 @@
"bringToFront": false, "bringToFront": false,
"whitelistUrl": "*", "whitelistUrl": "*",
"isCustomTitleBar": true, "isCustomTitleBar": true,
"memoryRefresh": true,
"notificationSettings": { "notificationSettings": {
"position": "upper-right", "position": "upper-right",
"display": "" "display": ""

View File

@ -5,6 +5,13 @@ const throttle = require('../utils/throttle');
const log = require('../log.js'); const log = require('../log.js');
const logLevels = require('../enums/logLevels.js'); const logLevels = require('../enums/logLevels.js');
let setIsAutoReload;
if (!process.env.ELECTRON_QA) {
/* eslint-disable global-require */
setIsAutoReload = require('../windowMgr').setIsAutoReload;
/* eslint-enable global-require */
}
let maxIdleTime; let maxIdleTime;
let activityWindow; let activityWindow;
let intervalId; let intervalId;
@ -51,6 +58,9 @@ function monitorUserActivity() {
if (systemIdleTime.getIdleTime() < maxIdleTime) { if (systemIdleTime.getIdleTime() < maxIdleTime) {
// If system is active, send an update to the app bridge and clear the timer // If system is active, send an update to the app bridge and clear the timer
sendActivity(); sendActivity();
if (typeof setIsAutoReload === 'function') {
setIsAutoReload(false);
}
clearInterval(intervalId); clearInterval(intervalId);
intervalId = undefined; intervalId = undefined;
} }

View File

@ -23,7 +23,7 @@ const dirs = new AppDirectory('Symphony');
let userConfig; let userConfig;
let globalConfig; let globalConfig;
let ignoreSettings = ['minimizeOnClose', 'launchOnStartup', 'alwaysOnTop', 'url']; let ignoreSettings = ['minimizeOnClose', 'launchOnStartup', 'alwaysOnTop', 'url', 'memoryRefresh'];
/** /**
* Tries to read given field from user config file, if field doesn't exist * Tries to read given field from user config file, if field doesn't exist

View File

@ -19,7 +19,8 @@ const cmds = keyMirror({
sanitize: null, sanitize: null,
bringToFront: null, bringToFront: null,
openScreenPickerWindow: null, openScreenPickerWindow: null,
popupMenu: null popupMenu: null,
optimizeMemoryConsumption: null
}); });
module.exports = { module.exports = {

View File

@ -17,6 +17,7 @@ const { bringToFront } = require('./bringToFront.js');
const eventEmitter = require('./eventEmitter'); const eventEmitter = require('./eventEmitter');
const { isMac } = require('./utils/misc'); const { isMac } = require('./utils/misc');
const { openScreenPickerWindow } = require('./desktopCapturer'); const { openScreenPickerWindow } = require('./desktopCapturer');
const { optimizeMemory } = require('./memoryMonitor');
const apiEnums = require('./enums/api.js'); const apiEnums = require('./enums/api.js');
const apiCmds = apiEnums.cmds; const apiCmds = apiEnums.cmds;
@ -147,6 +148,11 @@ electron.ipcMain.on(apiName, (event, arg) => {
windowMgr.getMenu().popup(browserWin, { x: 20, y: 15, async: true }); windowMgr.getMenu().popup(browserWin, { x: 20, y: 15, async: true });
} }
break; break;
case apiCmds.optimizeMemoryConsumption:
if (typeof arg.memory === 'object' && typeof arg.memory.workingSetSize === 'number') {
optimizeMemory(arg.memory);
}
break;
default: default:
} }

View File

@ -2,6 +2,14 @@
const log = require('./log.js'); const log = require('./log.js');
const logLevels = require('./enums/logLevels.js'); const logLevels = require('./enums/logLevels.js');
const { getMainWindow, setIsAutoReload } = require('./windowMgr');
const systemIdleTime = require('@paulcbetts/system-idle-time');
const { getConfigField } = require('./config');
const maxMemory = 800;
let maxIdleTime = 4 * 60 * 1000;
let reloadThreshold = 30 * 60 * 1000;
let reloadedTimeStamp;
// once a minute // once a minute
setInterval(gatherMemory, 1000 * 60); setInterval(gatherMemory, 1000 * 60);
@ -18,3 +26,35 @@ function gatherMemory() {
' sharedBytes: ' + memory.sharedBytes; ' sharedBytes: ' + memory.sharedBytes;
log.send(logLevels.INFO, details); log.send(logLevels.INFO, details);
} }
/**
* Method that checks memory usage every minute
* and verify if the user in inactive if so it reloads the
* application to free up some memory consumption
*
* @param memoryInfo
*/
function optimizeMemory(memoryInfo) {
const memoryConsumed = (memoryInfo && memoryInfo.workingSetSize / 1024) || 0;
const canReload = (!reloadedTimeStamp || (new Date().getTime() - reloadedTimeStamp) > reloadThreshold);
if (memoryConsumed > maxMemory && systemIdleTime.getIdleTime() > maxIdleTime && canReload) {
getConfigField('memoryRefresh')
.then((enabled) => {
if (enabled) {
const mainWindow = getMainWindow();
if (mainWindow && !mainWindow.isDestroyed()) {
setIsAutoReload(true);
reloadedTimeStamp = new Date().getTime();
log.send(logLevels.INFO, 'Reloading the app to optimize memory usage');
mainWindow.webContents.reload();
}
}
});
}
}
module.exports = {
optimizeMemory
};

View File

@ -16,13 +16,15 @@ const configFields = [
'launchOnStartup', 'launchOnStartup',
'alwaysOnTop', 'alwaysOnTop',
'notificationSettings', 'notificationSettings',
'bringToFront' 'bringToFront',
'memoryRefresh'
]; ];
let minimizeOnClose = false; let minimizeOnClose = false;
let launchOnStartup = false; let launchOnStartup = false;
let isAlwaysOnTop = false; let isAlwaysOnTop = false;
let bringToFront = false; let bringToFront = false;
let memoryRefresh = false;
let symphonyAutoLauncher; let symphonyAutoLauncher;
@ -305,6 +307,22 @@ function getTemplate(app) {
} }
}); });
// Window/View menu -> separator
template[index].submenu.push({
type: 'separator',
});
// Window - View menu -> memoryRefresh
template[index].submenu.push({
label: 'Memory Refresh',
type: 'checkbox',
checked: memoryRefresh,
click: function(item) {
memoryRefresh = item.checked;
updateConfigField('memoryRefresh', memoryRefresh);
}
});
if (!isMac) { if (!isMac) {
template[index].submenu.push({ template[index].submenu.push({
label: 'Quit Symphony', label: 'Quit Symphony',
@ -356,6 +374,9 @@ function setCheckboxValues() {
case 'bringToFront': case 'bringToFront':
bringToFront = configData[key]; bringToFront = configData[key];
break; break;
case 'memoryRefresh':
memoryRefresh = configData[key];
break;
default: default:
break; break;
} }

View File

@ -64,6 +64,15 @@ const throttledSetBadgeCount = throttle(1000, function(count) {
}); });
}); });
// Gathers renderer process memory
setInterval(() => {
const memory = process.getProcessMemoryInfo();
local.ipcRenderer.send(apiName, {
cmd: apiCmds.optimizeMemoryConsumption,
memory: memory
});
}, 1000 * 60 * 4);
createAPI(); createAPI();
// creates API exposed from electron. // creates API exposed from electron.

View File

@ -40,6 +40,7 @@ let alwaysOnTop = false;
let position = 'lower-right'; let position = 'lower-right';
let display; let display;
let sandboxed = false; let sandboxed = false;
let isAutoReload = false;
// By default, we set the user's default download directory // By default, we set the user's default download directory
let defaultDownloadsDirectory = app.getPath("downloads"); let defaultDownloadsDirectory = app.getPath("downloads");
@ -710,6 +711,13 @@ function setIsOnline(status) {
* without giving focus * without giving focus
*/ */
function activate(windowName, shouldFocus = true) { function activate(windowName, shouldFocus = true) {
// don't activate when the app is reloaded programmatically
// Electron-136
if (isAutoReload) {
return null;
}
let keys = Object.keys(windows); let keys = Object.keys(windows);
for (let i = 0, len = keys.length; i < len; i++) { for (let i = 0, len = keys.length; i < len; i++) {
let window = windows[keys[i]]; let window = windows[keys[i]];
@ -735,6 +743,16 @@ function activate(windowName, shouldFocus = true) {
return null; return null;
} }
/**
* Sets if is auto reloading the app
* @param reload
*/
function setIsAutoReload(reload) {
if (typeof reload === 'boolean') {
isAutoReload = reload
}
}
/** /**
* name of renderer window to notify when bounds of child window changes. * name of renderer window to notify when bounds of child window changes.
* @param {object} window Renderer window to use IPC with to inform about size/ * @param {object} window Renderer window to use IPC with to inform about size/
@ -972,5 +990,6 @@ module.exports = {
activate: activate, activate: activate,
setBoundsChangeWindow: setBoundsChangeWindow, setBoundsChangeWindow: setBoundsChangeWindow,
verifyDisplays: verifyDisplays, verifyDisplays: verifyDisplays,
getMenu: getMenu getMenu: getMenu,
setIsAutoReload: setIsAutoReload
}; };