mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-03-03 11:01:20 +01:00
@@ -1,10 +1,5 @@
|
||||
{
|
||||
"cache": "Cache",
|
||||
"post-cache": "Post Cache",
|
||||
"group-cache": "Group Cache",
|
||||
"local-cache": "Local Cache",
|
||||
"object-cache": "Object Cache",
|
||||
"notification-cache": "Notification Cache",
|
||||
"percent-full": "%1% Full",
|
||||
"post-cache-size": "Post Cache Size",
|
||||
"items-in-cache": "Items in Cache"
|
||||
|
||||
@@ -27,6 +27,46 @@ define('admin/advanced/cache', ['alerts'], function (alerts) {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '#cache-table th', function () {
|
||||
const table = $(this).closest('table');
|
||||
const tbody = table.find('tbody');
|
||||
const columnIndex = $(this).index();
|
||||
|
||||
const rows = tbody.find('tr').toArray();
|
||||
|
||||
// Toggle sort direction
|
||||
const ascending = !!$(this).data('asc');
|
||||
$(this).data('asc', !ascending);
|
||||
|
||||
// Remove sort indicators from all headers
|
||||
table.find('th i').addClass('invisible');
|
||||
|
||||
$(this).find('i').removeClass('invisible')
|
||||
.toggleClass('fa-sort-up', ascending)
|
||||
.toggleClass('fa-sort-down', !ascending);
|
||||
|
||||
rows.sort(function (a, b) {
|
||||
const A = $(a).children().eq(columnIndex).text().trim();
|
||||
const B = $(b).children().eq(columnIndex).text().trim();
|
||||
// Remove thousands separators
|
||||
const cleanA = A.replace(/,/g, '');
|
||||
const cleanB = B.replace(/,/g, '');
|
||||
|
||||
const numA = parseFloat(cleanA);
|
||||
const numB = parseFloat(cleanB);
|
||||
|
||||
if (!isNaN(numA) && !isNaN(numB)) {
|
||||
return ascending ? numA - numB : numB - numA;
|
||||
}
|
||||
|
||||
return ascending ?
|
||||
A.localeCompare(B) :
|
||||
B.localeCompare(A);
|
||||
});
|
||||
|
||||
tbody.append(rows);
|
||||
});
|
||||
};
|
||||
return Cache;
|
||||
});
|
||||
|
||||
2
src/cache/lru.js
vendored
2
src/cache/lru.js
vendored
@@ -5,6 +5,7 @@ module.exports = function (opts) {
|
||||
const os = require('os');
|
||||
|
||||
const pubsub = require('../pubsub');
|
||||
const tracker = require('./tracker');
|
||||
|
||||
// lru-cache@7 deprecations
|
||||
const winston = require('winston');
|
||||
@@ -162,5 +163,6 @@ module.exports = function (opts) {
|
||||
return lruCache.peek(key);
|
||||
};
|
||||
|
||||
tracker.addCache(opts.name, cache);
|
||||
return cache;
|
||||
};
|
||||
|
||||
59
src/cache/tracker.js
vendored
Normal file
59
src/cache/tracker.js
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
'use strict';
|
||||
|
||||
|
||||
const utils = require('../utils');
|
||||
|
||||
const cacheList = Object.create(null);
|
||||
|
||||
exports.addCache = function (key, cache) {
|
||||
if (Object.hasOwn(cacheList, key)) {
|
||||
throw new Error(`[cache/tracker] Cache with key "${key}" already exists. This will overwrite the existing cache.`);
|
||||
}
|
||||
cacheList[key] = cache;
|
||||
};
|
||||
|
||||
exports.getCacheList = async function (sort = 'hits') {
|
||||
const result = [];
|
||||
for (const value of Object.values(cacheList)) {
|
||||
result.push(getInfo(value, process.uptime()));
|
||||
}
|
||||
|
||||
result.sort((a, b) => b[sort].replace(/,/g, '') - a[sort].replace(/,/g, ''));
|
||||
|
||||
result.sort(function (a, b) {
|
||||
const A = a[sort].replace(/,/g, '');
|
||||
const B = b[sort].replace(/,/g, '');
|
||||
const numA = parseFloat(A);
|
||||
const numB = parseFloat(B);
|
||||
|
||||
if (!isNaN(numA) && !isNaN(numB)) {
|
||||
return numB - numA;
|
||||
}
|
||||
return B.localeCompare(A);
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
exports.findCacheByName = function (name) {
|
||||
return cacheList[name];
|
||||
};
|
||||
|
||||
function getInfo(cache, uptimeInSeconds) {
|
||||
return {
|
||||
name: cache.name,
|
||||
length: cache.length,
|
||||
max: cache.max,
|
||||
maxSize: cache.maxSize,
|
||||
itemCount: cache.itemCount,
|
||||
percentFull: cache.name === 'post' ?
|
||||
((cache.length / cache.maxSize) * 100).toFixed(2) :
|
||||
((cache.itemCount / cache.max) * 100).toFixed(2),
|
||||
hits: utils.addCommas(String(cache.hits)),
|
||||
hitsPerSecond: (cache.hits / uptimeInSeconds).toFixed(2),
|
||||
misses: utils.addCommas(String(cache.misses)),
|
||||
hitRatio: ((cache.hits / (cache.hits + cache.misses) || 0)).toFixed(4),
|
||||
enabled: cache.enabled,
|
||||
ttl: cache.ttl,
|
||||
};
|
||||
}
|
||||
2
src/cache/ttl.js
vendored
2
src/cache/ttl.js
vendored
@@ -7,6 +7,7 @@ module.exports = function (opts) {
|
||||
const chalk = require('chalk');
|
||||
|
||||
const pubsub = require('../pubsub');
|
||||
const tracker = require('./tracker');
|
||||
|
||||
const ttlCache = new TTLCache(opts);
|
||||
if (!opts.name) {
|
||||
@@ -137,5 +138,6 @@ module.exports = function (opts) {
|
||||
return ttlCache.get(key, { updateAgeOnGet: false });
|
||||
};
|
||||
|
||||
tracker.addCache(opts.name, cache);
|
||||
return cache;
|
||||
};
|
||||
|
||||
@@ -2,63 +2,24 @@
|
||||
|
||||
const cacheController = module.exports;
|
||||
|
||||
const utils = require('../../utils');
|
||||
const plugins = require('../../plugins');
|
||||
const tracker = require('../../cache/tracker');
|
||||
|
||||
cacheController.get = async function (req, res) {
|
||||
const postCache = require('../../posts/cache').getOrCreate();
|
||||
const groupCache = require('../../groups').cache;
|
||||
const { objectCache } = require('../../database');
|
||||
const localCache = require('../../cache');
|
||||
const { delayCache } = require('../../notifications');
|
||||
const uptimeInSeconds = process.uptime();
|
||||
function getInfo(cache) {
|
||||
return {
|
||||
length: cache.length,
|
||||
max: cache.max,
|
||||
maxSize: cache.maxSize,
|
||||
itemCount: cache.itemCount,
|
||||
percentFull: cache.name === 'post' ?
|
||||
((cache.length / cache.maxSize) * 100).toFixed(2) :
|
||||
((cache.itemCount / cache.max) * 100).toFixed(2),
|
||||
hits: utils.addCommas(String(cache.hits)),
|
||||
hitsPerSecond: (cache.hits / uptimeInSeconds).toFixed(2),
|
||||
misses: utils.addCommas(String(cache.misses)),
|
||||
hitRatio: ((cache.hits / (cache.hits + cache.misses) || 0)).toFixed(4),
|
||||
enabled: cache.enabled,
|
||||
ttl: cache.ttl,
|
||||
};
|
||||
}
|
||||
let caches = {
|
||||
post: postCache,
|
||||
group: groupCache,
|
||||
local: localCache,
|
||||
notification: delayCache,
|
||||
};
|
||||
if (objectCache) {
|
||||
caches.object = objectCache;
|
||||
}
|
||||
caches = await plugins.hooks.fire('filter:admin.cache.get', caches);
|
||||
for (const [key, value] of Object.entries(caches)) {
|
||||
caches[key] = getInfo(value);
|
||||
}
|
||||
// force post cache to get created
|
||||
require('../../posts/cache').getOrCreate();
|
||||
|
||||
const caches = await tracker.getCacheList();
|
||||
|
||||
res.render('admin/advanced/cache', { caches });
|
||||
};
|
||||
|
||||
cacheController.dump = async function (req, res, next) {
|
||||
let caches = {
|
||||
post: require('../../posts/cache').getOrCreate(),
|
||||
object: require('../../database').objectCache,
|
||||
group: require('../../groups').cache,
|
||||
local: require('../../cache'),
|
||||
};
|
||||
caches = await plugins.hooks.fire('filter:admin.cache.get', caches);
|
||||
if (!caches.hasOwnProperty(req.query.name)) {
|
||||
const foundCache = await tracker.findCacheByName(req.query.name);
|
||||
if (!foundCache || !foundCache.dump) {
|
||||
return next();
|
||||
}
|
||||
|
||||
const data = JSON.stringify(caches[req.query.name].dump(), null, 4);
|
||||
const data = JSON.stringify(foundCache.dump(), null, 4);
|
||||
res.setHeader('Content-disposition', `attachment; filename= ${req.query.name}-cache.json`);
|
||||
res.setHeader('Content-type', 'application/json');
|
||||
res.write(data, (err) => {
|
||||
|
||||
@@ -16,7 +16,7 @@ const cacheCreate = require('../cache/lru');
|
||||
const utils = require('../utils');
|
||||
|
||||
const roomUidCache = cacheCreate({
|
||||
name: 'chat:room:uids',
|
||||
name: 'chat-room-uids',
|
||||
max: 500,
|
||||
ttl: 0,
|
||||
});
|
||||
|
||||
@@ -2,32 +2,18 @@
|
||||
|
||||
const SocketCache = module.exports;
|
||||
|
||||
const db = require('../../database');
|
||||
const plugins = require('../../plugins');
|
||||
const tracker = require('../../cache/tracker');
|
||||
|
||||
SocketCache.clear = async function (socket, data) {
|
||||
const caches = await getAvailableCaches();
|
||||
if (!caches[data.name]) {
|
||||
return;
|
||||
const foundCache = await tracker.findCacheByName(data.name);
|
||||
if (foundCache && foundCache.reset) {
|
||||
foundCache.reset();
|
||||
}
|
||||
caches[data.name].reset();
|
||||
};
|
||||
|
||||
SocketCache.toggle = async function (socket, data) {
|
||||
const caches = await getAvailableCaches();
|
||||
if (!caches[data.name]) {
|
||||
return;
|
||||
const foundCache = await tracker.findCacheByName(data.name);
|
||||
if (foundCache) {
|
||||
foundCache.enabled = data.enabled;
|
||||
}
|
||||
caches[data.name].enabled = data.enabled;
|
||||
};
|
||||
|
||||
async function getAvailableCaches() {
|
||||
const caches = {
|
||||
post: require('../../posts/cache').getOrCreate(),
|
||||
object: db.objectCache,
|
||||
group: require('../../groups').cache,
|
||||
local: require('../../cache'),
|
||||
notification: require('../../notifications').delayCache,
|
||||
};
|
||||
return await plugins.hooks.fire('filter:admin.cache.get', caches);
|
||||
}
|
||||
@@ -8,7 +8,7 @@ const cacheCreate = require('../cache/lru');
|
||||
module.exports = function (User) {
|
||||
User.blocks = {
|
||||
_cache: cacheCreate({
|
||||
name: 'user:blocks',
|
||||
name: 'user-blocks',
|
||||
max: 100,
|
||||
ttl: 0,
|
||||
}),
|
||||
|
||||
@@ -11,19 +11,19 @@
|
||||
|
||||
<div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm text-sm">
|
||||
<table id="cache-table" class="table table-sm text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td class="text-end">capacity</td>
|
||||
<td class="text-end">count</td>
|
||||
<td class="text-end">size</td>
|
||||
<td class="text-end">hits</td>
|
||||
<td class="text-end">misses</td>
|
||||
<td class="text-end">hit ratio</td>
|
||||
<td class="text-end">hits/sec</td>
|
||||
<td class="text-end">ttl</td>
|
||||
<td></td>
|
||||
<th><a href="#" class="text-reset">name</a> <i class="fa-solid invisible fa-sort-down"></i></th>
|
||||
<th class="text-end"><a href="#" class="text-reset">capacity</a> <i class="fa-solid invisible fa-sort-down"></i></th>
|
||||
<th class="text-end"><a href="#" class="text-reset">count</a> <i class="fa-solid invisible fa-sort-down"></i></th>
|
||||
<th class="text-end"><a href="#" class="text-reset">size</a> <i class="fa-solid invisible fa-sort-down"></i></th>
|
||||
<th class="text-end"><a href="#" class="text-reset">hits</a> <i class="fa-solid fa-sort-down"></i></th>
|
||||
<th class="text-end"><a href="#" class="text-reset">misses</a> <i class="fa-solid invisible fa-sort-down"></i></th>
|
||||
<th class="text-end"><a href="#" class="text-reset">hit ratio</a> <i class="fa-solid invisible fa-sort-down"></i></th>
|
||||
<th class="text-end"><a href="#" class="text-reset">hits/sec</a> <i class="fa-solid invisible fa-sort-down"></i></th>
|
||||
<th class="text-end"><a href="#" class="text-reset">ttl</a> <i class="fa-solid invisible fa-sort-down"></i></th>
|
||||
<th></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="text-xs">
|
||||
@@ -34,7 +34,7 @@
|
||||
<div class="form-check form-switch text-sm" data-name="{@key}" style="min-height: initial;">
|
||||
<input class="form-check-input" type="checkbox" {{{if caches.enabled}}}checked{{{end}}}>
|
||||
</div>
|
||||
[[admin/advanced/cache:{@key}-cache]]
|
||||
{./name}
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-end">{./percentFull}%</td>
|
||||
@@ -58,8 +58,8 @@
|
||||
<td class="text-end">{./ttl}</td>
|
||||
<td class="">
|
||||
<div class="d-flex justify-content-end gap-1">
|
||||
<a href="{config.relative_path}/api/admin/advanced/cache/dump?name={@key}" class="btn btn-light btn-sm"><i class="fa fa-download"></i></a>
|
||||
<a class="btn btn-sm btn-danger clear" data-name="{@key}"><i class="fa fa-trash"></i></a>
|
||||
<a href="{config.relative_path}/api/admin/advanced/cache/dump?name={./name}" class="btn btn-light btn-sm"><i class="fa fa-download"></i></a>
|
||||
<a class="btn btn-sm btn-danger clear" data-name="{./name}"><i class="fa fa-trash"></i></a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
Reference in New Issue
Block a user