Skip to content

Commit e258682

Browse files
authored
Merge pull request #429 from tppadn/main
Adaptive Polling Implementation
2 parents e978a2d + 2f8d82e commit e258682

6 files changed

Lines changed: 369 additions & 4 deletions

File tree

backend/routes/api.js

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -912,6 +912,83 @@ router.post("/setTaskSettings", async (req, res) => {
912912
}
913913
});
914914

915+
// Get Activity Monitor Polling Settings
916+
router.get("/getActivityMonitorSettings", async (req, res) => {
917+
try {
918+
const settingsjson = await db.query('SELECT settings FROM app_config where "ID"=1').then((res) => res.rows);
919+
920+
if (settingsjson.length > 0) {
921+
const settings = settingsjson[0].settings || {};
922+
console.log(settings);
923+
const pollingSettings = settings.ActivityMonitorPolling || {
924+
activeSessionsInterval: 1000,
925+
idleInterval: 5000
926+
};
927+
res.send(pollingSettings);
928+
} else {
929+
res.status(404);
930+
res.send({ error: "Settings Not Found" });
931+
}
932+
} catch (error) {
933+
res.status(503);
934+
res.send({ error: "Error: " + error });
935+
}
936+
});
937+
938+
// Set Activity Monitor Polling Settings
939+
router.post("/setActivityMonitorSettings", async (req, res) => {
940+
const { activeSessionsInterval, idleInterval } = req.body;
941+
942+
if (activeSessionsInterval === undefined || idleInterval === undefined) {
943+
res.status(400);
944+
res.send("activeSessionsInterval and idleInterval are required");
945+
return;
946+
}
947+
948+
if (!Number.isInteger(activeSessionsInterval) || activeSessionsInterval <= 0) {
949+
res.status(400);
950+
res.send("A valid activeSessionsInterval(int) which is > 0 milliseconds is required");
951+
return;
952+
}
953+
954+
if (!Number.isInteger(idleInterval) || idleInterval <= 0) {
955+
res.status(400);
956+
res.send("A valid idleInterval(int) which is > 0 milliseconds is required");
957+
return;
958+
}
959+
960+
if (activeSessionsInterval > idleInterval) {
961+
res.status(400);
962+
res.send("activeSessionsInterval should be <= idleInterval for optimal performance");
963+
return;
964+
}
965+
966+
try {
967+
const settingsjson = await db.query('SELECT settings FROM app_config where "ID"=1').then((res) => res.rows);
968+
969+
if (settingsjson.length > 0) {
970+
const settings = settingsjson[0].settings || {};
971+
972+
settings.ActivityMonitorPolling = {
973+
activeSessionsInterval: activeSessionsInterval,
974+
idleInterval: idleInterval
975+
};
976+
977+
let query = 'UPDATE app_config SET settings=$1 where "ID"=1';
978+
await db.query(query, [settings]);
979+
980+
res.status(200);
981+
res.send(settings.ActivityMonitorPolling);
982+
} else {
983+
res.status(404);
984+
res.send({ error: "Settings Not Found" });
985+
}
986+
} catch (error) {
987+
res.status(503);
988+
res.send({ error: "Error: " + error });
989+
}
990+
});
991+
915992
//Jellystat functions
916993
router.get("/CheckForUpdates", async (req, res) => {
917994
try {

backend/tasks/ActivityMonitor.js

Lines changed: 97 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,20 +114,70 @@ function getWatchDogNotInSessions(SessionData, WatchdogData) {
114114
return removedData;
115115
}
116116

117-
function ActivityMonitor(interval) {
118-
// console.log("Activity Interval: " + interval);
117+
let currentIntervalId = null;
118+
let lastHadActiveSessions = false;
119+
let cachedPollingSettings = {
120+
activeSessionsInterval: 1000,
121+
idleInterval: 5000
122+
};
119123

120-
setInterval(async () => {
124+
async function ActivityMonitor(defaultInterval) {
125+
// console.log("Activity Monitor started with default interval: " + defaultInterval);
126+
127+
const runMonitoring = async () => {
121128
try {
122129
const config = await new configClass().getConfig();
123130

124131
if (config.error || config.state !== 2) {
125132
return;
126133
}
134+
135+
// Get adaptive polling settings from config
136+
const pollingSettings = config.settings?.ActivityMonitorPolling || {
137+
activeSessionsInterval: 1000,
138+
idleInterval: 5000
139+
};
140+
141+
// Check if polling settings have changed
142+
const settingsChanged =
143+
cachedPollingSettings.activeSessionsInterval !== pollingSettings.activeSessionsInterval ||
144+
cachedPollingSettings.idleInterval !== pollingSettings.idleInterval;
145+
146+
if (settingsChanged) {
147+
console.log('[ActivityMonitor] Polling settings changed, updating intervals');
148+
console.log('Old settings:', cachedPollingSettings);
149+
console.log('New settings:', pollingSettings);
150+
cachedPollingSettings = { ...pollingSettings };
151+
}
152+
127153
const ExcludedUsers = config.settings?.ExcludedUsers || [];
128154
const apiSessionData = await API.getSessions();
129155
const SessionData = apiSessionData.filter((row) => row.NowPlayingItem !== undefined && !ExcludedUsers.includes(row.UserId));
130156
sendUpdate("sessions", apiSessionData);
157+
158+
const hasActiveSessions = SessionData.length > 0;
159+
160+
// Determine current appropriate interval
161+
const currentInterval = hasActiveSessions ? pollingSettings.activeSessionsInterval : pollingSettings.idleInterval;
162+
163+
// Check if we need to change the interval (either due to session state change OR settings change)
164+
if (hasActiveSessions !== lastHadActiveSessions || settingsChanged) {
165+
if (hasActiveSessions !== lastHadActiveSessions) {
166+
console.log(`[ActivityMonitor] Switching to ${hasActiveSessions ? 'active' : 'idle'} polling mode (${currentInterval}ms)`);
167+
lastHadActiveSessions = hasActiveSessions;
168+
}
169+
if (settingsChanged) {
170+
console.log(`[ActivityMonitor] Applying new ${hasActiveSessions ? 'active' : 'idle'} interval: ${currentInterval}ms`);
171+
}
172+
173+
// Clear current interval and restart with new timing
174+
if (currentIntervalId) {
175+
clearInterval(currentIntervalId);
176+
}
177+
currentIntervalId = setInterval(runMonitoring, currentInterval);
178+
return; // Let the new interval handle the next execution
179+
}
180+
131181
/////get data from jf_activity_monitor
132182
const WatchdogData = await db.query("SELECT * FROM jf_activity_watchdog").then((res) => res.rows);
133183

@@ -258,7 +308,50 @@ function ActivityMonitor(interval) {
258308
}
259309
return [];
260310
}
261-
}, interval);
311+
};
312+
313+
// Get initial configuration to start with the correct interval
314+
const initConfig = async () => {
315+
try {
316+
const config = await new configClass().getConfig();
317+
318+
if (config.error || config.state !== 2) {
319+
console.log("[ActivityMonitor] Config not ready, starting with default interval:", defaultInterval + "ms");
320+
currentIntervalId = setInterval(runMonitoring, defaultInterval);
321+
return;
322+
}
323+
324+
// Get adaptive polling settings from config
325+
const pollingSettings = config.settings?.ActivityMonitorPolling || {
326+
activeSessionsInterval: 1000,
327+
idleInterval: 5000
328+
};
329+
330+
// Initialize cached settings
331+
cachedPollingSettings = { ...pollingSettings };
332+
333+
// Start with idle interval since there are likely no active sessions at startup
334+
const initialInterval = pollingSettings.idleInterval;
335+
console.log("[ActivityMonitor] Starting adaptive polling with idle interval:", initialInterval + "ms");
336+
console.log("[ActivityMonitor] Loaded settings:", pollingSettings);
337+
currentIntervalId = setInterval(runMonitoring, initialInterval);
338+
339+
} catch (error) {
340+
console.log("[ActivityMonitor] Error loading config, using default interval:", defaultInterval + "ms");
341+
currentIntervalId = setInterval(runMonitoring, defaultInterval);
342+
}
343+
};
344+
345+
// Initialize with proper configuration
346+
await initConfig();
347+
348+
// Return a cleanup function
349+
return () => {
350+
if (currentIntervalId) {
351+
clearInterval(currentIntervalId);
352+
currentIntervalId = null;
353+
}
354+
};
262355
}
263356

264357
module.exports = {

public/locales/en-UK/translation.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,15 @@
215215
"1_DAY": "1 Day",
216216
"1_WEEK": "1 Week"
217217
},
218+
"ACTIVITY_MONITOR": "Activity Monitor",
219+
"ACTIVE_SESSIONS_INTERVAL": "Active Sessions Interval (ms)",
220+
"ACTIVE_SESSIONS_HELP": "How often to check when users are watching content (recommended: 1000ms)",
221+
"IDLE_INTERVAL": "Idle Interval (ms)",
222+
"IDLE_HELP": "How often to check when no active sessions (recommended: 5000ms)",
223+
"POLLING_INFO_TITLE": "Smart Polling",
224+
"POLLING_INFO": "The system automatically adapts monitoring frequency: fast when users are watching content, slower when the server is idle. This reduces CPU load on your Jellyfin server.",
225+
"INTERVAL_WARNING": "Active sessions interval should not be greater than idle interval",
226+
"REALTIME_UPDATE_INFO": "Changes are applied in real-time without server restart.",
218227
"SELECT_LIBRARIES_TO_IMPORT": "Select Libraries to Import",
219228
"SELECT_LIBRARIES_TO_IMPORT_TOOLTIP": "Activity for Items within these libraries are still Tracked - Even when not imported.",
220229
"DATE_ADDED": "Date Added"

public/locales/fr-FR/translation.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,15 @@
211211
"1_DAY": "1 Jour",
212212
"1_WEEK": "1 Semaine"
213213
},
214+
"ACTIVITY_MONITOR": "Surveillance d'activité",
215+
"ACTIVE_SESSIONS_INTERVAL": "Intervalle avec sessions actives (ms)",
216+
"ACTIVE_SESSIONS_HELP": "Fréquence de vérification quand des utilisateurs regardent du contenu (recommandé: 1000ms)",
217+
"IDLE_INTERVAL": "Intervalle en veille (ms)",
218+
"IDLE_HELP": "Fréquence de vérification quand aucune session active (recommandé: 5000ms)",
219+
"POLLING_INFO_TITLE": "Polling intelligent",
220+
"POLLING_INFO": "Le système adapte automatiquement la fréquence de surveillance : rapide quand des utilisateurs regardent du contenu, plus lent quand le serveur est inactif. Cela réduit la charge CPU sur votre serveur Jellyfin.",
221+
"INTERVAL_WARNING": "L'intervalle actif ne devrait pas être supérieur à l'intervalle de veille",
222+
"REALTIME_UPDATE_INFO": "Les modifications sont appliquées en temps réel sans redémarrage du serveur.",
214223
"SELECT_LIBRARIES_TO_IMPORT": "Sélectionner les médiathèques à importer",
215224
"SELECT_LIBRARIES_TO_IMPORT_TOOLTIP": "L'activité du contenu de ces médiathèques est toujours suivie, même s'ils ne sont pas importés.",
216225
"DATE_ADDED": "Date d'ajout"

0 commit comments

Comments
 (0)