Skip to content

Commit

Permalink
[keyserver] Introduce cronjob for product metrics
Browse files Browse the repository at this point in the history
Summary: This cronjob will generate some product metrics daily and send them to me in a Comm chat.

Test Plan: Ran it via script on the auxiliary production keyserver instance running on my personal server and confirmed that it works

Reviewers: will, varun

Reviewed By: will

Subscribers: tomek

Differential Revision: https://phab.comm.dev/D13751
  • Loading branch information
Ashoat committed Oct 18, 2024
1 parent 4a37abc commit 4c29bb8
Show file tree
Hide file tree
Showing 2 changed files with 141 additions and 0 deletions.
14 changes: 14 additions & 0 deletions keyserver/src/cron/cron.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import schedule from 'node-schedule';

import { backupDB } from './backups.js';
import { createDailyUpdatesThread } from './daily-updates.js';
import { postMetrics } from './metrics.js';
import { postLeaderboard } from './phab-leaderboard.js';
import { updateAndReloadGeoipDB } from './update-geoip-db.js';
import { updateIdentityReservedUsernames } from './update-identity-reserved-usernames.js';
Expand Down Expand Up @@ -164,5 +165,18 @@ if (cluster.isMaster) {
}
},
);
schedule.scheduleJob(
'0 6 * * *', // every day at 6:00 AM in the keyserver's timezone
async () => {
try {
await postMetrics();
} catch (e) {
console.warn(
'encountered error while trying to post product metrics',
e,
);
}
},
);
}
}
127 changes: 127 additions & 0 deletions keyserver/src/cron/metrics.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// @flow

import bots from 'lib/facts/bots.js';
import { messageTypes } from 'lib/types/message-types-enum.js';

import createMessages from '../creators/message-creator.js';
import { dbQuery, SQL } from '../database/database.js';
import { createScriptViewer } from '../session/scripts.js';

const metricsChannel = '80820870';
const millisecondsPerDay = 24 * 60 * 60 * 1000;

async function postMetrics() {
if (!process.env.RUN_COMM_TEAM_DEV_SCRIPTS) {
// This is a job that the Comm internal team uses
return;
}

const oneDayAgo = Date.now() - millisecondsPerDay;
const thirtyDaysAgo = Date.now() - millisecondsPerDay * 30;
const [
dailyActives,
monthlyActives,
oneWeekRetention,
twoWeekRetention,
retentionSinceLaunch,
] = await Promise.all([
getActiveCountSince(oneDayAgo),
getActiveCountSince(thirtyDaysAgo),
getRetention(7),
getRetention(14),
getRetentionSinceLaunch(),
]);

const metrics = {
'DAUs': dailyActives,
'MAUs': monthlyActives,
'D7': oneWeekRetention,
'D14': twoWeekRetention,
'retention since launch': retentionSinceLaunch,
};
const today = new Date().toLocaleString('default', {
day: 'numeric',
month: 'long',
year: 'numeric',
});
const metricText =
`### Metrics for ${today}\n` +
'```\n' +
`${JSON.stringify(metrics, undefined, 2)}\n` +
'```';

const viewer = createScriptViewer(bots.commbot.userID);
const messageDatas = [
{
type: messageTypes.TEXT,
threadID: metricsChannel,
creatorID: bots.commbot.userID,
time: Date.now(),
text: metricText,
},
];
await createMessages(viewer, messageDatas);
}

async function getActiveCountSince(time: number): Promise<number> {
const [result] = await dbQuery(SQL`
SELECT COUNT(DISTINCT u.id) AS count
FROM users u
LEFT JOIN cookies c ON c.user = u.id
WHERE last_used IS NOT NULL AND last_used > ${time}
`);
const [row] = result;
return row.count;
}

// Of the users that created their account N days ago,
// how many were active in the last day?
type RetentionResult = { +retainedCount: number, +totalCount: number };
async function getRetention(daysAgo: number): Promise<RetentionResult> {
const startOfNDaysAgo = Date.now() - millisecondsPerDay * daysAgo;
const endOfNDaysAgo = Date.now() - millisecondsPerDay * (daysAgo - 1);
const [result] = await dbQuery(SQL`
SELECT u.id, MAX(c.last_used) AS lastUsed
FROM users u
LEFT JOIN cookies c ON c.user = u.id
WHERE u.creation_time >= ${startOfNDaysAgo}
AND u.creation_time < ${endOfNDaysAgo}
GROUP BY u.id
`);

const totalCount = result.length;

const oneDayAgo = Date.now() - millisecondsPerDay;
const retainedCount = result.filter(
({ lastUsed }) => lastUsed > oneDayAgo,
).length;

return { retainedCount, totalCount };
}

// We're measuring users that signed up in the 7 days following launch.
// They count as retained if they've used Comm in the last day.
async function getRetentionSinceLaunch(): Promise<RetentionResult> {
const launchDate = new Date('2024-10-03');
const launchDaysAgo = Math.ceil(
(Date.now() - launchDate.getTime()) / millisecondsPerDay,
);

const retentionPromises: Array<Promise<RetentionResult>> = [];
for (let i = 0; i < 7; i++) {
retentionPromises.push(getRetention(launchDaysAgo - i));
}

const totalRetentionResults = {
retainedCount: 0,
totalCount: 0,
};
const retentionResults = await Promise.all(retentionPromises);
for (const retentionResult of retentionResults) {
totalRetentionResults.retainedCount += retentionResult.retainedCount;
totalRetentionResults.totalCount += retentionResult.totalCount;
}
return totalRetentionResults;
}

export { postMetrics };

0 comments on commit 4c29bb8

Please sign in to comment.