Skip to content

Commit

Permalink
Merge pull request #114 from monti-apm/feat/memory-free
Browse files Browse the repository at this point in the history
feat: free memory native module
  • Loading branch information
zodern authored Mar 20, 2024
2 parents efb945a + da59554 commit a587d95
Show file tree
Hide file tree
Showing 3 changed files with 254 additions and 4 deletions.
87 changes: 86 additions & 1 deletion lib/models/system.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { KadiraModel } from './0model';
import { EventLoopMonitor } from '../event_loop_monitor.js';
import { Ntp } from '../ntp';
import os from 'os';
import fs from 'fs';
import cp from 'child_process';

export const MEMORY_ROUNDING_FACTOR = 100 * 1024; // 100kb
const EVENT_LOOP_ROUNDING_FACTOR = 500; // microseconds
Expand Down Expand Up @@ -39,14 +41,95 @@ export function SystemModel () {
this.previousCpuUsage = process.cpuUsage();
this.cpuHistory = [];
this.currentCpuUsage = 0;
this.freeMemory = os.freemem();

setInterval(() => {
this.cpuUsage();
}, 2000);

setInterval(async () => {
try {
await this.getFreeMemory();
} catch (e) {
console.log('Monti APM: failed to get memory info', e);
}
}, 2000);
}

_.extend(SystemModel.prototype, KadiraModel.prototype);

async function meminfo () {
let info = {};
const content = await new Promise((resolve, reject) => {
fs.readFile('/proc/meminfo', (error, data) => {
if (error) {
reject(error);
} else {
resolve(data);
}
});
});
let data = content.toString();
data.split(/\n/g).forEach(function (line) {
line = line.split(':');

// Ignore invalid lines, if any
if (line.length < 2) {
return;
}

// Remove parseInt call to make all values strings
info[line[0].trim()] = parseInt(line[1].trim(), 10);
});

return info;
}

SystemModel.prototype.getFreeMemory = async function () {
const isLinux = process.platform === 'linux';
const isMac = process.platform === 'darwin';
try {
if (isLinux) {
const info = await meminfo();
// in kb to bytes
this.freeMemory = 1024 * (info.MemFree + info.Buffers + info.Cached);
return true;
}
if (isMac) {
const output = await new Promise((resolve, reject) => {
cp.exec(
'vm_stat',
(error, stdout) => {
if (error) {
reject(error);
} else {
resolve(stdout);
}
});
});
const pageSizeArray = /page size of (\d*)/g.exec(output);
const pageSize = parseInt(pageSizeArray[1], 10);
const freePagesMatches = /Pages free:\s*(\d*)/g.exec(output);
const freePages = parseInt(freePagesMatches[1], 10);
const inactivePagesMatches = /Pages inactive:\s*(\d*)/g.exec(output);
const inactivePages = parseInt(inactivePagesMatches[1],10);
[pageSize, freePages, inactivePages].forEach(o => {
if (Number.isNaN(o)) {
throw new Error('Monti APM: failed to parse vm_stat');
}
});
const totalFreeMemory = pageSize * (freePages + inactivePages);
this.freeMemory = totalFreeMemory;
return true;
}
} catch (e) {
console.error('Monti APM: failed to get native memory info, falling back to default option', e);
}

this.freeMemory = os.freemem();
return false;
};

SystemModel.prototype.buildPayload = function () {
let metrics = {};
let now = Ntp._now();
Expand All @@ -60,7 +143,9 @@ SystemModel.prototype.buildPayload = function () {
metrics.memoryExternal = roundUsingFactor(memoryUsage.external, MEMORY_ROUNDING_FACTOR) / (1024 * 1024);
metrics.memoryHeapUsed = roundUsingFactor(memoryUsage.heapUsed, MEMORY_ROUNDING_FACTOR) / (1024 * 1024);
metrics.memoryHeapTotal = roundUsingFactor(memoryUsage.heapTotal, MEMORY_ROUNDING_FACTOR) / (1024 * 1024);
metrics.freeMemory = roundUsingFactor(os.freemem(), MEMORY_ROUNDING_FACTOR) / (1024 * 1024);

const freeMemory = this.freeMemory;
metrics.freeMemory = roundUsingFactor(freeMemory, MEMORY_ROUNDING_FACTOR) / (1024 * 1024);
metrics.totalMemory = roundUsingFactor(os.totalmem(), MEMORY_ROUNDING_FACTOR) / (1024 * 1024);

metrics.newSessions = this.newSessions;
Expand Down
3 changes: 2 additions & 1 deletion package.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,15 @@ let npmModules = {
parseurl: '1.3.3',
};

Npm.depends(npmModules);

Package.onUse(function (api) {
Npm.depends(npmModules);
configurePackage(api, false);
api.export(['Kadira', 'Monti']);
});

Package.onTest(function (api) {
Npm.depends(Object.assign({}, npmModules, {sinon: '6.3.5'}));
configurePackage(api, true);
api.use([
'peerlibrary:reactive-publish',
Expand Down
168 changes: 166 additions & 2 deletions tests/models/system.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import cp from 'child_process';
import fs from 'fs';
import { Meteor } from 'meteor/meteor';
import sinon from 'sinon';
import { MEMORY_ROUNDING_FACTOR, SystemModel } from '../../lib/models/system';
import { Wait } from '../_helpers/helpers';

import { Wait, releaseParts } from '../_helpers/helpers';
/**
* @flaky
*/
Expand All @@ -15,12 +17,174 @@ Tinytest.add(
let payload = model.buildPayload().systemMetrics[0];
test.isTrue(payload.memory > 0, `memory: ${payload.memory}`);
test.isTrue((payload.memory * 1024 * 1024 /* in bytes */) % MEMORY_ROUNDING_FACTOR === 0, 'memory is rounded');
test.isTrue((payload.freeMemory * 1024 * 1024 /* in bytes */) % MEMORY_ROUNDING_FACTOR === 0, 'memory is rounded');
test.isTrue(payload.freeMemory > 0, 'free memory is > 0');
test.isTrue(payload.freeMemory > 0, 'free memory is > 0');
test.isTrue(payload.pcpu >= 0, `pcpu: ${payload.pcpu}`);
test.isTrue(payload.sessions >= 0, `sessions: ${payload.sessions}`);
test.isTrue(payload.endTime >= payload.startTime + 500, `time: ${payload.endTime} - ${payload.startTime}`);
}
);

// sinon cant stub fs/cp on older node versions
if (releaseParts[0] > 1 || (releaseParts[0] === 1 && releaseParts[1] > 8) ) {
Tinytest.addAsync(
'Models - System - freeMemory',
async function (test) {
let model = new SystemModel();
/**
* MAC OS
*/
sinon.stub(process, 'platform').value('darwin');

sinon.replace(cp, 'exec', (a, callback) => {
callback(null,
`Mach Virtual Memory Statistics: (page size of 16384 bytes)
Pages free: 3293.
Pages active: 231224.
Pages inactive: 238682.
Pages speculative: 534.
Pages throttled: 0.
Pages wired down: 212821.
Pages purgeable: 15075.
"Translation faults": 3619403884.
Pages copy-on-write: 205742534.
Pages zero filled: 1133125308.
Pages reactivated: 862283057.
Pages purged: 221979774.
File-backed pages: 115729.
Anonymous pages: 354711.
Pages stored in compressor: 2284038.
Pages occupied by compressor: 452497.
Decompressions: 992611451.
Compressions: 1067810568.
Pageins: 58750181.
Pageouts: 2172799.
Swapins: 6971267.
Swapouts: 8892364.`);
});
await model.getFreeMemory();
test.isTrue(model.freeMemory === 3964518400, 'should use the file format on mac');
sinon.restore();

/**
* LINUX
*/
sinon.stub(process, 'platform').value('linux');
model = new SystemModel();
sinon.replace(fs, 'readFile', (_,callback) => {
callback(null,
{ toString: () => `MemTotal: 2097152 kB
MemFree: 2085696 kB
MemAvailable: 2085828 kB
Buffers: 0 kB
Cached: 132 kB
SwapCached: 0 kB
Active: 0 kB
Inactive: 4116 kB
Active(anon): 0 kB
Inactive(anon): 4116 kB
Active(file): 0 kB
Inactive(file): 0 kB
Unevictable: 0 kB
Mlocked: 0 kB
SwapTotal: 0 kB
SwapFree: 0 kB
Dirty: 0 kB
Writeback: 0 kB
AnonPages: 4116 kB
Mapped: 0 kB
Shmem: 0 kB
KReclaimable: 6807616 kB
Slab: 0 kB
SReclaimable: 0 kB
SUnreclaim: 0 kB
KernelStack: 36496 kB
PageTables: 48024 kB
NFS_Unstable: 0 kB
Bounce: 0 kB
WritebackTmp: 0 kB
CommitLimit: 325948112 kB
Committed_AS: 18755168 kB
VmallocTotal: 34359738367 kB
VmallocUsed: 163092 kB
VmallocChunk: 0 kB
Percpu: 525312 kB
HardwareCorrupted: 0 kB
AnonHugePages: 0 kB
ShmemHugePages: 0 kB
ShmemPmdMapped: 0 kB
FileHugePages: 0 kB
FilePmdMapped: 0 kB
HugePages_Total: 8192
HugePages_Free: 8189
HugePages_Rsvd: 61
HugePages_Surp: 0
Hugepagesize: 2048 kB
Hugetlb: 16777216 kB
DirectMap4k: 14918780 kB
DirectMap2M: 116037632 kB
DirectMap1G: 5242880 kB`});
});

await model.getFreeMemory();
console.log(model.freeMemory);
test.isTrue(model.freeMemory === 2135887872, 'should use the file format on linux');
sinon.restore();
}
);
Tinytest.addAsync(
'Models - System - freeMemory silent error',
async function (test) {
let model = new SystemModel();
/**
* MAC OS
*/
sinon.stub(process, 'platform').value('darwin');
sinon.replace(cp, 'exec', (a, callback) => {
callback(/* error */ true,null);
});
await model.getFreeMemory();
test.isTrue(model.freeMemory > 0, 'should use fallback on mac');
sinon.restore();

/**
* LINUX
*/
sinon.stub(process, 'platform').value('linux');
model = new SystemModel();
sinon.replace(fs, 'readFile', (_,callback) => {
callback(/* error */ true, null);
});

await model.getFreeMemory();
test.isTrue(model.freeMemory > 0 , 'should use fallback linux');
sinon.restore();
}
);
} else {
Tinytest.addAsync(
'Models - System - freeMemory silent error',
async function (test, done) {
const model = new SystemModel();
await model.getFreeMemory();
test.isTrue(model.freeMemory > 0 , 'should use fallback linux');
done();
});
}

if (process.platform !== 'win32') {
Tinytest.addAsync(
'Models - System - freeMemory succeed on osx/linux',
async function (test, done) {
const model = new SystemModel();
let success = await model.getFreeMemory();
test.isTrue(success);
done();
}
);
}

Tinytest.add(
'Models - System - new Sessions - count new session',
function (test) {
Expand Down

0 comments on commit a587d95

Please sign in to comment.