diff --git a/js/enums/api.js b/js/enums/api.js index 6b53e14b..38d0edb7 100644 --- a/js/enums/api.js +++ b/js/enums/api.js @@ -21,6 +21,7 @@ const cmds = keyMirror({ openScreenPickerWindow: null, popupMenu: null, optimizeMemoryConsumption: null, + optimizeMemoryRegister: null, setIsInMeeting: null, setLocale: null, keyPress: null, diff --git a/js/main.js b/js/main.js index 9853c188..3e897506 100644 --- a/js/main.js +++ b/js/main.js @@ -10,6 +10,7 @@ const squirrelStartup = require('electron-squirrel-startup'); const urlParser = require('url'); const nodePath = require('path'); const compareSemVersions = require('./utils/compareSemVersions.js'); +const eventEmitter = require('./eventEmitter'); // Local Dependencies const { @@ -185,6 +186,12 @@ setChromeFlags(); * Some APIs can only be used after this event occurs. */ app.on('ready', () => { + electron.powerMonitor.on('lock-screen', () => { + eventEmitter.emit('sys-locked'); + }); + electron.powerMonitor.on('unlock-screen', () => { + eventEmitter.emit('sys-unlocked'); + }); checkFirstTimeLaunch() .then(readConfigThenOpenMainWindow) .catch(readConfigThenOpenMainWindow); diff --git a/js/mainApiMgr.js b/js/mainApiMgr.js index 8e9e8e9d..2feec915 100644 --- a/js/mainApiMgr.js +++ b/js/mainApiMgr.js @@ -17,7 +17,7 @@ const { bringToFront } = require('./bringToFront.js'); const eventEmitter = require('./eventEmitter'); const { isMac } = require('./utils/misc'); const { openScreenPickerWindow } = require('./desktopCapturer'); -const { optimizeMemory, setIsInMeeting } = require('./memoryMonitor'); +const { setPreloadMemoryInfo, setIsInMeeting, setPreloadWindow } = require('./memoryMonitor'); const apiEnums = require('./enums/api.js'); const apiCmds = apiEnums.cmds; @@ -153,9 +153,12 @@ electron.ipcMain.on(apiName, (event, arg) => { } case apiCmds.optimizeMemoryConsumption: if (typeof arg.memory === 'object' && typeof arg.cpuUsage === 'object' && typeof arg.memory.workingSetSize === 'number') { - optimizeMemory(arg.memory, arg.cpuUsage); + setPreloadMemoryInfo(arg.memory, arg.cpuUsage); } break; + case apiCmds.optimizeMemoryRegister: + setPreloadWindow(event.sender); + break; case apiCmds.setIsInMeeting: if (typeof arg.isInMeeting === 'boolean') { setIsInMeeting(arg.isInMeeting); diff --git a/js/memoryMonitor.js b/js/memoryMonitor.js index 4119d5c5..6c654823 100644 --- a/js/memoryMonitor.js +++ b/js/memoryMonitor.js @@ -1,15 +1,23 @@ 'use strict'; +const eventEmitter = require('./eventEmitter'); + const log = require('./log.js'); const logLevels = require('./enums/logLevels.js'); -const { getMainWindow, setIsAutoReload } = require('./windowMgr'); -const systemIdleTime = require('@paulcbetts/system-idle-time'); +const { getMainWindow, setIsAutoReload, getIsOnline } = require('./windowMgr'); const { getConfigField } = require('./config'); const maxMemory = 800; +const defaultInterval = 30 * 1000; +const memoryRefreshThreshold = 60 * 60 * 1000; +const cpuUsageThreshold = 5; -let maxIdleTime = 4 * 60 * 60 * 1000; let isInMeeting = false; +let canReload = true; +let appMinimizedTimer; +let powerMonitorTimer; +let preloadMemory; +let preloadWindow; // once a minute setInterval(gatherMemory, 1000 * 60); @@ -31,16 +39,22 @@ function gatherMemory() { * 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 - * @param cpuUsage */ -function optimizeMemory(memoryInfo, cpuUsage) { - const memoryConsumed = (memoryInfo && memoryInfo.workingSetSize / 1024) || 0; +function optimizeMemory() { + + if (!preloadMemory || !preloadMemory.memoryInfo || !preloadMemory.cpuUsage) { + log.send(logLevels.INFO, `Memory info not available`); + return; + } + + const memoryConsumed = (preloadMemory.memoryInfo && preloadMemory.memoryInfo.workingSetSize / 1024) || 0; + const cpuUsagePercentage = preloadMemory.cpuUsage.percentCPUUsage; if (memoryConsumed > maxMemory - && systemIdleTime.getIdleTime() > maxIdleTime + && cpuUsagePercentage <= cpuUsageThreshold && !isInMeeting + && getIsOnline() + && canReload ) { getConfigField('memoryRefresh') .then((enabled) => { @@ -49,15 +63,29 @@ function optimizeMemory(memoryInfo, cpuUsage) { if (mainWindow && !mainWindow.isDestroyed()) { setIsAutoReload(true); - log.send(logLevels.INFO, 'Reloading the app to optimize memory usage as' + - ' memory consumption was ' + memoryConsumed + - ' CPU usage percentage was ' + cpuUsage.percentCPUUsage + - ' user activity tick was ' + systemIdleTime.getIdleTime() + - ' user was in a meeting? ' + isInMeeting ); + log.send(logLevels.INFO, `Reloading the app to optimize memory usage as + memory consumption was ${memoryConsumed} + CPU usage percentage was ${preloadMemory.cpuUsage.percentCPUUsage} + user was in a meeting? ${isInMeeting} + is network online? ${getIsOnline()}`); mainWindow.reload(); + + // do not refresh for another 1hrs + canReload = false; + setTimeout(() => { + canReload = true; + }, memoryRefreshThreshold); } + } else { + log.send(logLevels.INFO, `Memory refresh not enabled by the user so Not Reloading the app`); } }); + } else { + log.send(logLevels.INFO, `Not Reloading the app as + memory consumption was ${memoryConsumed} + CPU usage percentage was ${preloadMemory.cpuUsage.percentCPUUsage} + user was in a meeting? ${isInMeeting} + is network online? ${getIsOnline()}`); } } @@ -69,7 +97,93 @@ function setIsInMeeting(meetingStatus) { isInMeeting = meetingStatus; } +/** + * Sets preload memory info and calls optimize memory func + * + * @param memoryInfo + * @param cpuUsage + */ +function setPreloadMemoryInfo(memoryInfo, cpuUsage) { + log.send(logLevels.INFO, 'Memory info received from preload process now running optimize memory logic'); + preloadMemory = { memoryInfo, cpuUsage }; + optimizeMemory(); +} + +/** + * Called whenever the application is minimized + * and waits for 30s to optimize memory + */ +eventEmitter.on('appMinimized', () => { + appMinimizedTimer = setTimeout(() => { + const mainWindow = getMainWindow(); + if (mainWindow && !mainWindow.isDestroyed() && mainWindow.isMinimized()) { + log.send(logLevels.INFO, 'Application is minimised for more than 30s so calling requestMemoryInfo'); + requestMemoryInfo(); + } + }, defaultInterval); +}); + +/** + * Called whenever the application is restored from minimized state + * + * Clears appMinimizedTimer if the app is restored within 30s + * from minimized state + */ +eventEmitter.on('appRestored', () => { + log.send(logLevels.INFO, 'Application was restored from minimized state'); + if (appMinimizedTimer) { + clearTimeout(appMinimizedTimer); + } +}); + +/** + * Called whenever the system in locked + * and waits for 30s to optimize memory + */ +eventEmitter.on('sys-locked', () => { + log.send(logLevels.INFO, 'System screen was locked'); + powerMonitorTimer = setTimeout(() => { + log.send(logLevels.INFO, 'System screen was locked for more than 30s so calling requestMemoryInfo'); + requestMemoryInfo(); + }, defaultInterval); +}); + +/** + * Called whenever the system in locked + * + * Clears powerMonitorTimer if the system is unlocked within 30s + * from locked state + */ +eventEmitter.on('sys-unlocked', () => { + log.send(logLevels.INFO, 'System screen was unlocked'); + if (powerMonitorTimer) { + clearTimeout(powerMonitorTimer); + } +}); + +/** + * Sets the preload window + * + * @param win - preload window + */ +function setPreloadWindow(win) { + log.send(logLevels.INFO, 'Preload window registered'); + preloadWindow = win; +} + +/** + * Request memory info from the registered preload window + * which invokes the optimize memory func + */ +function requestMemoryInfo() { + if (preloadWindow) { + log.send(logLevels.INFO, 'Requesting memory information from the preload script'); + preloadWindow.send('memory-info-request'); + } +} + module.exports = { - optimizeMemory, - setIsInMeeting + setIsInMeeting, + setPreloadMemoryInfo, + setPreloadWindow, }; \ No newline at end of file diff --git a/js/preload/preloadMain.js b/js/preload/preloadMain.js index e8022174..4610035d 100644 --- a/js/preload/preloadMain.js +++ b/js/preload/preloadMain.js @@ -22,7 +22,6 @@ const getMediaSource = require('../desktopCapturer/getSource'); const { TitleBar } = require('../windowsTitlebar'); const titleBar = new TitleBar(); const { buildNumber } = require('../../package.json'); -const memoryMonitorInterval = 1000 * 60 * 60; const SnackBar = require('../snackBar').SnackBar; const KeyCodes = { Esc: 27, @@ -105,6 +104,13 @@ const throttledSetIsInMeetingStatus = throttle(1000, function (isInMeeting) { local.ipcRenderer.on('on-page-load', () => { loadSpellChecker(); snackBar = new SnackBar(); + + // only registers main window's preload + if (window.name === 'main') { + local.ipcRenderer.send(apiName, { + cmd: apiCmds.optimizeMemoryRegister, + }); + } }); const throttledActivate = throttle(1000, function (windowName) { @@ -129,17 +135,6 @@ const throttledSetLocale = throttle(1000, function (locale) { }); }); -// Gathers renderer process memory -setInterval(() => { - const memory = process.getProcessMemoryInfo(); - const cpuUsage = process.getCPUUsage(); - local.ipcRenderer.send(apiName, { - cmd: apiCmds.optimizeMemoryConsumption, - memory: memory, - cpuUsage: cpuUsage - }); -}, memoryMonitorInterval); - createAPI(); // creates API exposed from electron. @@ -519,6 +514,18 @@ function createAPI() { } }); + local.ipcRenderer.on('memory-info-request', () => { + if (window.name === 'main') { + const memory = process.getProcessMemoryInfo(); + const cpuUsage = process.getCPUUsage(); + local.ipcRenderer.send(apiName, { + cmd: apiCmds.optimizeMemoryConsumption, + memory: memory, + cpuUsage: cpuUsage + }); + } + }); + function updateOnlineStatus() { local.ipcRenderer.send(apiName, { cmd: apiCmds.isOnline, diff --git a/js/windowMgr.js b/js/windowMgr.js index 411e69f7..8681b391 100644 --- a/js/windowMgr.js +++ b/js/windowMgr.js @@ -216,6 +216,12 @@ function doCreateMainWindow(initialUrl, initialBounds, isCustomTitleBar) { // event sent to renderer process to remove snack bar mainWindow.webContents.send('window-leave-full-screen'); }); + mainWindow.on('minimize', () => { + eventEmitter.emit('appMinimized'); + }); + mainWindow.on('restore', () => { + eventEmitter.emit('appRestored'); + }); if (initialBounds) { // maximizes the application if previously maximized