diff --git a/bin/tessel-2.js b/bin/tessel-2.js index 913362b0..c494e004 100755 --- a/bin/tessel-2.js +++ b/bin/tessel-2.js @@ -171,6 +171,11 @@ makeCommand('wifi') }) .help('Configure the wireless connection'); +makeCommand('memory') + .callback(function(opts) { + callControllerWith('getMemoryInfo', opts); + }); + parser.command('key') .option('method', { position: 1, diff --git a/lib/controller.js b/lib/controller.js index d412384c..37e62469 100644 --- a/lib/controller.js +++ b/lib/controller.js @@ -616,6 +616,20 @@ controller.getWifiInfo = function(opts) { }); }; +controller.getMemoryInfo = function(opts) { + opts.authorized = true; + return controller.standardTesselCommand(opts, function(tessel) { + return tessel.memoryInfo() + .then(function(meminfo) { + Object.keys(meminfo).forEach(function(key) { + logs.basic(sprintf('\t%s\t%d kB', key, meminfo[key] / 1000)); + }); + + return controller.closeTesselConnections([tessel]); + }); + }); +}; + controller.connectToNetwork = function(opts) { opts.authorized = true; return controller.standardTesselCommand(opts, function(tessel) { diff --git a/lib/tessel/commands.js b/lib/tessel/commands.js index 537058dd..d530e79f 100644 --- a/lib/tessel/commands.js +++ b/lib/tessel/commands.js @@ -85,3 +85,6 @@ module.exports.callTesselMDNS = function(action) { module.exports.sysupgrade = function(path) { return ['sysupgrade', path]; }; +module.exports.getMemoryInfo = function() { + return ['cat', '/proc/meminfo']; +}; diff --git a/lib/tessel/deploy.js b/lib/tessel/deploy.js index 7a556674..8a59e8a7 100644 --- a/lib/tessel/deploy.js +++ b/lib/tessel/deploy.js @@ -13,6 +13,90 @@ var NODE_PUSH_SCRIPT = __dirname + '/../../resources/start_node_script.sh'; // exporting those definitions for testing. var actions = {}; +var pattern = /(.*):(?:\s+)([0-9]{1,9})/; +var replacements = { + '(anon)': '_anon', + '(file)': '_file', +}; + +function transformKey(value) { + return Object.keys(replacements).reduce(function(value, key) { + return value.replace(key, replacements[key]); + }, value); +} + +/* + Get the results of `cat /proc/meminfo` and create an object with the data. + + The produced object will look approximately like the following, where only the + values will vary: + + { + MemTotal: 61488000, + MemFree: 28396000, + MemAvailable: 42852000, + Buffers: 4224000, + Cached: 11860000, + SwapCached: 0, + Active: 11200000, + Inactive: 8172000, + Active_anon: 3320000, + Inactive_anon: 52000, + Active_file: 7880000, + Inactive_file: 8120000, + Unevictable: 0, + Mlocked: 0, + SwapTotal: 0, + SwapFree: 0, + Dirty: 0, + Writeback: 0, + AnonPages: 3304000, + Mapped: 5260000, + Shmem: 84000, + Slab: 7480000, + SReclaimable: 1836000, + SUnreclaim: 5644000, + KernelStack: 352000, + PageTables: 388000, + NFS_Unstable: 0, + Bounce: 0, + WritebackTmp: 0, + CommitLimit: 30744000, + Committed_AS: 7696000, + VmallocTotal: 1048372000, + VmallocUsed: 1320000, + VmallocChunk: 1040404000 + } + + Note that the values are in BYTES! +*/ + +Tessel.prototype.memoryInfo = function() { + var self = this; + return new Promise(function(resolve, reject) { + return actions.execRemoteCommand(self, 'getMemoryInfo') + .then(function(response) { + if (!response || !response.length) { + return reject('Could not read device memory information.'); + } + + var meminfo = response.split('\n').reduce(function(result, row) { + var parts = row.match(pattern); + var key, value; + + if (parts && parts.length) { + key = transformKey(parts[1]); + value = parseInt(parts[2], 10) * 1000; + result[key] = value; + } + return result; + }, {}); + + resolve(meminfo); + }) + .catch(reject); + }); +}; /* Run a script on a Tessel param opts: unused so far diff --git a/test/unit/deploy.js b/test/unit/deploy.js index 97a3e72f..9721079b 100644 --- a/test/unit/deploy.js +++ b/test/unit/deploy.js @@ -9,12 +9,121 @@ var mkdirp = require('mkdirp'); var path = require('path'); var rimraf = require('rimraf'); var Ignore = require('fstream-ignore'); +var meminfo = fs.readFileSync('test/unit/fixtures/proc-meminfo', 'utf8'); var deployFolder = path.join(__dirname, 'tmp'); var deployFile = path.join(deployFolder, 'app.js'); var codeContents = 'console.log("testing deploy");'; var reference = new Buffer(codeContents); var sandbox = sinon.sandbox.create(); +exports['Tessel.prototype.memoryInfo'] = { + setUp: function(done) { + this.execRemoteCommand = sandbox.stub(deploy, 'execRemoteCommand', function() { + return new Promise(function(resolve) { + resolve(meminfo); + }); + }); + + this.logsWarn = sandbox.stub(logs, 'warn', function() {}); + this.logsInfo = sandbox.stub(logs, 'info', function() {}); + + this.tessel = TesselSimulator(); + + // This is what the result of processing output from + // `cat /proc/meminfo` must look like. + this.expect = { + MemTotal: 61488000, + MemFree: 28660000, + MemAvailable: 43112000, + Buffers: 4224000, + Cached: 11860000, + SwapCached: 0, + Active: 10936000, + Inactive: 8244000, + Active_anon: 3128000, + Inactive_anon: 52000, + Active_file: 7808000, + Inactive_file: 8192000, + Unevictable: 0, + Mlocked: 0, + SwapTotal: 0, + SwapFree: 0, + Dirty: 0, + Writeback: 0, + AnonPages: 3112000, + Mapped: 5260000, + Shmem: 84000, + Slab: 7460000, + SReclaimable: 1832000, + SUnreclaim: 5628000, + KernelStack: 352000, + PageTables: 348000, + NFS_Unstable: 0, + Bounce: 0, + WritebackTmp: 0, + CommitLimit: 30744000, + Committed_AS: 7072000, + VmallocTotal: 1048372000, + VmallocUsed: 1320000, + VmallocChunk: 1040404000 + }; + + done(); + }, + + tearDown: function(done) { + this.tessel.mockClose(); + sandbox.restore(); + done(); + }, + + meminfo: function(test) { + test.expect(4); + this.tessel.memoryInfo().then(function(memory) { + test.equal(this.execRemoteCommand.callCount, 1); + test.equal(this.execRemoteCommand.lastCall.args[0], this.tessel); + test.equal(this.execRemoteCommand.lastCall.args[1], 'getMemoryInfo'); + test.deepEqual(memory, this.expect); + test.done(); + }.bind(this)); + }, + + failureNoResponse: function(test) { + test.expect(1); + + this.execRemoteCommand.restore(); + + this.execRemoteCommand = sandbox.stub(deploy, 'execRemoteCommand', function() { + return new Promise(function(resolve) { + resolve(); + }); + }); + + this.tessel.memoryInfo().catch(function(error) { + test.equal(error, 'Could not read device memory information.'); + test.done(); + }.bind(this)); + }, + + failureEmptyResponse: function(test) { + test.expect(1); + + this.execRemoteCommand.restore(); + + this.execRemoteCommand = sandbox.stub(deploy, 'execRemoteCommand', function() { + return new Promise(function(resolve) { + resolve(''); + }); + }); + + this.tessel.memoryInfo().catch(function(error) { + test.equal(error, 'Could not read device memory information.'); + test.done(); + }.bind(this)); + }, + +}; + exports['Tessel.prototype.deployScript'] = { setUp: function(done) { this.deployScript = sandbox.spy(Tessel.prototype, 'deployScript'); diff --git a/test/unit/fixtures/proc-meminfo b/test/unit/fixtures/proc-meminfo new file mode 100644 index 00000000..be80c7d5 --- /dev/null +++ b/test/unit/fixtures/proc-meminfo @@ -0,0 +1,34 @@ +MemTotal: 61488 kB +MemFree: 28660 kB +MemAvailable: 43112 kB +Buffers: 4224 kB +Cached: 11860 kB +SwapCached: 0 kB +Active: 10936 kB +Inactive: 8244 kB +Active(anon): 3128 kB +Inactive(anon): 52 kB +Active(file): 7808 kB +Inactive(file): 8192 kB +Unevictable: 0 kB +Mlocked: 0 kB +SwapTotal: 0 kB +SwapFree: 0 kB +Dirty: 0 kB +Writeback: 0 kB +AnonPages: 3112 kB +Mapped: 5260 kB +Shmem: 84 kB +Slab: 7460 kB +SReclaimable: 1832 kB +SUnreclaim: 5628 kB +KernelStack: 352 kB +PageTables: 348 kB +NFS_Unstable: 0 kB +Bounce: 0 kB +WritebackTmp: 0 kB +CommitLimit: 30744 kB +Committed_AS: 7072 kB +VmallocTotal: 1048372 kB +VmallocUsed: 1320 kB +VmallocChunk: 1040404 kB