@dra2020/baseclient 1.0.13 → 1.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +37 -0
- package/dist/all/all.d.ts +36 -0
- package/dist/all/allclient.d.ts +18 -0
- package/dist/base.js +33010 -0
- package/dist/base.js.map +1 -0
- package/dist/baseclient.js +8991 -0
- package/dist/baseclient.js.map +1 -0
- package/dist/context/all.d.ts +1 -0
- package/dist/context/context.d.ts +13 -0
- package/dist/dbabstract/all.d.ts +1 -0
- package/dist/dbabstract/db.d.ts +83 -0
- package/dist/dbdynamo/all.d.ts +1 -0
- package/dist/dbdynamo/dbdynamo.d.ts +190 -0
- package/dist/filterexpr/all.d.ts +1 -0
- package/dist/filterexpr/filterexpr.d.ts +64 -0
- package/dist/fsm/all.d.ts +1 -0
- package/dist/fsm/fsm.d.ts +118 -0
- package/dist/fsmfile/all.d.ts +1 -0
- package/dist/fsmfile/fsmfile.d.ts +47 -0
- package/dist/jsonstream/all.d.ts +1 -0
- package/dist/jsonstream/jsonstream.d.ts +130 -0
- package/dist/lambda/all.d.ts +1 -0
- package/dist/lambda/env.d.ts +10 -0
- package/dist/lambda/lambda.d.ts +18 -0
- package/dist/logabstract/all.d.ts +1 -0
- package/dist/logabstract/log.d.ts +26 -0
- package/dist/logclient/all.d.ts +1 -0
- package/dist/logclient/log.d.ts +6 -0
- package/dist/logserver/all.d.ts +5 -0
- package/dist/logserver/log.d.ts +11 -0
- package/dist/logserver/logaccum.d.ts +154 -0
- package/dist/logserver/logblob.d.ts +24 -0
- package/dist/logserver/logconcat.d.ts +55 -0
- package/dist/logserver/logkey.d.ts +28 -0
- package/dist/memsqs/all.d.ts +4 -0
- package/dist/memsqs/client.d.ts +13 -0
- package/dist/memsqs/loopback.d.ts +11 -0
- package/dist/memsqs/orderedlist.d.ts +19 -0
- package/dist/memsqs/queue.d.ts +84 -0
- package/dist/memsqs/server.d.ts +37 -0
- package/dist/ot-editutil/all.d.ts +2 -0
- package/dist/ot-editutil/oteditutil.d.ts +14 -0
- package/dist/ot-editutil/otmaputil.d.ts +21 -0
- package/dist/ot-js/all.d.ts +9 -0
- package/dist/ot-js/otarray.d.ts +111 -0
- package/dist/ot-js/otclientengine.d.ts +38 -0
- package/dist/ot-js/otcomposite.d.ts +37 -0
- package/dist/ot-js/otcounter.d.ts +17 -0
- package/dist/ot-js/otengine.d.ts +22 -0
- package/dist/ot-js/otmap.d.ts +19 -0
- package/dist/ot-js/otserverengine.d.ts +38 -0
- package/dist/ot-js/otsession.d.ts +111 -0
- package/dist/ot-js/ottypes.d.ts +29 -0
- package/dist/poly/all.d.ts +15 -0
- package/dist/poly/blend.d.ts +1 -0
- package/dist/poly/boundbox.d.ts +16 -0
- package/dist/poly/cartesian.d.ts +5 -0
- package/dist/poly/graham-scan.d.ts +8 -0
- package/dist/poly/hash.d.ts +1 -0
- package/dist/poly/matrix.d.ts +24 -0
- package/dist/poly/minbound.d.ts +1 -0
- package/dist/poly/poly.d.ts +52 -0
- package/dist/poly/polybin.d.ts +5 -0
- package/dist/poly/polylabel.d.ts +7 -0
- package/dist/poly/polypack.d.ts +30 -0
- package/dist/poly/polyround.d.ts +1 -0
- package/dist/poly/polysimplify.d.ts +1 -0
- package/dist/poly/quad.d.ts +48 -0
- package/dist/poly/selfintersect.d.ts +1 -0
- package/dist/poly/shamos.d.ts +1 -0
- package/dist/poly/simplify.d.ts +2 -0
- package/dist/poly/topo.d.ts +46 -0
- package/dist/poly/union.d.ts +48 -0
- package/dist/storage/all.d.ts +4 -0
- package/dist/storage/datablob.d.ts +9 -0
- package/dist/storage/env.d.ts +10 -0
- package/dist/storage/splitsblob.d.ts +13 -0
- package/dist/storage/storage.d.ts +166 -0
- package/dist/storages3/all.d.ts +1 -0
- package/dist/storages3/s3.d.ts +62 -0
- package/dist/util/all.d.ts +5 -0
- package/dist/util/bintrie.d.ts +93 -0
- package/dist/util/countedhash.d.ts +19 -0
- package/dist/util/gradient.d.ts +15 -0
- package/dist/util/indexedarray.d.ts +15 -0
- package/dist/util/util.d.ts +68 -0
- package/docs/context.md +2 -0
- package/docs/dbabstract.md +2 -0
- package/docs/dbdynamo.md +2 -0
- package/docs/fsm.md +243 -0
- package/docs/fsmfile.md +2 -0
- package/docs/jsonstream.md +44 -0
- package/docs/lambda.md +2 -0
- package/docs/logabstract.md +2 -0
- package/docs/logclient.md +2 -0
- package/docs/logserver.md +2 -0
- package/docs/ot-editutil.md +2 -0
- package/docs/ot-js.md +95 -0
- package/docs/poly.md +103 -0
- package/docs/storage.md +2 -0
- package/docs/storages3.md +2 -0
- package/docs/util.md +2 -0
- package/lib/all/all.ts +41 -0
- package/lib/all/allclient.ts +19 -0
- package/lib/context/all.ts +1 -0
- package/lib/context/context.ts +82 -0
- package/lib/dbabstract/all.ts +1 -0
- package/lib/dbabstract/db.ts +246 -0
- package/lib/dbdynamo/all.ts +1 -0
- package/lib/dbdynamo/dbdynamo.ts +1551 -0
- package/lib/filterexpr/all.ts +1 -0
- package/lib/filterexpr/filterexpr.ts +625 -0
- package/lib/fsm/all.ts +1 -0
- package/lib/fsm/fsm.ts +549 -0
- package/lib/fsmfile/all.ts +1 -0
- package/lib/fsmfile/fsmfile.ts +236 -0
- package/lib/jsonstream/all.ts +1 -0
- package/lib/jsonstream/jsonstream.ts +940 -0
- package/lib/lambda/all.ts +1 -0
- package/lib/lambda/env.ts +13 -0
- package/lib/lambda/lambda.ts +120 -0
- package/lib/logabstract/all.ts +1 -0
- package/lib/logabstract/log.ts +55 -0
- package/lib/logclient/all.ts +1 -0
- package/lib/logclient/log.ts +105 -0
- package/lib/logserver/all.ts +5 -0
- package/lib/logserver/log.ts +565 -0
- package/lib/logserver/logaccum.ts +1445 -0
- package/lib/logserver/logblob.ts +84 -0
- package/lib/logserver/logconcat.ts +313 -0
- package/lib/logserver/logkey.ts +125 -0
- package/lib/memsqs/all.ts +4 -0
- package/lib/memsqs/client.ts +268 -0
- package/lib/memsqs/loopback.ts +64 -0
- package/lib/memsqs/orderedlist.ts +74 -0
- package/lib/memsqs/queue.ts +395 -0
- package/lib/memsqs/server.ts +262 -0
- package/lib/ot-editutil/all.ts +2 -0
- package/lib/ot-editutil/oteditutil.ts +180 -0
- package/lib/ot-editutil/otmaputil.ts +209 -0
- package/lib/ot-js/all.ts +9 -0
- package/lib/ot-js/otarray.ts +1168 -0
- package/lib/ot-js/otclientengine.ts +327 -0
- package/lib/ot-js/otcomposite.ts +247 -0
- package/lib/ot-js/otcounter.ts +145 -0
- package/lib/ot-js/otengine.ts +71 -0
- package/lib/ot-js/otmap.ts +144 -0
- package/lib/ot-js/otserverengine.ts +329 -0
- package/lib/ot-js/otsession.ts +199 -0
- package/lib/ot-js/ottypes.ts +98 -0
- package/lib/poly/all.ts +15 -0
- package/lib/poly/blend.ts +27 -0
- package/lib/poly/boundbox.ts +102 -0
- package/lib/poly/cartesian.ts +130 -0
- package/lib/poly/graham-scan.ts +401 -0
- package/lib/poly/hash.ts +15 -0
- package/lib/poly/matrix.ts +309 -0
- package/lib/poly/minbound.ts +211 -0
- package/lib/poly/poly.ts +767 -0
- package/lib/poly/polybin.ts +218 -0
- package/lib/poly/polylabel.ts +204 -0
- package/lib/poly/polypack.ts +458 -0
- package/lib/poly/polyround.ts +30 -0
- package/lib/poly/polysimplify.ts +24 -0
- package/lib/poly/quad.ts +272 -0
- package/lib/poly/selfintersect.ts +87 -0
- package/lib/poly/shamos.ts +297 -0
- package/lib/poly/simplify.ts +119 -0
- package/lib/poly/topo.ts +525 -0
- package/lib/poly/union.ts +371 -0
- package/lib/storage/all.ts +4 -0
- package/lib/storage/datablob.ts +36 -0
- package/lib/storage/env.ts +14 -0
- package/lib/storage/splitsblob.ts +63 -0
- package/lib/storage/storage.ts +604 -0
- package/lib/storages3/all.ts +1 -0
- package/lib/storages3/s3.ts +576 -0
- package/lib/util/all.ts +5 -0
- package/lib/util/bintrie.ts +603 -0
- package/lib/util/countedhash.ts +83 -0
- package/lib/util/gradient.ts +108 -0
- package/lib/util/indexedarray.ts +80 -0
- package/lib/util/util.ts +695 -0
- package/package.json +8 -8
|
@@ -0,0 +1,1445 @@
|
|
|
1
|
+
import * as stream from 'stream';
|
|
2
|
+
import * as zlib from 'zlib';
|
|
3
|
+
|
|
4
|
+
// Shared libraries
|
|
5
|
+
import * as Util from '../util/all';
|
|
6
|
+
import * as Context from '../context/all';
|
|
7
|
+
import * as LogAbstract from '../logabstract/all';
|
|
8
|
+
import * as FSM from '../fsm/all';
|
|
9
|
+
import * as Storage from '../storage/all';
|
|
10
|
+
|
|
11
|
+
import { LogBlob } from './logblob';
|
|
12
|
+
import { LogKey, dateToKey, dateToMonthKey, dateToYearKey } from './logkey';
|
|
13
|
+
|
|
14
|
+
export interface Environment
|
|
15
|
+
{
|
|
16
|
+
context: Context.IContext;
|
|
17
|
+
log: LogAbstract.ILog;
|
|
18
|
+
fsmManager: FSM.FsmManager;
|
|
19
|
+
storageManager: Storage.StorageManager;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface AccumOptions
|
|
23
|
+
{
|
|
24
|
+
onlyAggregateClosed?: boolean;
|
|
25
|
+
}
|
|
26
|
+
const DefaultOptions = { onlyAggregateClosed: false };
|
|
27
|
+
|
|
28
|
+
const FSM_BLOB = FSM.FSM_CUSTOM1;
|
|
29
|
+
const FSM_QUERYING = FSM.FSM_CUSTOM2;
|
|
30
|
+
const FSM_LISTING = FSM.FSM_CUSTOM3;
|
|
31
|
+
const FSM_AGGREGATING = FSM.FSM_CUSTOM4;
|
|
32
|
+
const FSM_SAVING = FSM.FSM_CUSTOM5;
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
const FSM_ACCUM = FSM.FSM_CUSTOM1;
|
|
36
|
+
|
|
37
|
+
const MaxPendingDownloads = 50;
|
|
38
|
+
const MaxPendingUploads = 50;
|
|
39
|
+
|
|
40
|
+
class FsmAggregate extends FSM.Fsm
|
|
41
|
+
{
|
|
42
|
+
options: AccumOptions;
|
|
43
|
+
item: LogItem;
|
|
44
|
+
|
|
45
|
+
constructor(env: Environment, item: LogItem, options?: AccumOptions)
|
|
46
|
+
{
|
|
47
|
+
super(env);
|
|
48
|
+
this.options = Util.shallowAssignImmutable(DefaultOptions, options);
|
|
49
|
+
this.item = item;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
get env(): Environment { return this._env as Environment }
|
|
53
|
+
|
|
54
|
+
fetchNext(): void
|
|
55
|
+
{
|
|
56
|
+
if (this.item.children)
|
|
57
|
+
{
|
|
58
|
+
let nPending: number = 0;
|
|
59
|
+
for (let id in this.item.children) if (this.item.children.hasOwnProperty(id))
|
|
60
|
+
{
|
|
61
|
+
let item = this.item.children[id];
|
|
62
|
+
if (item.present && item.blob == null && item.accum == null)
|
|
63
|
+
{
|
|
64
|
+
delete item.children; // not needed since I have this aggregate
|
|
65
|
+
item.blob = LogBlob.createForDownload(this.env, item.id);
|
|
66
|
+
this.waitOn(item.blob.fsmLoad);
|
|
67
|
+
nPending++;
|
|
68
|
+
if (nPending >= MaxPendingDownloads)
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
aggregate(): void
|
|
76
|
+
{
|
|
77
|
+
// Already computed and aggregated completely?
|
|
78
|
+
if (this.item.key.closed && this.item.accum)
|
|
79
|
+
return;
|
|
80
|
+
|
|
81
|
+
// Only aggregate closed?
|
|
82
|
+
if (! this.item.key.closed && this.options.onlyAggregateClosed)
|
|
83
|
+
return;
|
|
84
|
+
|
|
85
|
+
// Otherwise aggregate
|
|
86
|
+
console.log(`logaccum: aggregating ${this.item.id}`);
|
|
87
|
+
let la: ILogAccumulator = LogAccumulator.create();
|
|
88
|
+
this.item.accum = la;
|
|
89
|
+
let bClosed = this.item.key.closed;
|
|
90
|
+
for (let id in this.item.children) if (this.item.children.hasOwnProperty(id))
|
|
91
|
+
{
|
|
92
|
+
let item = this.item.children[id];
|
|
93
|
+
|
|
94
|
+
if (item.key.isLog)
|
|
95
|
+
{
|
|
96
|
+
LogAccumulator.gather(la, item.id, item.key.instance, item.key.dateKey, item.blob.result);
|
|
97
|
+
if (bClosed) delete item.blob;
|
|
98
|
+
}
|
|
99
|
+
else
|
|
100
|
+
{
|
|
101
|
+
if (item.accum == null)
|
|
102
|
+
{
|
|
103
|
+
if (item.blob)
|
|
104
|
+
{
|
|
105
|
+
item.accum = JSON.parse(item.blob.result);
|
|
106
|
+
delete item.blob;
|
|
107
|
+
}
|
|
108
|
+
else
|
|
109
|
+
{
|
|
110
|
+
console.log(`logaccum: expected downloaded blob for non-aggregated child ${item.id}`);
|
|
111
|
+
item.accum = LogAccumulator.create();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
LogAccumulator.aggregate(la, item.accum);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Save memory - don't need children if I won't re-aggregate with new data (from above - test for closed)
|
|
119
|
+
if (bClosed)
|
|
120
|
+
{
|
|
121
|
+
if (! this.item.present)
|
|
122
|
+
{
|
|
123
|
+
this.env.log.chatter(`logaccum: saving ${this.item.id}`);
|
|
124
|
+
let blob = LogBlob.createForUpload(this.env, this.item.id, JSON.stringify(this.item.accum));
|
|
125
|
+
this.waitOn(blob.fsmSave);
|
|
126
|
+
this.item.present = true;
|
|
127
|
+
}
|
|
128
|
+
delete this.item.children;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
tick(): void
|
|
133
|
+
{
|
|
134
|
+
if (this.ready && this.isDependentError)
|
|
135
|
+
this.setState(FSM.FSM_ERROR);
|
|
136
|
+
else if (this.ready)
|
|
137
|
+
{
|
|
138
|
+
switch (this.state)
|
|
139
|
+
{
|
|
140
|
+
case FSM.FSM_STARTING:
|
|
141
|
+
this.fetchNext();
|
|
142
|
+
if (this.nWaitOn == 0)
|
|
143
|
+
this.setState(FSM_ACCUM);
|
|
144
|
+
break;
|
|
145
|
+
|
|
146
|
+
case FSM_ACCUM:
|
|
147
|
+
this.aggregate();
|
|
148
|
+
this.setState(FSM_SAVING);
|
|
149
|
+
break;
|
|
150
|
+
|
|
151
|
+
case FSM_SAVING:
|
|
152
|
+
this.setState(FSM.FSM_DONE);
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
class FsmDeepAggregate extends FSM.Fsm
|
|
160
|
+
{
|
|
161
|
+
options: AccumOptions;
|
|
162
|
+
root: LogItem;
|
|
163
|
+
items: LogItem[];
|
|
164
|
+
cur: number;
|
|
165
|
+
|
|
166
|
+
constructor(env: Environment, root: LogItem, options?: AccumOptions)
|
|
167
|
+
{
|
|
168
|
+
super(env);
|
|
169
|
+
this.options = Util.shallowAssignImmutable(DefaultOptions, options);
|
|
170
|
+
this.root = root;
|
|
171
|
+
this.items = root.query((item: LogItem) => { return item.accum === undefined && !item.present });
|
|
172
|
+
this.cur = 0;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
get env(): Environment { return this._env as Environment }
|
|
176
|
+
|
|
177
|
+
tick(): void
|
|
178
|
+
{
|
|
179
|
+
if (this.ready && this.isDependentError)
|
|
180
|
+
this.setState(FSM.FSM_ERROR);
|
|
181
|
+
else if (this.ready)
|
|
182
|
+
{
|
|
183
|
+
switch (this.state)
|
|
184
|
+
{
|
|
185
|
+
case FSM.FSM_STARTING:
|
|
186
|
+
// Save memory
|
|
187
|
+
if (this.cur > 100)
|
|
188
|
+
{
|
|
189
|
+
this.items.splice(0, this.cur);
|
|
190
|
+
this.cur = 0;
|
|
191
|
+
}
|
|
192
|
+
if (this.cur < this.items.length)
|
|
193
|
+
this.waitOn(new FsmAggregate(this.env, this.items[this.cur++], this.options));
|
|
194
|
+
else
|
|
195
|
+
this.setState(FSM.FSM_DONE);
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
class FsmSaveAggregates extends FSM.Fsm
|
|
203
|
+
{
|
|
204
|
+
options: AccumOptions;
|
|
205
|
+
root: LogItem;
|
|
206
|
+
items: LogItem[];
|
|
207
|
+
cur: number;
|
|
208
|
+
|
|
209
|
+
constructor(env: Environment, root: LogItem, options?: AccumOptions)
|
|
210
|
+
{
|
|
211
|
+
super(env);
|
|
212
|
+
this.options = Util.shallowAssignImmutable(DefaultOptions, options);
|
|
213
|
+
this.root = root;
|
|
214
|
+
this.items = root.query((item: LogItem) => { return item.accum !== undefined && !item.present && item.key.closed });
|
|
215
|
+
this.cur = 0;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
get env(): Environment { return this._env as Environment }
|
|
219
|
+
|
|
220
|
+
tick(): void
|
|
221
|
+
{
|
|
222
|
+
if (this.ready && this.isDependentError)
|
|
223
|
+
this.setState(FSM.FSM_ERROR);
|
|
224
|
+
else if (this.ready)
|
|
225
|
+
{
|
|
226
|
+
switch (this.state)
|
|
227
|
+
{
|
|
228
|
+
case FSM.FSM_STARTING:
|
|
229
|
+
let nPending: number = 0;
|
|
230
|
+
// Save memory
|
|
231
|
+
if (this.cur > 100)
|
|
232
|
+
{
|
|
233
|
+
this.items.splice(0, this.cur);
|
|
234
|
+
this.cur = 0;
|
|
235
|
+
}
|
|
236
|
+
while (this.cur < this.items.length && nPending < MaxPendingUploads)
|
|
237
|
+
{
|
|
238
|
+
let item = this.items[this.cur++];
|
|
239
|
+
item.blob = LogBlob.createForUpload(this.env, item.id, JSON.stringify(item.accum));
|
|
240
|
+
this.waitOn(item.blob.fsmSave);
|
|
241
|
+
nPending++;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (nPending == 0) this.setState(FSM.FSM_DONE);
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
export class LogItem
|
|
253
|
+
{
|
|
254
|
+
id: string;
|
|
255
|
+
key?: LogKey;
|
|
256
|
+
present?: boolean;
|
|
257
|
+
parent?: LogItem;
|
|
258
|
+
children?: { [id: string]: LogItem };
|
|
259
|
+
accum?: ILogAccumulator;
|
|
260
|
+
blob?: LogBlob;
|
|
261
|
+
|
|
262
|
+
constructor(id: string)
|
|
263
|
+
{
|
|
264
|
+
this.id = id;
|
|
265
|
+
if (id === 'root') // root
|
|
266
|
+
{
|
|
267
|
+
this.key = LogKey.root();
|
|
268
|
+
this.children = {};
|
|
269
|
+
}
|
|
270
|
+
else
|
|
271
|
+
{
|
|
272
|
+
this.key = LogKey.create(id);
|
|
273
|
+
if (this.key.kind !== 'Log')
|
|
274
|
+
this.children = {};
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
query(filter: (item: LogItem) => boolean): LogItem[]
|
|
279
|
+
{
|
|
280
|
+
let items: LogItem[] = [];
|
|
281
|
+
|
|
282
|
+
for (let yearID in this.children) if (this.children.hasOwnProperty(yearID))
|
|
283
|
+
{
|
|
284
|
+
let year = this.children[yearID];
|
|
285
|
+
for (let monthID in year.children) if (year.children.hasOwnProperty(monthID))
|
|
286
|
+
{
|
|
287
|
+
let month = year.children[monthID];
|
|
288
|
+
for (let dateID in month.children) if (month.children.hasOwnProperty(dateID))
|
|
289
|
+
{
|
|
290
|
+
let date = month.children[dateID];
|
|
291
|
+
for (let id in date.children) if (date.children.hasOwnProperty(id))
|
|
292
|
+
{
|
|
293
|
+
let log = date.children[id];
|
|
294
|
+
if (filter(log)) items.push(log);
|
|
295
|
+
}
|
|
296
|
+
if (filter(date)) items.push(date);
|
|
297
|
+
}
|
|
298
|
+
if (filter(month)) items.push(month);
|
|
299
|
+
}
|
|
300
|
+
if (filter(year)) items.push(year);
|
|
301
|
+
}
|
|
302
|
+
if (filter(this)) items.push(this);
|
|
303
|
+
|
|
304
|
+
return items;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
addListing(ls: string[]): void
|
|
308
|
+
{
|
|
309
|
+
ls.forEach((id: string) => this.add(new LogItem(id)));
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
addChild(id: string, child: LogItem): void
|
|
313
|
+
{
|
|
314
|
+
this.children[id] = child;
|
|
315
|
+
child.parent = this;
|
|
316
|
+
for (let p = child.parent; p; p = p.parent)
|
|
317
|
+
delete p.accum;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
add(item: LogItem): void
|
|
321
|
+
{
|
|
322
|
+
let yearID = item.key.yearID;
|
|
323
|
+
let year = this.children[yearID];
|
|
324
|
+
if (item.key.isYear)
|
|
325
|
+
{
|
|
326
|
+
if (year === undefined)
|
|
327
|
+
{
|
|
328
|
+
year = item;
|
|
329
|
+
this.addChild(yearID, item);
|
|
330
|
+
}
|
|
331
|
+
year.present = true;
|
|
332
|
+
}
|
|
333
|
+
else
|
|
334
|
+
{
|
|
335
|
+
if (year === undefined)
|
|
336
|
+
{
|
|
337
|
+
year = new LogItem(yearID);
|
|
338
|
+
this.addChild(yearID, year);
|
|
339
|
+
}
|
|
340
|
+
else if (year.present)
|
|
341
|
+
return;
|
|
342
|
+
let monthID = item.key.monthID;
|
|
343
|
+
let month = year.children[monthID];
|
|
344
|
+
if (item.key.isMonth)
|
|
345
|
+
{
|
|
346
|
+
if (month === undefined)
|
|
347
|
+
{
|
|
348
|
+
month = item;
|
|
349
|
+
year.addChild(monthID, item);
|
|
350
|
+
}
|
|
351
|
+
month.present = true;
|
|
352
|
+
}
|
|
353
|
+
else
|
|
354
|
+
{
|
|
355
|
+
if (month === undefined)
|
|
356
|
+
{
|
|
357
|
+
month = new LogItem(monthID);
|
|
358
|
+
year.addChild(monthID, month);
|
|
359
|
+
}
|
|
360
|
+
else if (month.present)
|
|
361
|
+
return;
|
|
362
|
+
let dateID = item.key.dateID;
|
|
363
|
+
let date = month.children[dateID];
|
|
364
|
+
if (item.key.isDate)
|
|
365
|
+
{
|
|
366
|
+
if (date === undefined)
|
|
367
|
+
{
|
|
368
|
+
date = item;
|
|
369
|
+
month.addChild(dateID, item);
|
|
370
|
+
}
|
|
371
|
+
date.present = true;
|
|
372
|
+
}
|
|
373
|
+
else
|
|
374
|
+
{
|
|
375
|
+
if (date === undefined)
|
|
376
|
+
{
|
|
377
|
+
date = new LogItem(dateID);
|
|
378
|
+
month.addChild(dateID, date);
|
|
379
|
+
}
|
|
380
|
+
else if (date.present)
|
|
381
|
+
return;
|
|
382
|
+
let log = date.children[item.id];
|
|
383
|
+
if (log === undefined)
|
|
384
|
+
{
|
|
385
|
+
date.addChild(item.id, item);
|
|
386
|
+
log = item;
|
|
387
|
+
}
|
|
388
|
+
log.present = true;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Everything we want to accumulate has a key that we use to identify and index it.
|
|
396
|
+
// We also want to track which instance generated the key (so bucket by instance)
|
|
397
|
+
// We also want to track which day some event occurred on (so bucket by day).
|
|
398
|
+
// Finally, we either want to just count occurrences or we want to track a value (to report min, max, avg).
|
|
399
|
+
// We assume NEvents >> NDays >> NInstances.
|
|
400
|
+
// So bucket by { [instance: string]: { [day: string]: { [event: string]: Accumulator } } }
|
|
401
|
+
// And then provide ways to walk that resulting data structure and aggregate and report it.
|
|
402
|
+
// Note that for some things (like API calls) we are interested in the total number of independent events.
|
|
403
|
+
// For other things (like unique users or sessions/maps) we are only interested in the total number of uniques.
|
|
404
|
+
// These are the type of answers:
|
|
405
|
+
// NInstances: number (number of instances seen)
|
|
406
|
+
// NDays: number (number of days seen)
|
|
407
|
+
// NUniques: number (number of unique things tracked, e.g. total number of unique users seen)
|
|
408
|
+
// NUniquesByDay: { [day: string]: number } total number of uniques each day
|
|
409
|
+
// Values: { [id: string]: ValueAccumulator } total number of occurrences of each thing tracked (across all days)
|
|
410
|
+
// ValuesByDay: { [id: string]: { [days: string]: ValueAccumulator } }
|
|
411
|
+
//
|
|
412
|
+
|
|
413
|
+
export interface IValueAccumulator
|
|
414
|
+
{
|
|
415
|
+
count: number;
|
|
416
|
+
total: number;
|
|
417
|
+
min?: number;
|
|
418
|
+
max?: number;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
export class ValueAccumulator
|
|
422
|
+
{
|
|
423
|
+
constructor()
|
|
424
|
+
{
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
static create(): IValueAccumulator
|
|
428
|
+
{
|
|
429
|
+
return { count: 0, total: 0 };
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
static avg(va: IValueAccumulator): number
|
|
433
|
+
{
|
|
434
|
+
return va.count > 0 ? Math.round(va.total / va.count) : 0;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
static incr(va: IValueAccumulator): void
|
|
438
|
+
{
|
|
439
|
+
va.count++;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
static accum(va: IValueAccumulator, v: number): void
|
|
443
|
+
{
|
|
444
|
+
va.count++;
|
|
445
|
+
va.total += v;
|
|
446
|
+
if (va.min === undefined || v < va.min)
|
|
447
|
+
va.min = v;
|
|
448
|
+
if (va.max === undefined || v > va.max)
|
|
449
|
+
va.max = v;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
static reduce(va: IValueAccumulator, reduction: IValueAccumulator): void
|
|
453
|
+
{
|
|
454
|
+
reduction.count += va.count;
|
|
455
|
+
reduction.total += va.total;
|
|
456
|
+
if (reduction.min === undefined || va.min < reduction.min)
|
|
457
|
+
reduction.min = va.min;
|
|
458
|
+
if (reduction.max === undefined || va.max > reduction.max)
|
|
459
|
+
reduction.max = va.max;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
static log(va: IValueAccumulator): string
|
|
463
|
+
{
|
|
464
|
+
if (va.min === undefined)
|
|
465
|
+
return `${va.count}`;
|
|
466
|
+
else
|
|
467
|
+
return `count: ${va.count} avg: ${Math.round(ValueAccumulator.avg(va))} min: ${va.min} max: ${va.max}`;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
export type IValueAccumulatorIndex = { [key: string]: IValueAccumulator };
|
|
472
|
+
export type IValueAccumulatorIndexIndex = { [key: string]: { [key: string]: IValueAccumulator } }; export type IEventAccumulator = IValueAccumulatorIndex;
|
|
473
|
+
export class EventAccumulator
|
|
474
|
+
{
|
|
475
|
+
constructor() { }
|
|
476
|
+
|
|
477
|
+
static create(): IEventAccumulator
|
|
478
|
+
{
|
|
479
|
+
return ({});
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
static getAccumulator(ea: IEventAccumulator, o: any, prop: string | string[]): IValueAccumulator
|
|
483
|
+
{
|
|
484
|
+
let event: string;
|
|
485
|
+
if (Array.isArray(prop))
|
|
486
|
+
{
|
|
487
|
+
let sa: string[] = [];
|
|
488
|
+
for (let i: number = 0; i < prop.length; i++)
|
|
489
|
+
{
|
|
490
|
+
if (o[prop[i]] === undefined)
|
|
491
|
+
return null;
|
|
492
|
+
sa.push(o[prop[i]]);
|
|
493
|
+
}
|
|
494
|
+
event = sa.join('+');
|
|
495
|
+
}
|
|
496
|
+
else
|
|
497
|
+
event = o[prop];
|
|
498
|
+
if (event !== undefined)
|
|
499
|
+
{
|
|
500
|
+
let a = ea[event];
|
|
501
|
+
if (a === undefined)
|
|
502
|
+
{
|
|
503
|
+
a = ValueAccumulator.create();
|
|
504
|
+
ea[event] = a;
|
|
505
|
+
}
|
|
506
|
+
return a;
|
|
507
|
+
}
|
|
508
|
+
return null;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
static NUnique(ea: IEventAccumulator): number
|
|
512
|
+
{
|
|
513
|
+
return Util.countKeys(ea);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
static incr(ea: IEventAccumulator, o: any, prop: string | string[]): void
|
|
517
|
+
{
|
|
518
|
+
let a = EventAccumulator.getAccumulator(ea, o, prop);
|
|
519
|
+
if (a) ValueAccumulator.incr(a);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
static accum(ea: IEventAccumulator, o: any, name: string, value: string): void
|
|
523
|
+
{
|
|
524
|
+
let a = EventAccumulator.getAccumulator(ea, o, name);
|
|
525
|
+
if (a && o[value] !== undefined)
|
|
526
|
+
ValueAccumulator.accum(a, Number(o[value]));
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
static reduce(ea: IEventAccumulator, event: string, reduction: IValueAccumulator)
|
|
530
|
+
{
|
|
531
|
+
if (ea[event] !== undefined)
|
|
532
|
+
ValueAccumulator.reduce(ea[event], reduction);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
export type IEventAccumulatorIndex = { [key: string]: IEventAccumulator };
|
|
537
|
+
export type IDayAccumulator = IEventAccumulatorIndex;
|
|
538
|
+
|
|
539
|
+
export class DayAccumulator
|
|
540
|
+
{
|
|
541
|
+
constructor() { }
|
|
542
|
+
|
|
543
|
+
static create(): IDayAccumulator { return ({}) }
|
|
544
|
+
|
|
545
|
+
static count(da: IDayAccumulator): number
|
|
546
|
+
{
|
|
547
|
+
return Util.countKeys(da);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
static getAccumulator(da: IDayAccumulator, date: string): IEventAccumulator
|
|
551
|
+
{
|
|
552
|
+
let a = da[date];
|
|
553
|
+
if (a === undefined)
|
|
554
|
+
{
|
|
555
|
+
a = EventAccumulator.create();
|
|
556
|
+
da[date] = a;
|
|
557
|
+
}
|
|
558
|
+
return a;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
static incr(da: IDayAccumulator, date: string, o: any, prop: string | string[]): void
|
|
562
|
+
{
|
|
563
|
+
let a = DayAccumulator.getAccumulator(da, date);
|
|
564
|
+
EventAccumulator.incr(a, o, prop);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
static accum(da: IDayAccumulator, date: string, o: any, name: string, value: string): void
|
|
568
|
+
{
|
|
569
|
+
let a = DayAccumulator.getAccumulator(da, date);
|
|
570
|
+
EventAccumulator.accum(a, o, name, value);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
static reduce(da: IDayAccumulator, event: string, reduction: IValueAccumulator): void
|
|
574
|
+
{
|
|
575
|
+
for (let date in da) if (da.hasOwnProperty(date))
|
|
576
|
+
EventAccumulator.reduce(da[date], event, reduction);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
static reduceByDateKeys(da: IDayAccumulator, dateKeys: IKeyIndex, event: string, reduction: IValueAccumulator): void
|
|
580
|
+
{
|
|
581
|
+
if (dateKeys === undefined)
|
|
582
|
+
{
|
|
583
|
+
for (let date in da) if (da.hasOwnProperty(date))
|
|
584
|
+
EventAccumulator.reduce(da[date], event, reduction);
|
|
585
|
+
}
|
|
586
|
+
else
|
|
587
|
+
{
|
|
588
|
+
for (let date in dateKeys) if (dateKeys.hasOwnProperty(date) && da.hasOwnProperty(date))
|
|
589
|
+
EventAccumulator.reduce(da[date], event, reduction);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
static reduceByDate(da: IDayAccumulator, date: string, event: string, dayReduction: IValueAccumulator): void
|
|
594
|
+
{
|
|
595
|
+
if (da[date] !== undefined)
|
|
596
|
+
EventAccumulator.reduce(da[date], event, dayReduction);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
static reduceUniqueByDate(da: IDayAccumulator, date: string, days: IValueAccumulatorIndexIndex): void
|
|
600
|
+
{
|
|
601
|
+
if (da[date] !== undefined)
|
|
602
|
+
{
|
|
603
|
+
let events = da[date];
|
|
604
|
+
let day = days[date];
|
|
605
|
+
for (let e in events) if (events.hasOwnProperty(e))
|
|
606
|
+
if (day[e] === undefined)
|
|
607
|
+
day[e] = ValueAccumulator.create();
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
export type IDayAccumulatorIndex = { [key: string]: IDayAccumulator };
|
|
613
|
+
export type IKeyIndex = { [key: string]: boolean };
|
|
614
|
+
export interface IInstanceAccumulator
|
|
615
|
+
{
|
|
616
|
+
dateKeys: IKeyIndex;
|
|
617
|
+
uniqueKeys: IKeyIndex;
|
|
618
|
+
instances: IDayAccumulatorIndex;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
export class InstanceAccumulator
|
|
622
|
+
{
|
|
623
|
+
constructor() { }
|
|
624
|
+
|
|
625
|
+
static create(): IInstanceAccumulator { return { dateKeys: {}, uniqueKeys: {}, instances: {} }; }
|
|
626
|
+
|
|
627
|
+
static getToday(ia: IInstanceAccumulator): string
|
|
628
|
+
{
|
|
629
|
+
let today: string = undefined;
|
|
630
|
+
|
|
631
|
+
for (let p in ia.dateKeys) if (ia.dateKeys.hasOwnProperty(p))
|
|
632
|
+
if (today === undefined || p > today)
|
|
633
|
+
today = p;
|
|
634
|
+
|
|
635
|
+
return today;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
static getAccumulator(ia: IInstanceAccumulator, instance: string): IDayAccumulator
|
|
639
|
+
{
|
|
640
|
+
let a = ia.instances[instance];
|
|
641
|
+
if (a === undefined)
|
|
642
|
+
{
|
|
643
|
+
a = DayAccumulator.create();
|
|
644
|
+
ia.instances[instance] = a;
|
|
645
|
+
}
|
|
646
|
+
return a;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
static incr(ia: IInstanceAccumulator, instance: string, date: string, o: any, prop: string | string[]): void
|
|
650
|
+
{
|
|
651
|
+
let key: string;
|
|
652
|
+
if (Array.isArray(prop))
|
|
653
|
+
{
|
|
654
|
+
let sa: string[] = [];
|
|
655
|
+
for (let i: number = 0; i < prop.length; i++)
|
|
656
|
+
{
|
|
657
|
+
if (o[prop[i]] === undefined)
|
|
658
|
+
return;
|
|
659
|
+
sa.push(o[prop[i]]);
|
|
660
|
+
}
|
|
661
|
+
key = sa.join('+');
|
|
662
|
+
}
|
|
663
|
+
else
|
|
664
|
+
{
|
|
665
|
+
key = o[prop];
|
|
666
|
+
if (key === undefined) return;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
ia.dateKeys[date] = true;
|
|
670
|
+
ia.uniqueKeys[key] = true;
|
|
671
|
+
let a = InstanceAccumulator.getAccumulator(ia, instance);
|
|
672
|
+
DayAccumulator.incr(a, date, o, prop);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
static accum(ia: IInstanceAccumulator, instance: string, date: string, o: any, name: string, value: string): void
|
|
676
|
+
{
|
|
677
|
+
if (o[name] !== undefined)
|
|
678
|
+
{
|
|
679
|
+
ia.dateKeys[date] = true;
|
|
680
|
+
ia.uniqueKeys[o[name]] = true;
|
|
681
|
+
let a = InstanceAccumulator.getAccumulator(ia, instance);
|
|
682
|
+
DayAccumulator.accum(a, date, o, name, value);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
static NInstances(ia: IInstanceAccumulator): number
|
|
687
|
+
{
|
|
688
|
+
return Util.countKeys(ia.instances);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
static NDays(ia: IInstanceAccumulator): number
|
|
692
|
+
{
|
|
693
|
+
return Util.countKeys(ia.dateKeys);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
static NUniques(ia: IInstanceAccumulator): number
|
|
697
|
+
{
|
|
698
|
+
return Util.countKeys(ia.uniqueKeys);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
static NUniquesByDay(ia: IInstanceAccumulator, dateKeys?: IKeyIndex): IValueAccumulatorIndex
|
|
702
|
+
{
|
|
703
|
+
let result: IValueAccumulatorIndex = {};
|
|
704
|
+
let gather: IValueAccumulatorIndexIndex = {};
|
|
705
|
+
let date: string;
|
|
706
|
+
|
|
707
|
+
// Initialize for all the requested dates
|
|
708
|
+
if (dateKeys === undefined) dateKeys = ia.dateKeys;
|
|
709
|
+
for (date in dateKeys) if (dateKeys.hasOwnProperty(date))
|
|
710
|
+
{
|
|
711
|
+
result[date] = ValueAccumulator.create();
|
|
712
|
+
gather[date] = {};
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// Now gather up from each instance
|
|
716
|
+
for (let instance in ia.instances) if (ia.instances.hasOwnProperty(instance))
|
|
717
|
+
for (date in dateKeys) if (dateKeys.hasOwnProperty(date))
|
|
718
|
+
DayAccumulator.reduceUniqueByDate(ia.instances[instance], date, gather);
|
|
719
|
+
|
|
720
|
+
// Now record totals
|
|
721
|
+
for (date in dateKeys) if (dateKeys.hasOwnProperty(date))
|
|
722
|
+
result[date].count = Util.countKeys(gather[date]);
|
|
723
|
+
|
|
724
|
+
return result;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
static AllCountsByDay(ia: IInstanceAccumulator, dateKeys?: IKeyIndex, eventKeys?: IKeyIndex): IValueAccumulatorIndexIndex
|
|
728
|
+
{
|
|
729
|
+
let result: IValueAccumulatorIndexIndex = {};
|
|
730
|
+
let date: string;
|
|
731
|
+
let event: string;
|
|
732
|
+
|
|
733
|
+
// If we don't ask for special date, just get all
|
|
734
|
+
if (dateKeys === undefined) dateKeys = ia.dateKeys;
|
|
735
|
+
|
|
736
|
+
// If we don't ask for special list, just get all
|
|
737
|
+
if (eventKeys === undefined) eventKeys = ia.uniqueKeys;
|
|
738
|
+
|
|
739
|
+
// Initialize for all the dates
|
|
740
|
+
for (date in dateKeys) if (dateKeys.hasOwnProperty(date))
|
|
741
|
+
{
|
|
742
|
+
let day: IValueAccumulatorIndex = {};
|
|
743
|
+
result[date] = day;
|
|
744
|
+
for (event in eventKeys) if (eventKeys.hasOwnProperty(event))
|
|
745
|
+
day[event] = ValueAccumulator.create();
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// Now walk through all instances, gathering dates
|
|
749
|
+
for (date in dateKeys) if (dateKeys.hasOwnProperty(date))
|
|
750
|
+
for (let instance in ia.instances) if (ia.instances.hasOwnProperty(instance))
|
|
751
|
+
for (event in eventKeys) if (eventKeys.hasOwnProperty(event))
|
|
752
|
+
DayAccumulator.reduceByDate(ia.instances[instance], date, event, result[date][event]);
|
|
753
|
+
|
|
754
|
+
return result;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
static TotalCountsByDay(ia: IInstanceAccumulator, dateKeys?: IKeyIndex, eventKeys?: IKeyIndex): IValueAccumulatorIndex
|
|
758
|
+
{
|
|
759
|
+
let result: IValueAccumulatorIndex = {};
|
|
760
|
+
let date: string;
|
|
761
|
+
let event: string;
|
|
762
|
+
|
|
763
|
+
// If we don't ask for special date, just get all
|
|
764
|
+
if (dateKeys === undefined) dateKeys = ia.dateKeys;
|
|
765
|
+
|
|
766
|
+
// If we don't ask for special list, just get all
|
|
767
|
+
if (eventKeys === undefined) eventKeys = ia.uniqueKeys;
|
|
768
|
+
|
|
769
|
+
// Initialize for all the dates
|
|
770
|
+
for (date in dateKeys) if (dateKeys.hasOwnProperty(date))
|
|
771
|
+
result[date] = ValueAccumulator.create();
|
|
772
|
+
|
|
773
|
+
// Now walk through all instances, gathering dates
|
|
774
|
+
for (date in dateKeys) if (dateKeys.hasOwnProperty(date))
|
|
775
|
+
for (let instance in ia.instances) if (ia.instances.hasOwnProperty(instance))
|
|
776
|
+
for (event in eventKeys) if (eventKeys.hasOwnProperty(event))
|
|
777
|
+
DayAccumulator.reduceByDate(ia.instances[instance], date, event, result[date]);
|
|
778
|
+
|
|
779
|
+
return result;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
static ValuesByEvent(ia: IInstanceAccumulator, dateKeys?: IKeyIndex, eventKeys?: IKeyIndex): IValueAccumulatorIndex
|
|
783
|
+
{
|
|
784
|
+
let result: IValueAccumulatorIndex = {};
|
|
785
|
+
let event: string;
|
|
786
|
+
|
|
787
|
+
// If we don't ask for special list, just get all
|
|
788
|
+
if (eventKeys === undefined) eventKeys = ia.uniqueKeys;
|
|
789
|
+
|
|
790
|
+
for (event in eventKeys) if (eventKeys.hasOwnProperty(event))
|
|
791
|
+
result[event] = ValueAccumulator.create();
|
|
792
|
+
|
|
793
|
+
for (let instance in ia.instances) if (ia.instances.hasOwnProperty(instance))
|
|
794
|
+
for (event in eventKeys) if (eventKeys.hasOwnProperty(event))
|
|
795
|
+
DayAccumulator.reduceByDateKeys(ia.instances[instance], dateKeys, event, result[event]);
|
|
796
|
+
|
|
797
|
+
return result;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
static ValuesByDay(ia: IInstanceAccumulator, dateKeys?: IKeyIndex, eventKeys?: IKeyIndex): IValueAccumulatorIndexIndex
|
|
801
|
+
{
|
|
802
|
+
let result: IValueAccumulatorIndexIndex = {}
|
|
803
|
+
let event: string;
|
|
804
|
+
let date: string;
|
|
805
|
+
|
|
806
|
+
// If we don't ask for special date, just get all
|
|
807
|
+
if (dateKeys === undefined) dateKeys = ia.dateKeys;
|
|
808
|
+
|
|
809
|
+
// If we don't ask for special list, just get all
|
|
810
|
+
if (eventKeys === undefined) eventKeys = ia.uniqueKeys;
|
|
811
|
+
|
|
812
|
+
// Initialize the double level index with events by day by value
|
|
813
|
+
for (event in eventKeys) if (eventKeys.hasOwnProperty(event))
|
|
814
|
+
{
|
|
815
|
+
let days: IValueAccumulatorIndex = {};
|
|
816
|
+
|
|
817
|
+
for (date in dateKeys) if (dateKeys.hasOwnProperty(date))
|
|
818
|
+
days[date] = ValueAccumulator.create();
|
|
819
|
+
|
|
820
|
+
result[event] = days;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// Now reduce them
|
|
824
|
+
for (let instance in ia.instances) if (ia.instances.hasOwnProperty(instance))
|
|
825
|
+
for (event in eventKeys) if (eventKeys.hasOwnProperty(event))
|
|
826
|
+
for (date in dateKeys) if (dateKeys.hasOwnProperty(date))
|
|
827
|
+
DayAccumulator.reduceByDate(ia.instances[instance], date, event, result[event][date]);
|
|
828
|
+
|
|
829
|
+
return result;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
static filterNoisyProperty(p: string): boolean
|
|
833
|
+
{
|
|
834
|
+
let noises: string[] = [ 'session(', 'Started server at' ];
|
|
835
|
+
|
|
836
|
+
for (let i = 0; i < noises.length; i++)
|
|
837
|
+
if (p.indexOf(noises[i]) == 0)
|
|
838
|
+
return true;
|
|
839
|
+
return false;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
static getCountRows(ia: IInstanceAccumulator, dateKeys?: IKeyIndex, eventKeys?: IKeyIndex): any[]
|
|
843
|
+
{
|
|
844
|
+
let values = InstanceAccumulator.ValuesByEvent(ia, dateKeys, eventKeys);
|
|
845
|
+
let rows: any[] = [];
|
|
846
|
+
|
|
847
|
+
for (let p in values) if (values.hasOwnProperty(p))
|
|
848
|
+
{
|
|
849
|
+
if (this.filterNoisyProperty(p))
|
|
850
|
+
continue;
|
|
851
|
+
|
|
852
|
+
rows.push({
|
|
853
|
+
id: String(rows.length),
|
|
854
|
+
event: p,
|
|
855
|
+
count: String(values[p].count),
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
return rows;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
static getValueRows(ia: IInstanceAccumulator, dateKeys?: IKeyIndex, eventKeys?: IKeyIndex): any[]
|
|
863
|
+
{
|
|
864
|
+
let values = InstanceAccumulator.ValuesByEvent(ia, dateKeys, eventKeys);
|
|
865
|
+
let rows: any[] = [];
|
|
866
|
+
|
|
867
|
+
for (let p in values) if (values.hasOwnProperty(p))
|
|
868
|
+
{
|
|
869
|
+
rows.push({
|
|
870
|
+
id: String(rows.length),
|
|
871
|
+
event: p,
|
|
872
|
+
count: String(values[p].count),
|
|
873
|
+
min: String(values[p].min),
|
|
874
|
+
max: String(values[p].max),
|
|
875
|
+
avg: String(ValueAccumulator.avg(values[p])),
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
return rows;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
|
|
884
|
+
type Schema = { [pos: number]: string };
|
|
885
|
+
type SchemaIndex = { [index: number]: Schema };
|
|
886
|
+
type Template = { schemaid: number, o: { [prop: string]: any } };
|
|
887
|
+
type TemplateIndex = Template[];
|
|
888
|
+
|
|
889
|
+
function objectToRows(o: any): any[]
|
|
890
|
+
{
|
|
891
|
+
let rows: any[] = [];
|
|
892
|
+
for (let p in o) if (o.hasOwnProperty(p))
|
|
893
|
+
rows.push({ event: p, count: o[p] });
|
|
894
|
+
return rows;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
function deltaRows(before: any[], after: any[]): any[]
|
|
898
|
+
{
|
|
899
|
+
let index: { [event: string]: number } = {};
|
|
900
|
+
let p: string;
|
|
901
|
+
let i: number;
|
|
902
|
+
|
|
903
|
+
if (before)
|
|
904
|
+
for (i = 0; i < before.length; i++)
|
|
905
|
+
index[before[i].event] = Number(before[i].count);
|
|
906
|
+
if (after)
|
|
907
|
+
for (i = 0; i < after.length; i++)
|
|
908
|
+
{
|
|
909
|
+
p = after[i].event;
|
|
910
|
+
let n: number = Number(after[i].count) - (index[p] === undefined ? 0 : index[p]);
|
|
911
|
+
if (n == 0)
|
|
912
|
+
delete index[p];
|
|
913
|
+
else
|
|
914
|
+
index[p] = n;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
let rows: any[] = [];
|
|
918
|
+
for (p in index) if (index.hasOwnProperty(p))
|
|
919
|
+
rows.push({ event: `${p} (delta)`, count: String(index[p]) });
|
|
920
|
+
return rows;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
export interface ILogAccumulator
|
|
924
|
+
{
|
|
925
|
+
users: IInstanceAccumulator;
|
|
926
|
+
sessions: IInstanceAccumulator;
|
|
927
|
+
events: IInstanceAccumulator;
|
|
928
|
+
errors: IInstanceAccumulator;
|
|
929
|
+
values: IInstanceAccumulator;
|
|
930
|
+
syncTimers: IInstanceAccumulator;
|
|
931
|
+
asyncTimers: IInstanceAccumulator;
|
|
932
|
+
logsSeen: { [id: string]: boolean };
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
const specialEventKey: IKeyIndex = {
|
|
936
|
+
'Listening on port 8081': true,
|
|
937
|
+
'API create ended': true,
|
|
938
|
+
'createuser': true,
|
|
939
|
+
'create visitor': true,
|
|
940
|
+
'anonymous visit': true,
|
|
941
|
+
'socialmanager: like': true,
|
|
942
|
+
'socialmanager: comment': true
|
|
943
|
+
};
|
|
944
|
+
|
|
945
|
+
const specialEventNames: any = {
|
|
946
|
+
'Listening on port 8081': 'restarts',
|
|
947
|
+
'API create ended': 'newmaps',
|
|
948
|
+
'createuser': 'newuser',
|
|
949
|
+
'create visitor': 'newvisitor',
|
|
950
|
+
'anonymous visit': 'anonview',
|
|
951
|
+
'socialmanager: like': 'likes',
|
|
952
|
+
'socialmanager: comment': 'comments'
|
|
953
|
+
};
|
|
954
|
+
|
|
955
|
+
export class LogAccumulator
|
|
956
|
+
{
|
|
957
|
+
static create(): ILogAccumulator
|
|
958
|
+
{
|
|
959
|
+
let la: ILogAccumulator =
|
|
960
|
+
{
|
|
961
|
+
users: InstanceAccumulator.create(),
|
|
962
|
+
sessions: InstanceAccumulator.create(),
|
|
963
|
+
events: InstanceAccumulator.create(),
|
|
964
|
+
errors: InstanceAccumulator.create(),
|
|
965
|
+
values: InstanceAccumulator.create(),
|
|
966
|
+
syncTimers: InstanceAccumulator.create(),
|
|
967
|
+
asyncTimers: InstanceAccumulator.create(),
|
|
968
|
+
logsSeen: {},
|
|
969
|
+
};
|
|
970
|
+
return la;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
static accumulate(la: ILogAccumulator, instance: string, dateKey: string, o: any): void
|
|
974
|
+
{
|
|
975
|
+
switch (o.kind)
|
|
976
|
+
{
|
|
977
|
+
case 'error':
|
|
978
|
+
InstanceAccumulator.incr(la.errors, instance, dateKey, o, 'event');
|
|
979
|
+
break;
|
|
980
|
+
|
|
981
|
+
case 'event':
|
|
982
|
+
InstanceAccumulator.incr(la.sessions, instance, dateKey, o, 'sessionid');
|
|
983
|
+
InstanceAccumulator.incr(la.users, instance, dateKey, o, 'userid');
|
|
984
|
+
InstanceAccumulator.incr(la.events, instance, dateKey, o, 'event');
|
|
985
|
+
break;
|
|
986
|
+
|
|
987
|
+
case 'value':
|
|
988
|
+
InstanceAccumulator.accum(la.values, instance, dateKey, o, 'event', 'value');
|
|
989
|
+
break;
|
|
990
|
+
|
|
991
|
+
case 'sync':
|
|
992
|
+
InstanceAccumulator.accum(la.syncTimers, instance, dateKey, o, 'event', 'ms');
|
|
993
|
+
break;
|
|
994
|
+
|
|
995
|
+
case 'async':
|
|
996
|
+
InstanceAccumulator.accum(la.asyncTimers, instance, dateKey, o, 'event', 'ms');
|
|
997
|
+
break;
|
|
998
|
+
|
|
999
|
+
case 'misc':
|
|
1000
|
+
// count misc
|
|
1001
|
+
break;
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
static unquoteFields(fields: string[]): string[]
|
|
1006
|
+
{
|
|
1007
|
+
// if field begins with ' strip that character and merge with subsequent
|
|
1008
|
+
// fields until encountering field that ends with ' (which might be same field).
|
|
1009
|
+
for (let i: number = 0; i < fields.length; )
|
|
1010
|
+
{
|
|
1011
|
+
let field = fields[i];
|
|
1012
|
+
if (field.indexOf("'") == 0)
|
|
1013
|
+
{
|
|
1014
|
+
let accum = field.substring(1);
|
|
1015
|
+
let iStart = i;
|
|
1016
|
+
do
|
|
1017
|
+
{
|
|
1018
|
+
if (accum.lastIndexOf("'") == accum.length-1)
|
|
1019
|
+
{
|
|
1020
|
+
accum = accum.substring(0, accum.length-1);
|
|
1021
|
+
fields.splice(iStart, i-iStart);
|
|
1022
|
+
fields[iStart] = accum;
|
|
1023
|
+
i++;
|
|
1024
|
+
accum = null;
|
|
1025
|
+
break;
|
|
1026
|
+
}
|
|
1027
|
+
else
|
|
1028
|
+
{
|
|
1029
|
+
i++;
|
|
1030
|
+
if (i < fields.length)
|
|
1031
|
+
accum += fields[i];
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
while (i < fields.length);
|
|
1035
|
+
|
|
1036
|
+
// if never encountered closing quote...
|
|
1037
|
+
if (accum != null)
|
|
1038
|
+
{
|
|
1039
|
+
fields.splice(iStart, i-iStart);
|
|
1040
|
+
fields[iStart] = accum;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
else
|
|
1044
|
+
i++;
|
|
1045
|
+
}
|
|
1046
|
+
return fields;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
static gather(la: ILogAccumulator, logid: string, instance: string, dateKey: string, csvblob: string): void
|
|
1050
|
+
{
|
|
1051
|
+
la.logsSeen[logid] = true;
|
|
1052
|
+
let schemas: SchemaIndex = {};
|
|
1053
|
+
let templates: TemplateIndex = [];
|
|
1054
|
+
let lines = csvblob.split('\n');
|
|
1055
|
+
|
|
1056
|
+
// Process schema lines
|
|
1057
|
+
let i: number = 0;
|
|
1058
|
+
for (; i < lines.length; i++)
|
|
1059
|
+
{
|
|
1060
|
+
let l = lines[i];
|
|
1061
|
+
if (l.indexOf('S') != 0)
|
|
1062
|
+
break;
|
|
1063
|
+
// Line is of form S,N,field1,field2,field3,...
|
|
1064
|
+
let s: Schema = {};
|
|
1065
|
+
let fields = l.split(',');
|
|
1066
|
+
for (let j: number = 2; j < fields.length; j++)
|
|
1067
|
+
s[j] = fields[j];
|
|
1068
|
+
schemas[Number(fields[1])] = s;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
// Process template lines
|
|
1072
|
+
let nMismatch: number = 0;
|
|
1073
|
+
for (; i < lines.length; i++)
|
|
1074
|
+
{
|
|
1075
|
+
let l = lines[i];
|
|
1076
|
+
if (l.indexOf('T') != 0)
|
|
1077
|
+
break;
|
|
1078
|
+
|
|
1079
|
+
// Line is of form T,SN,v1,v2,v3
|
|
1080
|
+
let fields = LogAccumulator.unquoteFields(l.split(','));
|
|
1081
|
+
let t: Template = { schemaid: Number(fields[1]), o: { } };
|
|
1082
|
+
templates.push(t);
|
|
1083
|
+
let schema = schemas[t.schemaid];
|
|
1084
|
+
for (let j: number = 2; j < fields.length; j++)
|
|
1085
|
+
{
|
|
1086
|
+
let prop = schema[j];
|
|
1087
|
+
if (prop === undefined)
|
|
1088
|
+
{
|
|
1089
|
+
if (nMismatch == 0)
|
|
1090
|
+
console.error('logaccumulator: template does not match schema definition');
|
|
1091
|
+
nMismatch++;
|
|
1092
|
+
}
|
|
1093
|
+
else
|
|
1094
|
+
t.o[prop] = fields[j];
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
// Gather log entries
|
|
1099
|
+
for (; i < lines.length; i++)
|
|
1100
|
+
{
|
|
1101
|
+
let l = lines[i];
|
|
1102
|
+
if (l == '') continue; // Really just EOF
|
|
1103
|
+
let fields = LogAccumulator.unquoteFields(l.split(','));
|
|
1104
|
+
let templateid = Number(fields[1]);
|
|
1105
|
+
if (templateid < 0 || templateid >= templates.length)
|
|
1106
|
+
{
|
|
1107
|
+
console.error('logaccumulator: missing template definition in log file');
|
|
1108
|
+
nMismatch++;
|
|
1109
|
+
continue;
|
|
1110
|
+
}
|
|
1111
|
+
let t = templates[templateid];
|
|
1112
|
+
let schema = schemas[t.schemaid];
|
|
1113
|
+
if (schema === undefined)
|
|
1114
|
+
console.error('logaccumulator: missing schema definition in log file');
|
|
1115
|
+
else
|
|
1116
|
+
{
|
|
1117
|
+
let o: any = {};
|
|
1118
|
+
o.instance = instance;
|
|
1119
|
+
for (let j: number = 2; j < fields.length; j++)
|
|
1120
|
+
{
|
|
1121
|
+
let prop = schema[j];
|
|
1122
|
+
if (prop === undefined)
|
|
1123
|
+
{
|
|
1124
|
+
nMismatch++;
|
|
1125
|
+
}
|
|
1126
|
+
else
|
|
1127
|
+
{
|
|
1128
|
+
o[prop] = fields[j];
|
|
1129
|
+
if (o[prop] === '')
|
|
1130
|
+
o[prop] = t.o[prop];
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
LogAccumulator.accumulate(la, instance, dateKey, o);
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
static aggregate(agg: ILogAccumulator, la: ILogAccumulator): void
|
|
1139
|
+
{
|
|
1140
|
+
Util.deepAccum(agg, la);
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
static datePatternToKeys(la: ILogAccumulator, pat: string): IKeyIndex
|
|
1144
|
+
{
|
|
1145
|
+
if (pat == null || pat === '' || pat === '...')
|
|
1146
|
+
return la.events.dateKeys;
|
|
1147
|
+
let apat = pat.split('.');
|
|
1148
|
+
if (apat.length > 3) return la.events.dateKeys;
|
|
1149
|
+
while (apat.length < 3) apat.push('');
|
|
1150
|
+
let keys: IKeyIndex = {};
|
|
1151
|
+
Object.keys(la.events.dateKeys).forEach((date: string) => {
|
|
1152
|
+
let adate = date.split('.');
|
|
1153
|
+
if ((apat[0] === '' || apat[0] === adate[0]) &&
|
|
1154
|
+
(apat[1] === '' || apat[1] === adate[1]) &&
|
|
1155
|
+
(apat[2] === '' || apat[2] === adate[2]))
|
|
1156
|
+
keys[date] = true;
|
|
1157
|
+
});
|
|
1158
|
+
return keys;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
static getToday(la: ILogAccumulator): IKeyIndex
|
|
1162
|
+
{
|
|
1163
|
+
let today = InstanceAccumulator.getToday(la.events);
|
|
1164
|
+
return today === undefined ? {} : { [today]: true };
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
static getTodayRows(la: ILogAccumulator): any[]
|
|
1168
|
+
{
|
|
1169
|
+
let todayKeys = LogAccumulator.getToday(la);
|
|
1170
|
+
if (Util.countKeys(todayKeys) == 0)
|
|
1171
|
+
return [];
|
|
1172
|
+
let today = Util.nthKey(todayKeys, 0);
|
|
1173
|
+
let daysUsers = InstanceAccumulator.NUniquesByDay(la.users, todayKeys);
|
|
1174
|
+
let daysSessions = InstanceAccumulator.NUniquesByDay(la.sessions, todayKeys);
|
|
1175
|
+
let dayEvents = InstanceAccumulator.ValuesByEvent(la.events, todayKeys);
|
|
1176
|
+
let dayErrors = InstanceAccumulator.ValuesByEvent(la.errors, todayKeys);
|
|
1177
|
+
let daysErrorTotals = InstanceAccumulator.TotalCountsByDay(la.errors, todayKeys);
|
|
1178
|
+
let totalErrors: number = 0;
|
|
1179
|
+
let rows: any[] = [];
|
|
1180
|
+
let row: any;
|
|
1181
|
+
let p: string;
|
|
1182
|
+
let dayUsersCount: string = daysUsers[today] !== undefined ? String(daysUsers[today].count) : '0';
|
|
1183
|
+
let daySessionsCount: string = daysSessions[today] !== undefined ? String(daysSessions[today].count) : '0';
|
|
1184
|
+
let dayErrorsCount: string = daysErrorTotals[today] !== undefined ? String(daysErrorTotals[today].count) : '0';
|
|
1185
|
+
rows.push( { type: 'summary', event: 'users', count: dayUsersCount } );
|
|
1186
|
+
rows.push( { type: 'summary', event: 'sessions', count: daySessionsCount } );
|
|
1187
|
+
rows.push( { type: 'summary', event: 'errors', count: dayErrorsCount } );
|
|
1188
|
+
for (p in dayErrors) if (dayErrors.hasOwnProperty(p))
|
|
1189
|
+
if (dayErrors[p].count !== 0)
|
|
1190
|
+
rows.push( { type: 'errors', event: p, count: String(dayErrors[p].count) } );
|
|
1191
|
+
for (p in dayEvents) if (dayEvents.hasOwnProperty(p))
|
|
1192
|
+
if (dayEvents[p].count !== 0)
|
|
1193
|
+
rows.push( { type: 'events', event: p, count: String(dayEvents[p].count) } );
|
|
1194
|
+
return rows;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
static getDailyRows(la: ILogAccumulator, datePattern?: string, eventKeys?: IKeyIndex): any[]
|
|
1198
|
+
{
|
|
1199
|
+
let dateKeys = LogAccumulator.datePatternToKeys(la, datePattern);
|
|
1200
|
+
eventKeys = eventKeys || specialEventKey;
|
|
1201
|
+
let daysUsers = InstanceAccumulator.NUniquesByDay(la.users, dateKeys);
|
|
1202
|
+
let daysSessions = InstanceAccumulator.NUniquesByDay(la.sessions, dateKeys);
|
|
1203
|
+
let daysEvents = InstanceAccumulator.AllCountsByDay(la.events, dateKeys, eventKeys);
|
|
1204
|
+
let daysErrorTotals = InstanceAccumulator.TotalCountsByDay(la.errors, dateKeys, eventKeys);
|
|
1205
|
+
let totalErrors: number = 0;
|
|
1206
|
+
let rows: any[] = [];
|
|
1207
|
+
let d: string;
|
|
1208
|
+
let row: any;
|
|
1209
|
+
for (d in daysUsers) if (daysUsers.hasOwnProperty(d))
|
|
1210
|
+
{
|
|
1211
|
+
row = { id: String(rows.length)+1, day: d, users: String(daysUsers[d].count) };
|
|
1212
|
+
let s = daysSessions[d];
|
|
1213
|
+
row.sessions = s === undefined ? '0' : String(s.count);
|
|
1214
|
+
let nErrors: number = daysErrorTotals[d] ? daysErrorTotals[d].count : 0;
|
|
1215
|
+
totalErrors += nErrors;
|
|
1216
|
+
row.errors = String(nErrors);
|
|
1217
|
+
let e = daysEvents[d];
|
|
1218
|
+
Object.keys(eventKeys).forEach((id: string) => {
|
|
1219
|
+
let name = (eventKeys === specialEventKey) ? specialEventNames[id] : id;
|
|
1220
|
+
row[name] = (e[id] === undefined) ? '0' : String(e[id].count);
|
|
1221
|
+
});
|
|
1222
|
+
rows.push(row);
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
// Add totals row to front of array
|
|
1226
|
+
row = {
|
|
1227
|
+
id: '0',
|
|
1228
|
+
day: '!total',
|
|
1229
|
+
users: String(InstanceAccumulator.NUniques(la.users)),
|
|
1230
|
+
sessions: String(InstanceAccumulator.NUniques(la.sessions)),
|
|
1231
|
+
errors: String(totalErrors)
|
|
1232
|
+
};
|
|
1233
|
+
Object.keys(specialEventNames).forEach((id: string) => {
|
|
1234
|
+
let count: number = 0;
|
|
1235
|
+
let name: string = (eventKeys === specialEventKey) ? specialEventNames[id] : id;
|
|
1236
|
+
rows.forEach((o: any) => { count += Number(o[name]); });
|
|
1237
|
+
row[name] = String(count);
|
|
1238
|
+
});
|
|
1239
|
+
rows.unshift(row);
|
|
1240
|
+
|
|
1241
|
+
// And sort by day
|
|
1242
|
+
rows.sort((r1: any, r2: any) => {
|
|
1243
|
+
return String(r1.day) < String(r2.day) ? -1 : (String(r1.day) > String(r2.day) ? 1 : 0);
|
|
1244
|
+
});
|
|
1245
|
+
return rows;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
static getLastDay(la: ILogAccumulator): any[]
|
|
1249
|
+
{
|
|
1250
|
+
let today = InstanceAccumulator.getToday(la.events);
|
|
1251
|
+
let rows = LogAccumulator.getDailyRows(la, today);
|
|
1252
|
+
if (rows.length == 0)
|
|
1253
|
+
return [];
|
|
1254
|
+
let row = rows[rows.length-1];
|
|
1255
|
+
delete row.day;
|
|
1256
|
+
return objectToRows(row);
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
static getTimerRows(la: ILogAccumulator, datePattern?: string, eventKeys?: IKeyIndex): any[]
|
|
1260
|
+
{
|
|
1261
|
+
let dateKeys = LogAccumulator.datePatternToKeys(la, datePattern);
|
|
1262
|
+
let rows1 = InstanceAccumulator.getValueRows(la.asyncTimers, dateKeys, eventKeys);
|
|
1263
|
+
let rows2 = InstanceAccumulator.getValueRows(la.syncTimers, dateKeys, eventKeys);
|
|
1264
|
+
return [...rows1, ...rows2];
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
static getErrorRows(la: ILogAccumulator, datePattern?: string, eventKeys?: IKeyIndex): any[]
|
|
1268
|
+
{
|
|
1269
|
+
let dateKeys = LogAccumulator.datePatternToKeys(la, datePattern);
|
|
1270
|
+
return InstanceAccumulator.getCountRows(la.errors, dateKeys, eventKeys);
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
static getEventRows(la: ILogAccumulator, datePattern?: string, eventKeys?: IKeyIndex): any[]
|
|
1274
|
+
{
|
|
1275
|
+
let dateKeys = LogAccumulator.datePatternToKeys(la, datePattern);
|
|
1276
|
+
return InstanceAccumulator.getCountRows(la.events, dateKeys, eventKeys);
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
static getValueRows(la: ILogAccumulator, datePattern?: string, eventKeys?: IKeyIndex): any[]
|
|
1280
|
+
{
|
|
1281
|
+
let dateKeys = LogAccumulator.datePatternToKeys(la, datePattern);
|
|
1282
|
+
return InstanceAccumulator.getValueRows(la.values, dateKeys, eventKeys);
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
export class FsmLogAccum extends FSM.Fsm
|
|
1287
|
+
{
|
|
1288
|
+
options: AccumOptions;
|
|
1289
|
+
root: LogItem;
|
|
1290
|
+
blobLs: LogBlob;
|
|
1291
|
+
lastErrorsRows: any[];
|
|
1292
|
+
firstScanDone: boolean;
|
|
1293
|
+
|
|
1294
|
+
constructor(env: Environment, accum?: ILogAccumulator, options?: AccumOptions)
|
|
1295
|
+
{
|
|
1296
|
+
super(env);
|
|
1297
|
+
this.options = Util.shallowAssignImmutable(DefaultOptions, options);
|
|
1298
|
+
this.firstScanDone = false;
|
|
1299
|
+
this.lastErrorsRows = [];
|
|
1300
|
+
this.root = new LogItem('root');
|
|
1301
|
+
if (accum !== undefined) // just using to query
|
|
1302
|
+
{
|
|
1303
|
+
this.root.accum = accum;
|
|
1304
|
+
this.setState(FSM.FSM_DONE);
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
get env(): Environment { return this._env as Environment }
|
|
1309
|
+
|
|
1310
|
+
resetLastErrors(): void
|
|
1311
|
+
{
|
|
1312
|
+
this.lastErrorsRows = this.getRows('errors');
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
getIncrementalErrors(): any[]
|
|
1316
|
+
{
|
|
1317
|
+
if (this.lastErrorsRows == null) this.lastErrorsRows = this.getRows('errors');
|
|
1318
|
+
return deltaRows(this.lastErrorsRows, this.getRows('errors'));
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
refresh(): void
|
|
1322
|
+
{
|
|
1323
|
+
if (this.done)
|
|
1324
|
+
{
|
|
1325
|
+
this.clearDependentError();
|
|
1326
|
+
this.setState(FSM.FSM_STARTING);
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
getCategories(): string[]
|
|
1331
|
+
{
|
|
1332
|
+
if (this.root.accum == null)
|
|
1333
|
+
return [ 'Loading' ];
|
|
1334
|
+
|
|
1335
|
+
return [
|
|
1336
|
+
'summary',
|
|
1337
|
+
'loading',
|
|
1338
|
+
'errors',
|
|
1339
|
+
'values',
|
|
1340
|
+
'events',
|
|
1341
|
+
'timers',
|
|
1342
|
+
'daily',
|
|
1343
|
+
'today',
|
|
1344
|
+
];
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
getSummaryRows(): any[]
|
|
1348
|
+
{
|
|
1349
|
+
return LogAccumulator.getLastDay(this.root.accum).concat(this.getIncrementalErrors());
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
getRows(category: string, datePattern?: string, eventKeys?: IKeyIndex): any[]
|
|
1353
|
+
{
|
|
1354
|
+
if (this.root.accum === undefined) return [];
|
|
1355
|
+
|
|
1356
|
+
switch (category)
|
|
1357
|
+
{
|
|
1358
|
+
default:
|
|
1359
|
+
return null;
|
|
1360
|
+
case 'Loading':
|
|
1361
|
+
case 'unknown':
|
|
1362
|
+
return [];
|
|
1363
|
+
|
|
1364
|
+
case 'loading':
|
|
1365
|
+
return [
|
|
1366
|
+
{
|
|
1367
|
+
id: '0',
|
|
1368
|
+
logs: String(Util.countKeys(this.root.accum.logsSeen))
|
|
1369
|
+
}
|
|
1370
|
+
];
|
|
1371
|
+
|
|
1372
|
+
case 'errors':
|
|
1373
|
+
return LogAccumulator.getErrorRows(this.root.accum, datePattern, eventKeys);
|
|
1374
|
+
break;
|
|
1375
|
+
|
|
1376
|
+
case 'values':
|
|
1377
|
+
return LogAccumulator.getValueRows(this.root.accum, datePattern, eventKeys);
|
|
1378
|
+
break;
|
|
1379
|
+
|
|
1380
|
+
case 'events':
|
|
1381
|
+
return LogAccumulator.getEventRows(this.root.accum, datePattern, eventKeys);
|
|
1382
|
+
break;
|
|
1383
|
+
|
|
1384
|
+
case 'timers':
|
|
1385
|
+
return LogAccumulator.getTimerRows(this.root.accum, datePattern, eventKeys);
|
|
1386
|
+
break;
|
|
1387
|
+
|
|
1388
|
+
case 'daily':
|
|
1389
|
+
return LogAccumulator.getDailyRows(this.root.accum, datePattern, eventKeys);
|
|
1390
|
+
break;
|
|
1391
|
+
|
|
1392
|
+
case 'today':
|
|
1393
|
+
return LogAccumulator.getTodayRows(this.root.accum);
|
|
1394
|
+
break;
|
|
1395
|
+
|
|
1396
|
+
case 'summary':
|
|
1397
|
+
return this.getSummaryRows();
|
|
1398
|
+
break;
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
tick(): void
|
|
1403
|
+
{
|
|
1404
|
+
if (this.ready && this.isDependentError)
|
|
1405
|
+
this.setState(FSM.FSM_ERROR);
|
|
1406
|
+
else if (this.ready)
|
|
1407
|
+
{
|
|
1408
|
+
switch (this.state)
|
|
1409
|
+
{
|
|
1410
|
+
case FSM.FSM_STARTING:
|
|
1411
|
+
this.blobLs = LogBlob.createForLs(this.env);
|
|
1412
|
+
this.waitOn(this.blobLs.fsmArray);
|
|
1413
|
+
this.setState(FSM_LISTING);
|
|
1414
|
+
break;
|
|
1415
|
+
|
|
1416
|
+
case FSM_LISTING:
|
|
1417
|
+
this.root.addListing(this.blobLs.keys.filter(this.blobLs.filter));
|
|
1418
|
+
this.blobLs.resetList();
|
|
1419
|
+
if (this.blobLs.fsmList.done)
|
|
1420
|
+
{
|
|
1421
|
+
delete this.blobLs;
|
|
1422
|
+
this.waitOn(new FsmDeepAggregate(this.env, this.root, this.options));
|
|
1423
|
+
this.setState(FSM_AGGREGATING);
|
|
1424
|
+
}
|
|
1425
|
+
else
|
|
1426
|
+
this.waitOn(this.blobLs.fsmArray);
|
|
1427
|
+
break;
|
|
1428
|
+
|
|
1429
|
+
case FSM_AGGREGATING:
|
|
1430
|
+
if (! this.firstScanDone)
|
|
1431
|
+
{
|
|
1432
|
+
this.firstScanDone = true;
|
|
1433
|
+
this.resetLastErrors();
|
|
1434
|
+
}
|
|
1435
|
+
this.waitOn(new FsmSaveAggregates(this.env, this.root, this.options));
|
|
1436
|
+
this.setState(FSM_SAVING);
|
|
1437
|
+
break;
|
|
1438
|
+
|
|
1439
|
+
case FSM_SAVING:
|
|
1440
|
+
this.setState(FSM.FSM_DONE);
|
|
1441
|
+
break;
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
}
|