From 4e5b98af4cf902627061f67c589bf7b105743828 Mon Sep 17 00:00:00 2001 From: masterex1000 Date: Wed, 26 Sep 2018 17:47:01 -0600 Subject: [PATCH] Add les.js (Memory Evict) --- lib/les.js | 121 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 lib/les.js diff --git a/lib/les.js b/lib/les.js new file mode 100644 index 00000000..e6f3cb16 --- /dev/null +++ b/lib/les.js @@ -0,0 +1,121 @@ +; +(function() { + + // _ _____ ____ _ + // | | | ____/ ___| (_)___ + // | | | _| \___ \ | / __| + // | |___| |___ ___) | | \__ \ + // |_____|_____|____(_)/ |___/ + // ---------------------------- + // LES.js (Last rEcently uSed) + // ---------------------------- + // A Small, lightweight, queue-based + // Garbage Collector for Gun + // Originally By: Collin Conrad (@masterex1000) + + //NOTE: set to false is running from file in YOUR code + var USELOCALGUN = true; + + //NOTE: adds some debug messages + var DEBUG = false; + + + var Gun = (typeof window !== "undefined") ? window.Gun : (USELOCALGUN ? require('../gun') : require("gun")); + var ev = {}; + var empty = {}; + + Gun.on('opt', function(root) { + this.to.next(root); + if (root.once) + return; + if (typeof process == 'undefined') + return + var mem = process.memoryUsage; + + if (!mem) //exit because we are in the browser + return; + + //Figure out the most amount of memory we can use. TODO: make configurable? + ev.max = parseFloat(root.opt.memory || process.env.WEB_MEMORY || 512) * 0.8; + + var nodes = {}; //checks if the node already exists + var nodesArray = []; //used to easily sort everything and store info about the nodes + var memoryUpdate = 0; // last time we printed the current memory stats + + var check = function() { + ev.used = mem().rss / 1024 / 1024; //Contains the amt. of used ram in MB + setTimeout(function() { // So we can handle requests etc. before we start collecting + GC(ev.used / ev.max); // Calculate the memory ratio, and execute the garbage collector + }, 1); + } + + setInterval(check, 1000); // set the garbage collector to run every second, TODO: make configurable + + //Executed every time a node gets modifyed + root.on("put", function(e) { + var ctime = Date.now(); + var souls = Object.keys(e.put || empty); + for (var i = 0; i < souls.length; i++) { + enqueueNode(souls[i], ctime); + } + }); + + //Adds a soul the garbage collectors "freeing" queue + function enqueueNode(soul, ctime) { + if (nodes[soul] == true) { //The node already exists in the queue + var index = nodesArray.findIndex(function(e) { + return e[0] === soul; + }); + if (index == -1) { + console.err("Something happened and the node '" + soul + "' won't get garbage collection unless the value is updated agian"); + return; + } else { + nodesArray.splice(index, 1); // remove the existing ref. + nodesArray.push([soul, ctime]); // push the new instance + } + } else { + nodesArray.push([soul, ctime]); + nodes[soul] = true; + } + } + + //The main garbage collecting routine + function GC(memRatio) { + var curTime = Date.now(); // get the current time + + if (curTime - memoryUpdate >= 5000) { + console.log("|GC| %s | Current Memory Ratio: %d | Current Ram Usage %sMB | Nodes in Memory %s", new Date().toLocaleString(), round(memRatio, 2), round(ev.used, 2), Object.keys(root.graph || empty).length); + memoryUpdate = curTime; + } + + var freed = 0; + + while (nodesArray.length > 0) { + var soul = nodesArray[0][0]; + var nts = nodesArray[0][1]; + if (DEBUG) + console.log("Soul: " + soul + " | Remove Importance: " + calcRemoveImportance(nts, curTime, memRatio) + + " | Memory Ratio: " + memRatio + " | Time Existed: " + (curTime - nts) / 1000); + if (calcRemoveImportance(nodesArray[0][1], curTime, memRatio) >= 100) { + root.gun.get(nodesArray[0][0]).off(); //Remove the node + delete nodes[nodesArray[0][0]]; // remove the lookup value + nodesArray.splice(0, 1); + freed++; + } else + break; + } + if (freed > 0) + console.log("|GC| Removed %s nodes in %s seconds-----------------------------------------------------------------", freed, (Date.now() - curTime) * 0.001); + } + + //Generates a number that, after it hits a threshold, the node gets removed + function calcRemoveImportance(timestamp, ctime, memoryUsageRatio) { + var time = (ctime - timestamp) * 0.001; + return time * 10 * (memoryUsageRatio * memoryUsageRatio) + } + + function round(value, decimals) { //a basic rounding function + return Number(Math.round(value + 'e' + decimals) + 'e-' + decimals); + } + }); +}()); \ No newline at end of file