Skip to content

Instantly share code, notes, and snippets.

@suzmas
Last active February 14, 2025 16:20
Show Gist options
  • Save suzmas/9206cf14f3d2384a8e709fe97b371417 to your computer and use it in GitHub Desktop.
Save suzmas/9206cf14f3d2384a8e709fe97b371417 to your computer and use it in GitHub Desktop.
logging stuff for meteor
////////////////////////////////////////////////////////////////
// -- Tracking stats for methods and subscriptions -- //
///////////////////////////////////////////////////////////////
/**
* methodStats[methodName] = {
* calls: Number, // how many times we called it
* bytes: Number // total bytes received for the method’s result
* }
*
* subStats[subName] = {
* subs: Number, // how many times we subscribed
* }
*
* subCollStats[collName] = {
* msgs: Number, // number of sub messages related to this collection
* bytes: Number // total bytes received related to this collection
* }
*
* methodIds[methodId] = methodName // track method-ID -> methodName
* subIds[subId] = subName // track sub-ID -> subName
*/
const methodStats = {};
const subStats = {};
const subCollStats = {};
const methodIds = {};
const subIds = {};
let totalBytesReceived = 0; // absolute total across *all* messages
// Functions you can call from the console:
function logMethodTotals() {
console.table(methodStats);
}
function logSubTotals() {
console.table(subStats);
}
function logSubCollTotals() {
console.table(subCollStats);
}
function resetTotals() {
totalBytesReceived = 0;
Object.keys(methodStats).forEach(name => {
methodStats[name].calls = 0;
methodStats[name].bytes = 0;
});
Object.keys(subStats).forEach(name => {
subStats[name].subs = 0;
subStats[name].bytes = 0;
});
Object.keys(subCollStats).forEach(name => {
subCollStats[name].msgs = 0;
subCollStats[name].bytes = 0;
});
console.log("All totals have been reset.");
}
// Attach them to window so you can call from DevTools console
window.logMethodTotals = logMethodTotals;
window.logSubTotals = logSubTotals;
window.logSubCollTotals = logSubCollTotals;
window.resetTotals = resetTotals;
////////////////////////////////////////////////////////////////
// -- Log DDP messages sent & maintain method/sub stats -- //
///////////////////////////////////////////////////////////////
if (Meteor.isClient) {
// Wrap Meteor.connection._send to intercept outgoing messages
const originalSend = Meteor.connection._send;
Meteor.connection._send = function (obj) {
// If it’s a method call
if (obj.msg === "method") {
const name = obj.method;
if (!methodStats[name]) {
methodStats[name] = { calls: 0, bytes: 0 };
}
methodStats[name].calls += 1;
// Keep track of the methodId => methodName
methodIds[obj.id] = name;
}
// If it’s a subscription
else if (obj.msg === "sub") {
const name = obj.name;
if (!subStats[name]) {
subStats[name] = { subs: 0, bytes: 0 };
}
subStats[name].subs += 1;
// Keep track of the subId => subName
subIds[obj.id] = name;
}
// Optionally handle "unsub" or anything else if desired
// else if (obj.msg === "unsub") { ... }
// Log the outgoing message
console.log("send", obj);
// Send it normally
originalSend.call(this, obj);
};
}
////////////////////////////////////////////////////////////////
// -- Log DDP messages received & accumulate byte totals -- //
///////////////////////////////////////////////////////////////
function formatBytes(bytes, decimals = 2) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
// Quick-and-dirty “size of” function for stringified messages
function memorySizeOf(obj) {
let bytes = 0;
if (obj !== null && obj !== undefined) {
switch (typeof obj) {
case 'number':
bytes += 8;
break;
case 'string':
bytes += obj.length * 2;
break;
case 'boolean':
bytes += 4;
break;
case 'object':
const objClass = Object.prototype.toString.call(obj).slice(8, -1);
if (objClass === 'Object' || objClass === 'Array') {
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
bytes += memorySizeOf(obj[key]);
}
}
} else {
bytes += obj.toString().length * 2;
}
break;
}
}
return bytes;
}
if (Meteor.isClient) {
Meteor.connection._stream.on('message', function (message) {
// Measure incoming DDP message size
const messageBytes = memorySizeOf(message);
totalBytesReceived += messageBytes;
let parsed;
try {
parsed = JSON.parse(message);
} catch (err) {
// fallback if parse fails
parsed = { raw: message };
}
// Log the incoming message
console.log("receive", parsed);
// Attribute these received bytes back to the relevant method or sub if possible:
if (parsed.msg === "result" && parsed.id) {
// This is a method result referencing a single method ID
const methodName = methodIds[parsed.id];
if (methodName && methodStats[methodName]) {
methodStats[methodName].bytes += messageBytes;
}
}
else if (parsed.msg === "updated" && Array.isArray(parsed.methods)) {
// "updated" sometimes references an array of method IDs
parsed.methods.forEach((mid) => {
const methodName = methodIds[mid];
if (methodName && methodStats[methodName]) {
methodStats[methodName].bytes += messageBytes;
}
});
}
else if (parsed.msg === "ready" && Array.isArray(parsed.subs)) {
// "ready" references an array of subscription IDs
parsed.subs.forEach((sid) => {
const subName = subIds[sid];
if (subName && subStats[subName]) {
subStats[subName].bytes += messageBytes;
}
});
}
else if (parsed.msg === "nosub" && parsed.id) {
// "nosub" references a single subscription ID
const subName = subIds[parsed.id];
if (subName && subStats[subName]) {
subStats[subName].bytes += messageBytes;
}
}
// If you wanted to track "added", "changed", "removed" by subscription, you’d have to
// inspect the DDP message to see if it includes a sub-ID (some versions of Meteor do).
// For example:
else if ((parsed.msg === "added" || parsed.msg === "changed") && parsed.collection) {
if (!subCollStats[parsed.collection]) {
subCollStats[parsed.collection] = { msgs: 0, bytes: 0 };
}
subCollStats[parsed.collection].bytes += messageBytes;
subCollStats[parsed.collection].msgs += 1;
}
// Still log the absolute bytes
console.log(`Message size received: ${formatBytes(messageBytes)}`);
console.log(`Total bytes received: ${formatBytes(totalBytesReceived)}`);
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment