From a259b09a7ed71afba6fb93b5b08e385ee8abe011 Mon Sep 17 00:00:00 2001 From: David Kebler Date: Sat, 1 Aug 2020 07:54:34 -0700 Subject: [PATCH] working memory tracking utility --- .eslintrc.js | 37 +++++++++++++ .gitignore | 2 + .npmignore | 5 ++ examples/example.js | 22 ++++++++ nodemon.json | 3 + package.json | 33 +++++++++++ readme.md | 18 ++++++ src/memory.js | 131 ++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 251 insertions(+) create mode 100644 .eslintrc.js create mode 100644 .gitignore create mode 100644 .npmignore create mode 100644 examples/example.js create mode 100644 nodemon.json create mode 100644 package.json create mode 100644 readme.md create mode 100644 src/memory.js diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..49bac18 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,37 @@ +module.exports = { + "ecmaFeatures": { + "modules": true, + "spread" : true, + "restParams" : true + }, + // "plugins": [ + // "unicorn" + // ], + "env": { + "es6": true, + "node": true, + "mocha": true + }, + "parserOptions": { + "ecmaVersion": 2017, + "sourceType": "module" + }, + "extends": "eslint:recommended", + "rules": { + "indent": [ + "error", + 2 + ], + // "unicorn/no-array-instanceof": "error", + "no-console": 0, + "semi": ["error", "never"], + "linebreak-style": [ + "error", + "unix" + ], + "quotes": [ + "error", + "single" + ] + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e61051f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/node_modules/ +/coverage/ diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..2f680a8 --- /dev/null +++ b/.npmignore @@ -0,0 +1,5 @@ +tests/ +test/ +*.test.js +testing/ +examples/ diff --git a/examples/example.js b/examples/example.js new file mode 100644 index 0000000..2977577 --- /dev/null +++ b/examples/example.js @@ -0,0 +1,22 @@ +import Memory from '../src/memory.js' + +const mem = new Memory({db:0}) + +// let text +// mem.watch(console.log) + +mem.watch() + +setInterval(()=> + mem.emit('tick') + ,5000) + +mem.on('tick',()=> { + mem.log({msg:'testing'}) + let text + for (let i = 0; i < 1000000; i++) { + text += 'a' + } + mem.text = text + console.log('memory that text is using', mem.sizeof(mem.text),mem.sizeof(text), 'M') +}) diff --git a/nodemon.json b/nodemon.json new file mode 100644 index 0000000..f21684b --- /dev/null +++ b/nodemon.json @@ -0,0 +1,3 @@ +{ + "ignore":["examples/*.json"] +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..7a36112 --- /dev/null +++ b/package.json @@ -0,0 +1,33 @@ +{ + "name": "@uci-utils/memory", + "version": "0.1.4", + "description": "memory logger and watcher", + "main": "src/memory.js", + "scripts": { + "example": "node -r esm examples/example", + "example:dev": "./node_modules/.bin/nodemon -r esm examples/example" + }, + "author": "David Kebler", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/uCOMmandIt/.git" + }, + "keywords": [ + "node.js" + ], + "bugs": { + "url": "https://github.com/uCOMmandIt/uci-utils/issues" + }, + "homepage": "https://github.com/uCOMmandIt/uci-utils#readme", + "dependencies": { + "debounce-fn": "^4.0.0", + "exact-math": "^2.2.0", + "gc-stats": "^1.4.0", + "object-sizeof": "^1.6.1" + }, + "devDependencies": { + "esm": "^3.2.25", + "nodemon": "^2.0.4" + } +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..270b475 --- /dev/null +++ b/readme.md @@ -0,0 +1,18 @@ +# uCOMmandIt Reactive Class + +A Great Class to Extend! It has built in support to make reactive any/all class properties and their nested leaves. + +## Why? + +To have reactivity similar to vue and react but in any backend object? +This allow a backend app to maintain app settings and allow "clients" to get updated values when the app changes state. Essentially will create a 'mini' state event bus. + +## Features + +Will both emit and allow subscriptions to any property 'path' that has been made reactive. + +Allows custom registration of unlimited state change event hooks. + +Allows additional rxjs operators for the pipe the observer for each property. + +See Examples folder diff --git a/src/memory.js b/src/memory.js new file mode 100644 index 0000000..e3f2bc9 --- /dev/null +++ b/src/memory.js @@ -0,0 +1,131 @@ +import EventEmitter from 'events' +import gc from 'gc-stats' +import _ from 'exact-math' +import dbfn from 'debounce-fn' +import sizeof from 'object-sizeof' +import { getHeapStatistics } from 'v8' + +const STATS = +{ + total_heap_size: 'byte', + total_heap_size_executable: 'byte', + total_physical_size: 'byte', + total_available_size: 'byte', + used_heap_size: 'byte', + heap_size_limit: 'byte', + malloced_memory: 'byte', + peak_malloced_memory: 'byte', + does_zap_garbage: 'boolean', + number_of_native_contexts: 'val', + number_of_detached_contexts: 'val' +} + +const PROPS = Object.keys(STATS) + +class Memory extends EventEmitter { + constructor(opts={}) { + super() + this._props = Object.keys(this.stats) + this._diff = {} + PROPS.forEach(prop=>this._diff[prop]={}) + this._current = this.get() + this.db = opts.db || 3000 + this.reset() + this.last = this.first + this.gc = gc() + } + + get stats () { + return getHeapStatistics() + } + + sizeof (obj, u='M') { + return mb(sizeof(obj)) + } + + reset() { + this.first=this.get() + } + + get() { + const mem = this.stats + PROPS.forEach(prop => { + if (STATS[prop] === 'byte') mem[prop] = mb(mem[prop],2) + }) + return mem + } + + snapshot() { + this.last=Object.assign({},this._current) + this._current=this.get() + this.remaining = _.sub(this._current.heap_size_limit,this._current.used_heap_size) + PROPS.forEach(prop => { + if (STATS[prop] !== 'boolean') { + this._diff[prop].last = _.sub(this._current[prop],this.last[prop]) + this._diff[prop].overall=_.sub(this._current[prop],this.first[prop]) + } + }) + return { remaining:this.remaining, first:this.first, last:this.last, current:this._current, diff:this._diff } + } + + log (opts) { + this.snapshot() + console.log(`============= ${opts.msg||''} =====================`,new Date().toLocaleString()) + console.log(`remaining Heap: ${this.remaining} mb`) + console.log(this._log('used_heap_size')) + console.log(this._log('number_of_native_contexts')) + console.log(`============= ${opts.msg||''} =====================`) + } + + _log (prop) { + return `${prop} > \ +diff:${this._diff[prop].overall}, \ +lastdiff:${this._diff[prop].last}, \ +current:${this._current[prop]}, \ +last:${this.last[prop]}, \ +first:${this.first[prop]}, \ +` + } + + // totalHeapSize: 17911808, + // totalHeapExecutableSize: 573440, + // usedHeapSize: 13410032, + // heapSizeLimit: 2197815296, + // totalPhysicalSize: 17675040, + // totalAvailableSize: 2184093576, + // mallocedMemory: 8192, + // peakMallocedMemory: 897248, + // numberOfNativeContexts: 3, + // numberOfDetachedContexts: 0 + + watch (handler) { + gc().on('stats', dbfn( + stats => { + const diff = _.sub(stats.after.heapSizeLimit,stats.after.usedHeapSize) + if (stats.gctype > 0) { + if (!handler) { + console.log(stats.gctype,'----------- Heap at GC ------------',new Date().toLocaleString()) + console.log(`Heap> \ +remaining: ${mb(diff)} mb \ +before:${mb(stats.before.usedHeapSize)}, \ +after:${mb(stats.after.usedHeapSize)} mb, \ +diff:${mb(stats.diff.usedHeapSize)} mb \ +`) + console.log('-----------------------') + } + else handler(stats) + }}, + {wait:this.db} + ) + )} + + +} // end class + +function mb (val,p=2) { + // console.log(val,val / 1024 / 1024, _.round(val / 1024 / 1024, -p) ) + return _.round(val / 1024 / 1024, -p) +} + +export default Memory +export { Memory }