Skip to content

Commit

Permalink
Adding miscellaneous metrics (#706)
Browse files Browse the repository at this point in the history
  • Loading branch information
kev306 authored Jul 3, 2024
1 parent 54fcaf3 commit 01d80d1
Show file tree
Hide file tree
Showing 12 changed files with 112 additions and 11 deletions.
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ services:
environment:
GF_SECURITY_ADMIN_USER: admin
GF_SECURITY_ADMIN_PASSWORD: pass
GF_SERVER_ROOT_URL: http://localhost:5000
user: "$UID:$GID"
depends_on:
- victoria-metrics
2 changes: 2 additions & 0 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import { SESSIONS_COLLECTION_NAME } from './constants';
import { promAgent } from './clients/victoriaMetrics/promAgent';

import './metrics/systemMetrics';
import './metrics/gameMetrics';
import './metrics/miscellaneousMetrics';

const assetsPath = path.join(__dirname, '../assets');

Expand Down
39 changes: 39 additions & 0 deletions src/clients/victoriaMetrics/promMetricCounter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import promClient, { Counter } from 'prom-client';
import { promAgent } from './promAgent';

interface CounterConfig {
name: string;
help: string;
labelNames?: string[];
}

export class PromMetricCounter {
private counter: Counter;
private labelNames: string[];

constructor(counterConfig: CounterConfig) {
promAgent.registerMetric(counterConfig.name);

this.counter = new promClient.Counter(counterConfig);
this.labelNames = counterConfig.labelNames;
}

public inc(num: number, labels?: Record<string, string>) {
if (labels) {
this.validateLabels(labels);
this.counter.inc(labels, num);
} else {
this.counter.inc(num);
}
}

private validateLabels(labels: Record<string, string>) {
const invalidLabels = Object.keys(labels).filter(
(label) => !this.labelNames.includes(label),
);

if (invalidLabels.length > 0) {
throw new Error(`Invalid labels provided: ${invalidLabels.join(', ')}.`);
}
}
}
5 changes: 5 additions & 0 deletions src/gameplay/game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { millisToStr } from '../util/time';
import shuffleArray from '../util/shuffleArray';
import { Anonymizer } from './anonymizer';
import { sendReplyToCommand } from '../sockets/sockets';
import { gamesPlayedMetric } from '../metrics/gameMetrics';

export const WAITING = 'Waiting';
export const MIN_PLAYERS = 5;
Expand Down Expand Up @@ -1254,6 +1255,8 @@ class Game extends Room {
}

// From this point on, no more game moves can be made. Game is finished.
gamesPlayedMetric.inc(1, { status: 'finished' });

// Clean up from here.
for (let i = 0; i < this.allSockets.length; i++) {
this.allSockets[i].emit('gameEnded');
Expand Down Expand Up @@ -2074,6 +2077,8 @@ class Game extends Room {
}

if (this.voidGameTracker.playerVoted(socket.request.user.username)) {
gamesPlayedMetric.inc(1, { status: 'voided' });

this.changePhase(Phase.Voided);
this.sendText(`Game has been voided.`, 'server-text');
}
Expand Down
1 change: 1 addition & 0 deletions src/gameplay/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export interface IUser {
pronoun?: string | null;
dateJoined?: Date;
lastLoggedIn?: Date[];
lastLoggedInDateMetric?: Date;
totalTimePlayed?: Date | number;
totalGamesPlayed?: number;
totalRankedGamesPlayed?: number;
Expand Down
7 changes: 7 additions & 0 deletions src/metrics/gameMetrics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { PromMetricCounter } from '../clients/victoriaMetrics/promMetricCounter';

export const gamesPlayedMetric = new PromMetricCounter({
name: 'games_played_total',
help: 'test',
labelNames: ['status'],
});
32 changes: 32 additions & 0 deletions src/metrics/miscellaneousMetrics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { allSockets } from '../sockets/sockets';
import { PromMetricGauge } from '../clients/victoriaMetrics/promMetricGauge';
import { PromMetricCounter } from '../clients/victoriaMetrics/promMetricCounter';

const onlinePlayersMetric = new PromMetricGauge({
name: `online_players_total`,
help: `Number of online players.`,
collect() {
this.set(allSockets.length);
},
});

export const uniqueLoginsMetric = new PromMetricCounter({
name: 'unique_logins_total',
help: 'Total number of unique logins over a 24h time period.',
});

export const avatarSubmissionsMetric = new PromMetricCounter({
name: `custom_avatar_submissions_total`,
help: `Total number of custom avatars submitted/rejected/approved.`,
labelNames: ['status'],
});

export const passwordResetRequestsMetric = new PromMetricCounter({
name: `password_reset_requests_total`,
help: `Total number of password reset emails sent out.`,
});

export const passwordResetCompletedMetric = new PromMetricCounter({
name: `password_resets_completed_total`,
help: `Total number of password resets completed.`,
});
1 change: 1 addition & 0 deletions src/models/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const UserSchema = new mongoose.Schema<IUser>({

// Oldest entries at the front. Latest at the end.
lastLoggedIn: [Date],
lastLoggedInDateMetric: Date,

totalTimePlayed: {
type: Date,
Expand Down
3 changes: 3 additions & 0 deletions src/myFunctions/sendResetPassword.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import uuid from 'uuid';
import ejs from 'ejs';
import emailTemplateResetPassword from './emailTemplateResetPassword';
import { sendEmail } from './sendEmail';
import { passwordResetRequestsMetric } from '../metrics/miscellaneousMetrics';

const TOKEN_TIMEOUT = 60 * 60 * 1000; // 1 hour

Expand All @@ -27,4 +28,6 @@ export const sendResetPassword = async (user: any, email: string) => {
const subject = 'ProAvalon Reset Password Request.';

sendEmail(email, subject, message);

passwordResetRequestsMetric.inc(1);
};
5 changes: 3 additions & 2 deletions src/routes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ import { resRoles, rolesToAlliances, spyRoles } from '../gameplay/roles/roles';
import { sendResetPassword } from '../myFunctions/sendResetPassword';
import uuid from 'uuid';
import { captchaMiddleware } from '../util/captcha';
import { PatreonAgent } from '../clients/patreon/patreonAgent';
import { PatreonController } from '../clients/patreon/patreonController';
import { passwordResetCompletedMetric } from '../metrics/miscellaneousMetrics';

const router = new Router();

Expand Down Expand Up @@ -296,6 +295,8 @@ router.get('/resetPassword/verifyResetPassword', async (req, res) => {

await user.save();

passwordResetCompletedMetric.inc(1);

req.flash('success', 'Your password has been reset!');
res.render('resetPasswordSuccess', { newPassword });
return;
Expand Down
5 changes: 5 additions & 0 deletions src/routes/profile/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { createNotification } from '../../myFunctions/createNotification';
import { getAndUpdatePatreonRewardTierForUser } from '../../rewards/getRewards';
import userAdapter from '../../databaseAdapters/user';
import { getAvatarLibrarySizeForUser } from '../../rewards/getRewards';
import { avatarSubmissionsMetric } from '../../metrics/miscellaneousMetrics';

const MAX_ACTIVE_AVATAR_REQUESTS = 1;
const MIN_GAMES_REQUIRED = 100;
Expand Down Expand Up @@ -368,10 +369,12 @@ router.post(
});

if (decision) {
avatarSubmissionsMetric.inc(1, { status: 'approved' });
console.log(
`Custom avatar request approved: forUser="${avatarReq.forUsername}" byMod="${modWhoProcessed.username}" modComment="${modComment}" resLink="${avatarReq.resLink}" spyLink="${avatarReq.spyLink}"`,
);
} else {
avatarSubmissionsMetric.inc(1, { status: 'rejected' });
console.log(
`Custom avatar request rejected: forUser="${avatarReq.forUsername}" byMod="${modWhoProcessed.username}" modComment="${modComment}"`,
);
Expand Down Expand Up @@ -488,6 +491,8 @@ router.post(

await avatarRequest.create(avatarRequestData);

avatarSubmissionsMetric.inc(1, { status: 'submitted' });

res
.status(200)
.send(
Expand Down
22 changes: 13 additions & 9 deletions src/sockets/sockets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ import { Role } from '../gameplay/roles/types';
import { Phase } from '../gameplay/phases/types';
import { Card } from '../gameplay/cards/types';
import { TOCommandsImported } from './commands/tournamentOrganisers';
import { PromMetricGauge } from '../clients/victoriaMetrics/promMetricGauge';
import userAdapter from '../databaseAdapters/user';
import { uniqueLoginsMetric } from '../metrics/miscellaneousMetrics';

const ONE_DAY_MILLIS = 24 * 60 * 60 * 1000; // 1 day

const chatSpamFilter = new ChatSpamFilter();
const createRoomFilter = new CreateRoomFilter();
Expand All @@ -57,14 +60,6 @@ if (process.env.NODE_ENV !== 'test') {
}, 1000);
}

const onlinePlayersMetric = new PromMetricGauge({
name: `online_players_total`,
help: `Number of online players.`,
collect() {
this.set(allSockets.length);
},
});

const quote = new Quote();

const dateResetRequired = 1543480412695;
Expand Down Expand Up @@ -739,6 +734,15 @@ export const server = function (io: SocketServer): void {

socket.rewards = await getAllRewardsForUser(socket.request.user);
socket = applyApplicableRewards(socket);

if (
!socket.request.user.lastLoggedInDateMetric ||
new Date() - socket.request.user.lastLoggedInDateMetric > ONE_DAY_MILLIS
) {
socket.request.user.lastLoggedInDateMetric = new Date();
await socket.request.user.save();
uniqueLoginsMetric.inc(1);
}
});
};

Expand Down

0 comments on commit 01d80d1

Please sign in to comment.