mirror of https://github.com/rancher/dashboard.git
283 lines
8.1 KiB
TypeScript
283 lines
8.1 KiB
TypeScript
import { SETTING } from '@shell/config/settings';
|
|
import { COUNT, MANAGEMENT } from '@shell/config/types';
|
|
import { GC_DEFAULTS, GC_PREFERENCES } from './gc-types';
|
|
|
|
class GarbageCollect {
|
|
private static ENABLE_DEBUG_LOGGING = false;
|
|
|
|
/**
|
|
* Don't run GC if it's been run within 5 seconds
|
|
*/
|
|
private static GC_RE_RUN_GAP = 1000 * 5;
|
|
|
|
private cachedGcPrefs: GC_PREFERENCES = GC_DEFAULTS;
|
|
|
|
private cachedGcPrefsStamp = '';
|
|
|
|
private debugLog = (...args: any) => {
|
|
if (GarbageCollect.ENABLE_DEBUG_LOGGING) {
|
|
console.debug('GC', ...args); // eslint-disable-line no-console
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Time the GC last ran
|
|
*/
|
|
private gcLastRun: number | null = 0;
|
|
|
|
/**
|
|
* To avoid JSON.parse on the `ui-performance` setting keep a local cache
|
|
*/
|
|
private getUiPerfGarbageCollection = (rootState: any) => {
|
|
const uiPerfSetting = rootState.management.types[MANAGEMENT.SETTING]?.list?.find((s: any) => s.id === SETTING.UI_PERFORMANCE);
|
|
|
|
if (!uiPerfSetting || !uiPerfSetting.value) {
|
|
// Could be in the process of logging out
|
|
return undefined;
|
|
}
|
|
const stamp = `${ uiPerfSetting.metadata.generation }-${ uiPerfSetting.metadata.resourceVersion }`;
|
|
|
|
if (this.cachedGcPrefsStamp !== stamp) {
|
|
this.debugLog(`Updating ${ SETTING.UI_PERFORMANCE } cache`);
|
|
this.cachedGcPrefsStamp = stamp;
|
|
const uiPerfSettingParsed = JSON.parse(uiPerfSetting.value);
|
|
|
|
this.cachedGcPrefs = uiPerfSettingParsed.garbageCollection;
|
|
}
|
|
|
|
return this.cachedGcPrefs;
|
|
};
|
|
|
|
/**
|
|
* The last time a resource was accessed by either find or getters style functions
|
|
*/
|
|
private lastAccessedCache: {
|
|
[store: string]: {
|
|
[type: string]: number;
|
|
}
|
|
} = {}
|
|
|
|
/**
|
|
* Track when a logged in route changes.
|
|
*/
|
|
private lastRouteChange = 0;
|
|
|
|
// ------------- GC Enabled ---------------------
|
|
|
|
gcEnabledAll(pseudoCtx: any, type: string) {
|
|
return this.gcEnabledForStore(pseudoCtx.state) && this.gcEnabledSetting(pseudoCtx) && this.gcEnabledForType(pseudoCtx, type);
|
|
}
|
|
|
|
gcEnabledSetting(pseudoCtx: any) {
|
|
const { rootState } = pseudoCtx;
|
|
|
|
// Don't use a getter... as we'll end up triggering ourselves again
|
|
const uiPerfGarbageCollection = this.getUiPerfGarbageCollection(rootState);
|
|
|
|
return uiPerfGarbageCollection?.enabled;
|
|
}
|
|
|
|
gcEnabledForStore(state: any) {
|
|
return state?.config?.supportsGc;
|
|
}
|
|
|
|
/**
|
|
* Store can require certain types are not GC'd (for example `cluster` and `schema`s, `counts`, etc)
|
|
*/
|
|
gcEnabledForType(pseudoCtx: any, type: string) {
|
|
const { getters } = pseudoCtx;
|
|
|
|
if (!type || getters.gcIgnoreTypes[type]) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
gcEnabledInterval(pseudoCtx: any) {
|
|
const { rootState } = pseudoCtx;
|
|
|
|
// Don't use a getter... as we'll end up triggering ourselves again
|
|
const uiPerfGarbageCollection = this.getUiPerfGarbageCollection(rootState);
|
|
|
|
return {
|
|
enabled: uiPerfGarbageCollection?.enabledInterval,
|
|
interval: uiPerfGarbageCollection?.interval || 0
|
|
};
|
|
}
|
|
|
|
gcEnabledRoute(pseudoCtx: any) {
|
|
const { rootState } = pseudoCtx;
|
|
|
|
// Don't use a getter... as we'll end up triggering ourselves again
|
|
const uiPerfGarbageCollection = this.getUiPerfGarbageCollection(rootState);
|
|
|
|
return uiPerfGarbageCollection?.enabledOnNavigate;
|
|
}
|
|
|
|
// ------------- GC (actual) ---------------------
|
|
|
|
/**
|
|
* Remove stale resource types from the store and stop watching them for changes
|
|
*/
|
|
garbageCollect(ctx: any, ignoreTypes: {[type: string]: boolean} = {}) {
|
|
const now = new Date().getTime();
|
|
|
|
// Is gc currently running OR has run in the past GC_RE_RUN_GAP return early
|
|
if (this.gcLastRun === null || now - this.gcLastRun < GarbageCollect.GC_RE_RUN_GAP) {
|
|
this.debugLog('Skipping (running or recently run)', this.gcLastRun ? new Date(this.gcLastRun) : 'running');
|
|
|
|
return;
|
|
}
|
|
|
|
this.gcLastRun = null;
|
|
const gcd: {[type: string]: number} = {};
|
|
|
|
try {
|
|
const { getters, rootState, dispatch } = ctx;
|
|
|
|
if (!rootState.clusterReady) {
|
|
this.debugLog('Skipping (cluster not ready)');
|
|
this.gcLastRun = new Date().getTime();
|
|
|
|
return ;
|
|
}
|
|
this.debugLog(`------ Started ------`);
|
|
|
|
const uiPerfGarbageCollection = this.getUiPerfGarbageCollection(rootState);
|
|
|
|
if (!uiPerfGarbageCollection) {
|
|
return ;
|
|
}
|
|
const maxAge = uiPerfGarbageCollection.ageThreshold * 1000;
|
|
const maxCount = uiPerfGarbageCollection.countThreshold;
|
|
|
|
this.debugLog(`Max Age: ${ maxAge }. Max Count: ${ maxCount }`);// , 'Cache', this.lastAccessedCache
|
|
|
|
const store = ctx.state.config.namespace;
|
|
|
|
// this.debugLog('Cache', this.lastAccessedCache);
|
|
|
|
Object.entries((this.lastAccessedCache[store] || {})).forEach(([type, lastAccessed]) => {
|
|
if (!lastAccessed) {
|
|
// There's no last accessed time... gc is probably disabled in another way
|
|
this.debugLog(`${ type }: Skipping (no accessed time)`);
|
|
|
|
return;
|
|
}
|
|
|
|
if (!this.gcEnabledForType(ctx, type)) {
|
|
// This specific store is telling us to ignore the type (for example `cluster` store will not GC schema's, counts, etc)
|
|
this.debugLog(`${ type }: Skipping (type ignored by store)`);
|
|
|
|
return;
|
|
}
|
|
|
|
if (ignoreTypes[type]) {
|
|
// We're going to a place that needs the resource
|
|
this.debugLog(`${ type }: Skipping (navigating to type)`);
|
|
|
|
return;
|
|
}
|
|
|
|
if (now - lastAccessed <= maxAge) {
|
|
// The resource was recently accessed
|
|
|
|
this.debugLog(`${ type }: Skipping (recently accessed)`);
|
|
|
|
return;
|
|
}
|
|
|
|
if (this.lastRouteChange !== undefined && this.lastRouteChange < lastAccessed ) {
|
|
// The resource is being used in the current route/page
|
|
this.debugLog(`${ type }: Skipping (used in current route/page)`);
|
|
|
|
return;
|
|
}
|
|
|
|
const countFromResource = getters.all(COUNT)[0].counts[type]?.summary.count;
|
|
const currentCount = countFromResource ?? 0;
|
|
|
|
if (currentCount === undefined || currentCount < maxCount) {
|
|
// There's too few resources, we might as well keep them to avoid a network request when we need them again
|
|
this.debugLog(`${ type }: Skipping (too few of resource)`);
|
|
|
|
return;
|
|
}
|
|
|
|
this.debugLog(`${ type }: Removing from store`);
|
|
dispatch('forgetType', type);
|
|
gcd[type] = currentCount;
|
|
});
|
|
|
|
this.gcLastRun = new Date().getTime();
|
|
} catch (e) {
|
|
this.debugLog(`: Error`, e);
|
|
this.gcLastRun = new Date().getTime();
|
|
}
|
|
|
|
if (Object.keys(gcd).length > 0) {
|
|
console.info('Garbage Collected Resources', gcd); // eslint-disable-line no-console
|
|
}
|
|
|
|
this.debugLog(`------ Finished ------`);
|
|
}
|
|
|
|
// ------------- GC Update local cache ---------------------
|
|
|
|
/**
|
|
* Update the time that the resource type was accessed
|
|
* This needs to run after any type initialisation (aka registerType)
|
|
*/
|
|
gcUpdateLastAccessed(pseudoCtx: any, type: string) {
|
|
if (!this.gcEnabledAll(pseudoCtx, type)) {
|
|
return;
|
|
}
|
|
const store = pseudoCtx.state.config.namespace;
|
|
|
|
if (!this.lastAccessedCache[store]) {
|
|
this.lastAccessedCache[store] = {};
|
|
}
|
|
|
|
this.lastAccessedCache[store][type] = new Date().getTime();
|
|
}
|
|
|
|
/**
|
|
* Update the time the user last changed routes
|
|
*/
|
|
gcUpdateRouteChanged() {
|
|
this.lastRouteChange = new Date().getTime();
|
|
}
|
|
|
|
// ------------- GC reset ---------------------
|
|
|
|
/**
|
|
* Remove all cached access times for the given store
|
|
*/
|
|
gcResetStore(state: any) {
|
|
const store = state.config.namespace;
|
|
|
|
delete this.lastAccessedCache[store];
|
|
|
|
this.debugLog('Forgetting Store:', store);
|
|
}
|
|
|
|
/**
|
|
* Remove cached access time for the given resource type
|
|
*/
|
|
gcResetType(state: any, type: string) {
|
|
const store = state.config.namespace;
|
|
|
|
if (!this.lastAccessedCache[store]) {
|
|
return;
|
|
}
|
|
delete this.lastAccessedCache[store][type];
|
|
|
|
this.debugLog('Forgetting Type:', store, type);
|
|
}
|
|
}
|
|
|
|
const gc = new GarbageCollect();
|
|
|
|
export default gc;
|