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