refactor: cleanup ip:recent

This commit is contained in:
Barış Soner Uşaklı
2025-02-14 10:25:59 -05:00
parent df07fcfa54
commit d872470843
6 changed files with 37 additions and 62 deletions

View File

@@ -41,9 +41,6 @@
"sockets.default-placeholder": "Default: %1",
"sockets.delay": "Reconnection Delay",
"analytics.settings": "Analytics Settings",
"analytics.max-cache": "Analytics Cache Max Value",
"analytics.max-cache-help": "On high-traffic installs, the cache could be exhausted continuously if there are more concurrent active users than the Max Cache value. (Restart required)",
"compression.settings": "Compression Settings",
"compression.enable": "Enable Compression",
"compression.help": "This setting enables gzip compression. For a high-traffic website in production, the best way to put compression in place is to implement it at a reverse proxy level. You can enable it here for testing purposes."

View File

@@ -18,6 +18,8 @@ get:
latestVersion:
type: string
nullable: true
hideAllTime:
type: boolean
upgradeAvailable:
type: boolean
nullable: true

View File

@@ -3,7 +3,6 @@
const cronJob = require('cron').CronJob;
const winston = require('winston');
const nconf = require('nconf');
const crypto = require('crypto');
const util = require('util');
const _ = require('lodash');
@@ -12,36 +11,24 @@ const sleep = util.promisify(setTimeout);
const db = require('./database');
const utils = require('./utils');
const plugins = require('./plugins');
const meta = require('./meta');
const pubsub = require('./pubsub');
const cacheCreate = require('./cache/lru');
const Analytics = module.exports;
const secret = nconf.get('secret');
let local = {
counters: {},
pageViews: 0,
pageViewsRegistered: 0,
pageViewsGuest: 0,
pageViewsBot: 0,
uniqueIPCount: 0,
uniquevisitors: 0,
};
const empty = _.cloneDeep(local);
const total = _.cloneDeep(local);
let ipCache;
const runJobs = nconf.get('runJobs');
Analytics.init = async function () {
ipCache = cacheCreate({
max: parseInt(meta.config['analytics:maxCache'], 10) || 500,
ttl: 0,
});
new cronJob('*/10 * * * * *', (async () => {
publishLocalAnalytics();
if (runJobs) {
@@ -50,6 +37,14 @@ Analytics.init = async function () {
}
}), null, true);
if (runJobs) {
new cronJob('*/30 * * * *', (async () => {
const cutoff = Date.now() - 172800000;
const ips = await db.getSortedSetRangeByScore('ip:recent', 0, 500, '-inf', cutoff);
await db.sortedSetRemove('ip:recent', ips);
}), null, true);
}
if (runJobs) {
pubsub.on('analytics:publish', (data) => {
incrementProperties(total, data.local);
@@ -106,22 +101,17 @@ Analytics.pageView = async function (payload) {
}
if (payload.ip) {
// Retrieve hash or calculate if not present
let hash = ipCache.get(payload.ip + secret);
if (!hash) {
hash = crypto.createHash('sha1').update(payload.ip + secret).digest('hex');
ipCache.set(payload.ip + secret, hash);
const score = await db.sortedSetScore('ip:recent', payload.ip);
let record = !score;
if (score) {
const today = new Date();
today.setHours(today.getHours(), 0, 0, 0);
record = score < today.getTime();
}
const score = await db.sortedSetScore('ip:recent', hash);
if (!score) {
local.uniqueIPCount += 1;
}
const today = new Date();
today.setHours(today.getHours(), 0, 0, 0);
if (!score || score < today.getTime()) {
if (record) {
local.uniquevisitors += 1;
await db.sortedSetAdd('ip:recent', Date.now(), hash);
await db.sortedSetAdd('ip:recent', Date.now(), payload.ip);
}
}
};
@@ -176,11 +166,6 @@ Analytics.writeData = async function () {
total.uniquevisitors = 0;
}
if (total.uniqueIPCount > 0) {
dbQueue.push(db.incrObjectFieldBy('global', 'uniqueIPCount', total.uniqueIPCount));
total.uniqueIPCount = 0;
}
for (const [key, value] of Object.entries(total.counters)) {
incrByBulk.push([`analytics:${key}`, value, today.getTime()]);
metrics.push(key);

View File

@@ -41,6 +41,7 @@ dashboardController.get = async function (req, res) {
lastrestart: lastrestart,
showSystemControls: isAdmin,
popularSearches: popularSearches,
hideAllTime: true,
});
};
@@ -128,7 +129,7 @@ async function getStats() {
}
let results = await Promise.all([
getStatsFromAnalytics('uniquevisitors', 'uniqueIPCount'),
getStatsFromAnalytics('uniquevisitors', ''),
getStatsFromAnalytics('logins', 'loginCount'),
getStatsForSet('users:joindate', 'userCount'),
getStatsForSet('posts:pid', 'postCount'),
@@ -227,6 +228,7 @@ function calculateDeltas(results) {
}
async function getGlobalField(field) {
if (!field) return 0;
const count = await db.getObjectField('global', field);
return parseInt(count, 10) || 0;
}

View File

@@ -1,31 +1,33 @@
<div class="table-responsive mb-3">
<table class="table text-sm">
<thead>
<table class="table">
<thead class="text-xs">
<tr>
<th></th>
<th class="text-end">[[admin/dashboard:stats.yesterday]]</th>
<th class="text-end">[[admin/dashboard:stats.today]]</th>
<th class="text-end text-nowrap">[[admin/dashboard:stats.yesterday]]</th>
<th class="text-end text-nowrap">[[admin/dashboard:stats.today]]</th>
<th></th>
<th class="text-end">[[admin/dashboard:stats.last-week]]</th>
<th class="text-end">[[admin/dashboard:stats.this-week]]</th>
<th class="text-end text-nowrap">[[admin/dashboard:stats.last-week]]</th>
<th class="text-end text-nowrap">[[admin/dashboard:stats.this-week]]</th>
<th></th>
<th class="text-end">[[admin/dashboard:stats.last-month]]</th>
<th class="text-end">[[admin/dashboard:stats.this-month]]</th>
<th class="text-end text-nowrap">[[admin/dashboard:stats.last-month]]</th>
<th class="text-end text-nowrap">[[admin/dashboard:stats.this-month]]</th>
<th></th>
{{{ if !hideAllTime}}}
<th class="text-end">[[admin/dashboard:stats.all]]</th>
{{{ end }}}
</tr>
</thead>
<tbody>
<tbody class="text-sm">
{{{ each stats }}}
<tr>
<td>
<strong>
<td class="fw-bold text-nowrap">
{{{ if ./href }}}
<a href="{./href}">{./name}</a>
{{{ else }}}
{./name}
{{{ end }}}
</strong>
</td>
<td class="text-end">{formattedNumber(./yesterday)}</td>
<td class="text-end">{formattedNumber(./today)}</td>
@@ -38,8 +40,9 @@
<td class="text-end">{formattedNumber(./lastmonth)}</td>
<td class="text-end">{formattedNumber(./thismonth)}</td>
<td class="{./monthTextClass}"><small>{./monthIncrease}%</small></td>
{{{ if !hideAllTime}}}
<td class="text-end">{formattedNumber(./alltime)}</td>
{{{ end }}}
</tr>
{{{ end }}}
</tbody>

View File

@@ -146,20 +146,6 @@
<hr/>
<div id="analytics-settings" class="mb-4">
<h5 class="fw-bold tracking-tight settings-header">[[admin/settings/advanced:analytics.settings]]</h5>
<div class="mb-3">
<label class="form-label" for="analytics:maxCache">[[admin/settings/advanced:analytics.max-cache]]</label>
<input class="form-control" id="analytics:maxCache" type="text" value="500" placeholder="500" data-field="analytics:maxCache" />
<p class="form-text">
[[admin/settings/advanced:analytics.max-cache-help]]
</p>
</div>
</div>
<hr/>
<div id="compression-settings" class="mb-4">
<h5 class="fw-bold tracking-tight settings-header">[[admin/settings/advanced:compression.settings]]</h5>