les.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. ;
  2. (function() {
  3. // _ _____ ____ _
  4. // | | | ____/ ___| (_)___
  5. // | | | _| \___ \ | / __|
  6. // | |___| |___ ___) | | \__ \
  7. // |_____|_____|____(_)/ |___/
  8. // ----------------------------
  9. // LES.js (Last rEcently uSed)
  10. // ----------------------------
  11. // A Small, lightweight, queue-based
  12. // Garbage Collector for Gun
  13. // Originally By: Collin Conrad (@masterex1000)
  14. /**
  15. *
  16. * Usage: require the file in your application
  17. *
  18. * Gun Params: these are passed to the new gun constructor
  19. *
  20. * - gc_enable : enables the gc, good if you are running multiple instances of gun, etc... def. true
  21. * - gc_delay : sets the amount of time between attempted garbage collections in milliseconds
  22. * - gc_info_enable : Enables or Disables the info printout
  23. * - gc_info : sets the ~ amount of time between info messages
  24. * this is checked everytime the gc is ran
  25. * - gc_info_mini : this will use a smaller, less user friendly info printout
  26. * - gc_importance_func : This will be the function used for finding the importance of a potental collect
  27. * takes the form of func(timestamp, ctime, memoryUsageRatio) {return val}
  28. * Collects when returned value is 100
  29. */
  30. //NOTE: set to false to use require for getting gun DEFUALT: false
  31. var USELOCALGUN = false;
  32. //NOTE: adds some debug messages DEFUALT: false
  33. var DEBUG = false;
  34. if(!(typeof window !== "undefined") && USELOCALGUN)
  35. console.log("NOTE: You currently have LES.js set to use the 'local' file version of gun, This might crash if set wrong!");
  36. var Gun = (typeof window !== "undefined") ? window.Gun : (USELOCALGUN ? require('../gun') : require("gun"));
  37. //Removes a node from the garbage collection until next write
  38. Gun.chain.gcDequeue = function() {
  39. //console.log(this._.root.dequeueNode);
  40. if(this._.root.dequeueNode) { // check that we actually have the dequeue command on this node
  41. let ctx = this;
  42. this.get(function (soul) {
  43. ctx._.root.dequeueNode(soul);
  44. }, true);
  45. }
  46. }
  47. //Puts node at the front for garbage collection, NOTE: only collects when it is hit it's time
  48. Gun.chain.gcCollect = function() {
  49. if(this._.root.collectNode) { // check that we actually have the dequeue command on this node
  50. let ctx = this;
  51. this.get(function (soul) {
  52. ctx._.root.collectNode(soul);
  53. }, true);
  54. }
  55. }
  56. Gun.on('opt', function(root) {
  57. //Setup various options
  58. const gc_enable = root.opt.gc_enable ? root.opt.gc_enable : true;
  59. const gc_delay = root.opt.gc_delay ? root.opt.gc_delay : 1000;
  60. const gc_info_enable = ("gc_info_enable" in root.opt) ? root.opt.gc_info_enable : true;
  61. const gc_info = root.opt.gc_info ? root.opt.gc_info : 5000;
  62. const gc_info_mini = root.opt.gc_info_mini ? root.opt.gc_info_mini : false;
  63. //This is long, but it works well
  64. const calcRemoveImportance = root.opt.gc_importance_func ? root.opt.gc_importance_func : function (timestamp, ctime, memoryUsageRatio) {
  65. var time = (ctime - timestamp) * 0.001;
  66. return time * 10 * (memoryUsageRatio * memoryUsageRatio);
  67. }
  68. if(DEBUG) console.log(root.opt);
  69. this.to.next(root);
  70. if (root.once)
  71. return;
  72. if (typeof process == 'undefined')
  73. return
  74. var mem = process.memoryUsage;
  75. if(!gc_enable) // exit because the gc is disabled
  76. return;
  77. if (!mem) //exit because we are in the browser
  78. return;
  79. var ev = {}; //stores the environment
  80. var empty = {}; //An empty list used to prevent crashes
  81. //Figure out the most amount of memory we can use. TODO: make configurable?
  82. ev.max = parseFloat(root.opt.memory || process.env.WEB_MEMORY || 512) * 0.8;
  83. var nodes = {}; //checks if the node already exists
  84. var nodesArray = []; //used to easily sort everything and store info about the nodes
  85. var memoryUpdate = 0; // last time we printed the current memory stats
  86. root.dequeueNode = (soul) => { //forward the call to our gc
  87. dequeueNode(soul);
  88. }
  89. root.collectNode = (soul) => { //forward the call to our gc
  90. collectNode(soul);
  91. }
  92. var check = function() {
  93. ev.used = mem().rss / 1024 / 1024; //Contains the amt. of used ram in MB
  94. setTimeout(function() { // So we can handle requests etc. before we start collecting
  95. GC(ev.used / ev.max); // Calculate the memory ratio, and execute the garbage collector
  96. //GC(0.99);
  97. }, 1);
  98. }
  99. setInterval(check, gc_delay); // set the garbage collector to run every second
  100. //Executed every time a node gets modified
  101. root.on("put", function(e) {
  102. this.to.next(e);
  103. var ctime = Date.now();
  104. var souls = Object.keys(e.put || empty); // get all of the nodes in the update
  105. for (var i = 0; i < souls.length; i++) { // iterate over them and add them
  106. enqueueNode(souls[i], ctime);
  107. }
  108. });
  109. //Adds a soul the garbage collectors "freeing" queue
  110. function enqueueNode(soul, ctime) {
  111. if (nodes[soul] == true) { //The node already exists in the queue
  112. var index = nodesArray.findIndex(function(e) {
  113. return e[0] === soul;
  114. });
  115. if (index == -1) {
  116. console.error("Something happened and the node '" + soul + "' won't get garbage collection unless the value is updated again");
  117. return;
  118. } else {
  119. nodesArray.splice(index, 1); // remove the existing ref. faster than dequeue
  120. nodesArray.push([soul, ctime]); // push the new instance
  121. }
  122. } else {
  123. nodesArray.push([soul, ctime]);
  124. nodes[soul] = true;
  125. }
  126. }
  127. //Removes a node from the queue
  128. function dequeueNode(soul) {
  129. if (nodes[soul] == true) { //The node already exists in the queue
  130. var index = nodesArray.findIndex(function(e) {
  131. return e[0] === soul;
  132. });
  133. if (index != -1) {
  134. //nodesArray.splice(index, 1); // remove the existing ref.
  135. nodesArray.shift();
  136. nodes[soul] = false; // store that we no longer have that node in the queue
  137. }
  138. }
  139. }
  140. //Moves a node to the start of the queue
  141. function collectNode(soul) {
  142. if (nodes[soul] == true) { //The node already exists in the queue
  143. var index = nodesArray.findIndex(function(e) {
  144. return e[0] === soul;
  145. });
  146. if (index != -1) {
  147. //nodesArray.splice(index, 1); // remove the existing ref.
  148. nodesArray.shift(); // WAY faster than splice
  149. }
  150. nodesArray.unshift([soul, nodesArray[0][1]]); // create a new node with the next nodes time stamp
  151. nodes[soul] = true; // store that we no longer have that node in the queue
  152. }
  153. }
  154. //The main garbage collecting routine
  155. function GC(memRatio) {
  156. var curTime = Date.now(); // get the current time
  157. if (gc_info_enable && curTime - memoryUpdate >= gc_info) { // check if we need to print info
  158. if(!gc_info_mini)
  159. 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);
  160. else
  161. console.log("|GC| %s, Mem Ratio %d, Ram %sMB, Nodes in mem %s, Tracked Nodes %s", new Date().toLocaleString(), round(memRatio, 2), round(ev.used, 2), Object.keys(root.graph || empty).length, nodesArray.length);
  162. memoryUpdate = curTime; // reset the last update time
  163. }
  164. var freed = 0; // Just a nice performance counter
  165. while (nodesArray.length > 0) { // iterate over all of our nodes
  166. var soul = nodesArray[0][0];
  167. var nts = nodesArray[0][1];
  168. if (DEBUG)
  169. console.log("Soul: " + soul + " | Remove Importance: " + calcRemoveImportance(nts, curTime, memRatio) +
  170. " | Memory Ratio: " + memRatio + " | Time Existed: " + (curTime - nts) / 1000);
  171. if (calcRemoveImportance(nodesArray[0][1], curTime, memRatio) >= 100) {
  172. root.gun.get(nodesArray[0][0]).off(); //Remove the node
  173. delete nodes[nodesArray[0][0]]; // remove the lookup value
  174. //nodesArray.splice(0, 1);
  175. nodesArray.shift();
  176. freed++; // add one to our perf counter
  177. } else
  178. break; // Break out of the loop because we don't have any more nodes to free
  179. }
  180. if (freed > 0)
  181. console.log("|GC| Removed %s nodes in %s seconds-----------------------------------------------------------------", freed, (Date.now() - curTime) * 0.001);
  182. }
  183. function round(value, decimals) { //a basic rounding function
  184. return Number(Math.round(value + 'e' + decimals) + 'e-' + decimals);
  185. }
  186. });
  187. }());