mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-16 13:23:52 +02:00
refactor
This commit is contained in:
@@ -1,135 +0,0 @@
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const STORAGE_KEY = 'vrcx:sentry:piniaActions';
|
||||
const DEFAULT_MAX_ENTRIES = 200;
|
||||
|
||||
function getStorage() {
|
||||
try {
|
||||
return localStorage;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function safeJsonParse(value) {
|
||||
if (!value) return null;
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function safeJsonStringify(value, fallback = '[]') {
|
||||
try {
|
||||
return JSON.stringify(value);
|
||||
} catch {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
export function getPiniaActionTrail() {
|
||||
const storage = getStorage();
|
||||
if (!storage) return [];
|
||||
|
||||
const data = safeJsonParse(storage.getItem(STORAGE_KEY));
|
||||
return Array.isArray(data) ? data : [];
|
||||
}
|
||||
|
||||
export function clearPiniaActionTrail() {
|
||||
const storage = getStorage();
|
||||
if (!storage) return;
|
||||
storage.removeItem(STORAGE_KEY);
|
||||
}
|
||||
|
||||
export function appendPiniaActionTrail(entry, options) {
|
||||
const storage = getStorage();
|
||||
if (!storage) return;
|
||||
|
||||
const maxEntries = options?.maxEntries ?? DEFAULT_MAX_ENTRIES;
|
||||
|
||||
const existing = getPiniaActionTrail();
|
||||
existing.push(entry);
|
||||
|
||||
if (existing.length > maxEntries) {
|
||||
existing.splice(0, existing.length - maxEntries);
|
||||
}
|
||||
|
||||
try {
|
||||
storage.setItem(STORAGE_KEY, safeJsonStringify(existing));
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
export function createPiniaActionTrailPlugin(options) {
|
||||
const maxEntries = options?.maxEntries ?? DEFAULT_MAX_ENTRIES;
|
||||
return ({ store }) => {
|
||||
store.$onAction(({ name }) => {
|
||||
appendPiniaActionTrail(
|
||||
{
|
||||
t: dayjs().format('HH:mm:ss'),
|
||||
a: name
|
||||
},
|
||||
{ maxEntries }
|
||||
);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function readPerformanceMemory() {
|
||||
// @ts-ignore
|
||||
const memory = window?.performance?.memory;
|
||||
if (!memory) return null;
|
||||
const { usedJSHeapSize, jsHeapSizeLimit } = memory;
|
||||
if (
|
||||
typeof usedJSHeapSize !== 'number' ||
|
||||
typeof jsHeapSizeLimit !== 'number'
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return { usedJSHeapSize, jsHeapSizeLimit };
|
||||
}
|
||||
|
||||
export function startRendererMemoryThresholdReport(
|
||||
Sentry,
|
||||
{ intervalMs = 10_000, thresholdRatio = 0.8, cooldownMs = 5 * 60_000 } = {}
|
||||
) {
|
||||
const initial = readPerformanceMemory();
|
||||
if (!initial) return null;
|
||||
|
||||
if (!Sentry?.withScope) return null;
|
||||
|
||||
let lastSent = 0;
|
||||
|
||||
return setInterval(() => {
|
||||
const m = readPerformanceMemory();
|
||||
if (!m) return;
|
||||
|
||||
const ratio = m.usedJSHeapSize / m.jsHeapSizeLimit;
|
||||
if (!Number.isFinite(ratio) || ratio < thresholdRatio) return;
|
||||
|
||||
const now = Date.now();
|
||||
if (now - lastSent < cooldownMs) return;
|
||||
lastSent = now;
|
||||
|
||||
const trail = getPiniaActionTrail();
|
||||
const trailText = JSON.stringify(trail);
|
||||
Sentry.withScope((scope) => {
|
||||
scope.setLevel('warning');
|
||||
scope.setTag('reason', 'high-js-heap');
|
||||
scope.setContext('memory', {
|
||||
usedMB: m.usedJSHeapSize / 1024 / 1024,
|
||||
limitMB: m.jsHeapSizeLimit / 1024 / 1024,
|
||||
ratio
|
||||
});
|
||||
scope.setContext('pinia_actions', {
|
||||
trailText,
|
||||
count: trail.length
|
||||
});
|
||||
Sentry.captureMessage(
|
||||
'Memory usage critical: nearing JS heap limit'
|
||||
);
|
||||
});
|
||||
}, intervalMs);
|
||||
}
|
||||
50
src/plugins/rendererMemoryReport.js
Normal file
50
src/plugins/rendererMemoryReport.js
Normal file
@@ -0,0 +1,50 @@
|
||||
function readPerformanceMemory() {
|
||||
// @ts-ignore
|
||||
const memory = window?.performance?.memory;
|
||||
if (!memory) return null;
|
||||
const { usedJSHeapSize, jsHeapSizeLimit } = memory;
|
||||
if (
|
||||
typeof usedJSHeapSize !== 'number' ||
|
||||
typeof jsHeapSizeLimit !== 'number'
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return { usedJSHeapSize, jsHeapSizeLimit };
|
||||
}
|
||||
|
||||
export function startRendererMemoryThresholdReport(
|
||||
Sentry,
|
||||
{ intervalMs = 10_000, thresholdRatio = 0.8, cooldownMs = 5 * 60_000 } = {}
|
||||
) {
|
||||
const initial = readPerformanceMemory();
|
||||
if (!initial) return null;
|
||||
|
||||
if (!Sentry?.withScope) return null;
|
||||
|
||||
let lastSent = 0;
|
||||
|
||||
return setInterval(() => {
|
||||
const m = readPerformanceMemory();
|
||||
if (!m) return;
|
||||
|
||||
const ratio = m.usedJSHeapSize / m.jsHeapSizeLimit;
|
||||
if (!Number.isFinite(ratio) || ratio < thresholdRatio) return;
|
||||
|
||||
const now = Date.now();
|
||||
if (now - lastSent < cooldownMs) return;
|
||||
lastSent = now;
|
||||
|
||||
Sentry.withScope((scope) => {
|
||||
scope.setLevel('warning');
|
||||
scope.setTag('reason', 'high-js-heap');
|
||||
scope.setContext('memory', {
|
||||
usedMB: m.usedJSHeapSize / 1024 / 1024,
|
||||
limitMB: m.jsHeapSizeLimit / 1024 / 1024,
|
||||
ratio
|
||||
});
|
||||
Sentry.captureMessage(
|
||||
'Memory usage critical: nearing JS heap limit'
|
||||
);
|
||||
});
|
||||
}, intervalMs);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { router } from './router';
|
||||
import { startRendererMemoryThresholdReport } from './piniaActionTrail';
|
||||
import { startRendererMemoryThresholdReport } from './rendererMemoryReport';
|
||||
|
||||
import configRepository from '../services/config';
|
||||
|
||||
|
||||
@@ -90,23 +90,23 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
|
||||
const favoriteFriends = computed(() => {
|
||||
if (appearanceSettingsStore.sortFavorites) {
|
||||
return state.favoriteFriends_.sort(compareByFavoriteSortOrder);
|
||||
return state.favoriteFriends_.toSorted(compareByFavoriteSortOrder);
|
||||
}
|
||||
return state.favoriteFriends_.sort(compareByName);
|
||||
return state.favoriteFriends_.toSorted(compareByName);
|
||||
});
|
||||
|
||||
const favoriteWorlds = computed(() => {
|
||||
if (appearanceSettingsStore.sortFavorites) {
|
||||
return state.favoriteWorlds_.sort(compareByFavoriteSortOrder);
|
||||
return state.favoriteWorlds_.toSorted(compareByFavoriteSortOrder);
|
||||
}
|
||||
return state.favoriteWorlds_.sort(compareByName);
|
||||
return state.favoriteWorlds_.toSorted(compareByName);
|
||||
});
|
||||
|
||||
const favoriteAvatars = computed(() => {
|
||||
if (appearanceSettingsStore.sortFavorites) {
|
||||
return state.favoriteAvatars_.sort(compareByFavoriteSortOrder);
|
||||
return state.favoriteAvatars_.toSorted(compareByFavoriteSortOrder);
|
||||
}
|
||||
return state.favoriteAvatars_.sort(compareByName);
|
||||
return state.favoriteAvatars_.toSorted(compareByName);
|
||||
});
|
||||
|
||||
watch(
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { createPinia } from 'pinia';
|
||||
|
||||
import { getSentry, isSentryOptedIn } from '../plugins';
|
||||
import { createPiniaActionTrailPlugin } from '../plugins/piniaActionTrail';
|
||||
import { useAdvancedSettingsStore } from './settings/advanced';
|
||||
import { useAppearanceSettingsStore } from './settings/appearance';
|
||||
import { useAuthStore } from './auth';
|
||||
@@ -43,11 +42,6 @@ import { useWristOverlaySettingsStore } from './settings/wristOverlay';
|
||||
|
||||
export const pinia = createPinia();
|
||||
|
||||
function registerPiniaActionTrailPlugin() {
|
||||
if (!NIGHTLY) return;
|
||||
pinia.use(createPiniaActionTrailPlugin({ maxEntries: 200 }));
|
||||
}
|
||||
|
||||
async function registerSentryPiniaPlugin() {
|
||||
if (!NIGHTLY) return;
|
||||
if (!(await isSentryOptedIn())) return;
|
||||
@@ -125,9 +119,6 @@ async function registerSentryPiniaPlugin() {
|
||||
|
||||
export async function initPiniaPlugins() {
|
||||
await registerSentryPiniaPlugin();
|
||||
setTimeout(() => {
|
||||
registerPiniaActionTrailPlugin();
|
||||
}, 60000);
|
||||
}
|
||||
|
||||
export function createGlobalStores() {
|
||||
|
||||
@@ -10,10 +10,6 @@ import {
|
||||
SEARCH_LIMIT_MIN
|
||||
} from '../shared/constants';
|
||||
import { avatarRequest, queryRequest } from '../api';
|
||||
import {
|
||||
clearPiniaActionTrail,
|
||||
getPiniaActionTrail
|
||||
} from '../plugins/piniaActionTrail';
|
||||
import { debounce, parseLocation } from '../shared/utils';
|
||||
import { AppDebug } from '../services/appConfig';
|
||||
import { database } from '../services/database';
|
||||
@@ -556,29 +552,16 @@ export const useVrcxStore = defineStore('Vrcx', () => {
|
||||
if (advancedSettingsStore.sentryErrorReporting) {
|
||||
try {
|
||||
import('@sentry/vue').then((Sentry) => {
|
||||
const trail = getPiniaActionTrail()
|
||||
.filter((entry) => {
|
||||
if (!entry) return false;
|
||||
return (
|
||||
typeof entry.t === 'string' &&
|
||||
typeof entry.a === 'string'
|
||||
);
|
||||
})
|
||||
.reverse();
|
||||
const trailText = JSON.stringify(trail);
|
||||
Sentry.withScope((scope) => {
|
||||
scope.setLevel('fatal');
|
||||
scope.setTag('reason', 'crash-recovery');
|
||||
scope.setContext('pinia_actions', {
|
||||
trailText,
|
||||
scope.setContext('session', {
|
||||
sessionTime: performance.now() / 1000 / 60
|
||||
});
|
||||
Sentry.captureMessage(
|
||||
`crash message: ${crashMessage}`
|
||||
);
|
||||
});
|
||||
|
||||
clearPiniaActionTrail();
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error setting up Sentry feedback:', error);
|
||||
|
||||
@@ -278,9 +278,11 @@
|
||||
|
||||
const shouldHideSameInstance = computed(() => isSidebarGroupByInstance.value && isHideFriendsInSameInstance.value);
|
||||
|
||||
const selectedFavoriteGroupKeys = computed(() => new Set(sidebarFavoriteGroups.value));
|
||||
|
||||
const selectedFavoriteGroupIds = computed(() => {
|
||||
const selectedGroups = sidebarFavoriteGroups.value;
|
||||
const hasFilter = selectedGroups.length > 0;
|
||||
const selectedGroups = selectedFavoriteGroupKeys.value;
|
||||
const hasFilter = selectedGroups.size > 0;
|
||||
if (!hasFilter) {
|
||||
return allFavoriteFriendIds.value;
|
||||
}
|
||||
@@ -337,22 +339,18 @@
|
||||
);
|
||||
});
|
||||
|
||||
const vipFriendsByGroupStatus = computed(() => {
|
||||
return visibleFavoriteOnlineFriends.value;
|
||||
});
|
||||
|
||||
// VIP friends divide by group
|
||||
const vipFriendsDivideByGroup = computed(() => {
|
||||
const remoteFriendsByGroup = groupedByGroupKeyFavoriteFriends.value;
|
||||
const selectedGroups = sidebarFavoriteGroups.value;
|
||||
const hasFilter = selectedGroups.length > 0;
|
||||
const selectedGroups = selectedFavoriteGroupKeys.value;
|
||||
const hasFilter = selectedGroups.size > 0;
|
||||
|
||||
// Build a normalized list of { key, groupName, memberIds }
|
||||
const groups = [];
|
||||
|
||||
for (const key in remoteFriendsByGroup) {
|
||||
if (Object.hasOwn(remoteFriendsByGroup, key)) {
|
||||
if (hasFilter && !selectedGroups.includes(key)) continue;
|
||||
if (hasFilter && !selectedGroups.has(key)) continue;
|
||||
const groupName = favoriteFriendGroups.value.find((g) => g.key === key)?.displayName || '';
|
||||
const memberIds = new Set(remoteFriendsByGroup[key].map((f) => f.id));
|
||||
groups.push({ key, groupName, memberIds });
|
||||
@@ -361,7 +359,7 @@
|
||||
|
||||
for (const groupName in localFriendFavorites.value) {
|
||||
const selectedKey = `local:${groupName}`;
|
||||
if (hasFilter && !selectedGroups.includes(selectedKey)) continue;
|
||||
if (hasFilter && !selectedGroups.has(selectedKey)) continue;
|
||||
const userIds = localFriendFavorites.value[groupName];
|
||||
if (userIds?.length) {
|
||||
groups.push({ key: selectedKey, groupName, memberIds: new Set(userIds) });
|
||||
@@ -407,7 +405,7 @@
|
||||
|
||||
const vipFriendCount = isSidebarDivideByFriendGroup.value
|
||||
? vipFriendsDivideByGroup.value.reduce((sum, group) => sum + group.length, 0)
|
||||
: vipFriendsByGroupStatus.value.length;
|
||||
: visibleFavoriteOnlineFriends.value.length;
|
||||
|
||||
if (vipFriendCount) {
|
||||
rows.push(
|
||||
@@ -456,7 +454,7 @@
|
||||
}
|
||||
});
|
||||
} else {
|
||||
vipFriendsByGroupStatus.value.forEach((friend, idx) => {
|
||||
visibleFavoriteOnlineFriends.value.forEach((friend, idx) => {
|
||||
rows.push(buildFriendRow(friend, `vip:${friend?.id ?? idx}`));
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user