Last active
February 14, 2025 16:20
-
-
Save suzmas/9206cf14f3d2384a8e709fe97b371417 to your computer and use it in GitHub Desktop.
logging stuff for meteor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//////////////////////////////////////////////////////////////// | |
// -- 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