@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.
Files changed (185) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +37 -0
  3. package/dist/all/all.d.ts +36 -0
  4. package/dist/all/allclient.d.ts +18 -0
  5. package/dist/base.js +33010 -0
  6. package/dist/base.js.map +1 -0
  7. package/dist/baseclient.js +8991 -0
  8. package/dist/baseclient.js.map +1 -0
  9. package/dist/context/all.d.ts +1 -0
  10. package/dist/context/context.d.ts +13 -0
  11. package/dist/dbabstract/all.d.ts +1 -0
  12. package/dist/dbabstract/db.d.ts +83 -0
  13. package/dist/dbdynamo/all.d.ts +1 -0
  14. package/dist/dbdynamo/dbdynamo.d.ts +190 -0
  15. package/dist/filterexpr/all.d.ts +1 -0
  16. package/dist/filterexpr/filterexpr.d.ts +64 -0
  17. package/dist/fsm/all.d.ts +1 -0
  18. package/dist/fsm/fsm.d.ts +118 -0
  19. package/dist/fsmfile/all.d.ts +1 -0
  20. package/dist/fsmfile/fsmfile.d.ts +47 -0
  21. package/dist/jsonstream/all.d.ts +1 -0
  22. package/dist/jsonstream/jsonstream.d.ts +130 -0
  23. package/dist/lambda/all.d.ts +1 -0
  24. package/dist/lambda/env.d.ts +10 -0
  25. package/dist/lambda/lambda.d.ts +18 -0
  26. package/dist/logabstract/all.d.ts +1 -0
  27. package/dist/logabstract/log.d.ts +26 -0
  28. package/dist/logclient/all.d.ts +1 -0
  29. package/dist/logclient/log.d.ts +6 -0
  30. package/dist/logserver/all.d.ts +5 -0
  31. package/dist/logserver/log.d.ts +11 -0
  32. package/dist/logserver/logaccum.d.ts +154 -0
  33. package/dist/logserver/logblob.d.ts +24 -0
  34. package/dist/logserver/logconcat.d.ts +55 -0
  35. package/dist/logserver/logkey.d.ts +28 -0
  36. package/dist/memsqs/all.d.ts +4 -0
  37. package/dist/memsqs/client.d.ts +13 -0
  38. package/dist/memsqs/loopback.d.ts +11 -0
  39. package/dist/memsqs/orderedlist.d.ts +19 -0
  40. package/dist/memsqs/queue.d.ts +84 -0
  41. package/dist/memsqs/server.d.ts +37 -0
  42. package/dist/ot-editutil/all.d.ts +2 -0
  43. package/dist/ot-editutil/oteditutil.d.ts +14 -0
  44. package/dist/ot-editutil/otmaputil.d.ts +21 -0
  45. package/dist/ot-js/all.d.ts +9 -0
  46. package/dist/ot-js/otarray.d.ts +111 -0
  47. package/dist/ot-js/otclientengine.d.ts +38 -0
  48. package/dist/ot-js/otcomposite.d.ts +37 -0
  49. package/dist/ot-js/otcounter.d.ts +17 -0
  50. package/dist/ot-js/otengine.d.ts +22 -0
  51. package/dist/ot-js/otmap.d.ts +19 -0
  52. package/dist/ot-js/otserverengine.d.ts +38 -0
  53. package/dist/ot-js/otsession.d.ts +111 -0
  54. package/dist/ot-js/ottypes.d.ts +29 -0
  55. package/dist/poly/all.d.ts +15 -0
  56. package/dist/poly/blend.d.ts +1 -0
  57. package/dist/poly/boundbox.d.ts +16 -0
  58. package/dist/poly/cartesian.d.ts +5 -0
  59. package/dist/poly/graham-scan.d.ts +8 -0
  60. package/dist/poly/hash.d.ts +1 -0
  61. package/dist/poly/matrix.d.ts +24 -0
  62. package/dist/poly/minbound.d.ts +1 -0
  63. package/dist/poly/poly.d.ts +52 -0
  64. package/dist/poly/polybin.d.ts +5 -0
  65. package/dist/poly/polylabel.d.ts +7 -0
  66. package/dist/poly/polypack.d.ts +30 -0
  67. package/dist/poly/polyround.d.ts +1 -0
  68. package/dist/poly/polysimplify.d.ts +1 -0
  69. package/dist/poly/quad.d.ts +48 -0
  70. package/dist/poly/selfintersect.d.ts +1 -0
  71. package/dist/poly/shamos.d.ts +1 -0
  72. package/dist/poly/simplify.d.ts +2 -0
  73. package/dist/poly/topo.d.ts +46 -0
  74. package/dist/poly/union.d.ts +48 -0
  75. package/dist/storage/all.d.ts +4 -0
  76. package/dist/storage/datablob.d.ts +9 -0
  77. package/dist/storage/env.d.ts +10 -0
  78. package/dist/storage/splitsblob.d.ts +13 -0
  79. package/dist/storage/storage.d.ts +166 -0
  80. package/dist/storages3/all.d.ts +1 -0
  81. package/dist/storages3/s3.d.ts +62 -0
  82. package/dist/util/all.d.ts +5 -0
  83. package/dist/util/bintrie.d.ts +93 -0
  84. package/dist/util/countedhash.d.ts +19 -0
  85. package/dist/util/gradient.d.ts +15 -0
  86. package/dist/util/indexedarray.d.ts +15 -0
  87. package/dist/util/util.d.ts +68 -0
  88. package/docs/context.md +2 -0
  89. package/docs/dbabstract.md +2 -0
  90. package/docs/dbdynamo.md +2 -0
  91. package/docs/fsm.md +243 -0
  92. package/docs/fsmfile.md +2 -0
  93. package/docs/jsonstream.md +44 -0
  94. package/docs/lambda.md +2 -0
  95. package/docs/logabstract.md +2 -0
  96. package/docs/logclient.md +2 -0
  97. package/docs/logserver.md +2 -0
  98. package/docs/ot-editutil.md +2 -0
  99. package/docs/ot-js.md +95 -0
  100. package/docs/poly.md +103 -0
  101. package/docs/storage.md +2 -0
  102. package/docs/storages3.md +2 -0
  103. package/docs/util.md +2 -0
  104. package/lib/all/all.ts +41 -0
  105. package/lib/all/allclient.ts +19 -0
  106. package/lib/context/all.ts +1 -0
  107. package/lib/context/context.ts +82 -0
  108. package/lib/dbabstract/all.ts +1 -0
  109. package/lib/dbabstract/db.ts +246 -0
  110. package/lib/dbdynamo/all.ts +1 -0
  111. package/lib/dbdynamo/dbdynamo.ts +1551 -0
  112. package/lib/filterexpr/all.ts +1 -0
  113. package/lib/filterexpr/filterexpr.ts +625 -0
  114. package/lib/fsm/all.ts +1 -0
  115. package/lib/fsm/fsm.ts +549 -0
  116. package/lib/fsmfile/all.ts +1 -0
  117. package/lib/fsmfile/fsmfile.ts +236 -0
  118. package/lib/jsonstream/all.ts +1 -0
  119. package/lib/jsonstream/jsonstream.ts +940 -0
  120. package/lib/lambda/all.ts +1 -0
  121. package/lib/lambda/env.ts +13 -0
  122. package/lib/lambda/lambda.ts +120 -0
  123. package/lib/logabstract/all.ts +1 -0
  124. package/lib/logabstract/log.ts +55 -0
  125. package/lib/logclient/all.ts +1 -0
  126. package/lib/logclient/log.ts +105 -0
  127. package/lib/logserver/all.ts +5 -0
  128. package/lib/logserver/log.ts +565 -0
  129. package/lib/logserver/logaccum.ts +1445 -0
  130. package/lib/logserver/logblob.ts +84 -0
  131. package/lib/logserver/logconcat.ts +313 -0
  132. package/lib/logserver/logkey.ts +125 -0
  133. package/lib/memsqs/all.ts +4 -0
  134. package/lib/memsqs/client.ts +268 -0
  135. package/lib/memsqs/loopback.ts +64 -0
  136. package/lib/memsqs/orderedlist.ts +74 -0
  137. package/lib/memsqs/queue.ts +395 -0
  138. package/lib/memsqs/server.ts +262 -0
  139. package/lib/ot-editutil/all.ts +2 -0
  140. package/lib/ot-editutil/oteditutil.ts +180 -0
  141. package/lib/ot-editutil/otmaputil.ts +209 -0
  142. package/lib/ot-js/all.ts +9 -0
  143. package/lib/ot-js/otarray.ts +1168 -0
  144. package/lib/ot-js/otclientengine.ts +327 -0
  145. package/lib/ot-js/otcomposite.ts +247 -0
  146. package/lib/ot-js/otcounter.ts +145 -0
  147. package/lib/ot-js/otengine.ts +71 -0
  148. package/lib/ot-js/otmap.ts +144 -0
  149. package/lib/ot-js/otserverengine.ts +329 -0
  150. package/lib/ot-js/otsession.ts +199 -0
  151. package/lib/ot-js/ottypes.ts +98 -0
  152. package/lib/poly/all.ts +15 -0
  153. package/lib/poly/blend.ts +27 -0
  154. package/lib/poly/boundbox.ts +102 -0
  155. package/lib/poly/cartesian.ts +130 -0
  156. package/lib/poly/graham-scan.ts +401 -0
  157. package/lib/poly/hash.ts +15 -0
  158. package/lib/poly/matrix.ts +309 -0
  159. package/lib/poly/minbound.ts +211 -0
  160. package/lib/poly/poly.ts +767 -0
  161. package/lib/poly/polybin.ts +218 -0
  162. package/lib/poly/polylabel.ts +204 -0
  163. package/lib/poly/polypack.ts +458 -0
  164. package/lib/poly/polyround.ts +30 -0
  165. package/lib/poly/polysimplify.ts +24 -0
  166. package/lib/poly/quad.ts +272 -0
  167. package/lib/poly/selfintersect.ts +87 -0
  168. package/lib/poly/shamos.ts +297 -0
  169. package/lib/poly/simplify.ts +119 -0
  170. package/lib/poly/topo.ts +525 -0
  171. package/lib/poly/union.ts +371 -0
  172. package/lib/storage/all.ts +4 -0
  173. package/lib/storage/datablob.ts +36 -0
  174. package/lib/storage/env.ts +14 -0
  175. package/lib/storage/splitsblob.ts +63 -0
  176. package/lib/storage/storage.ts +604 -0
  177. package/lib/storages3/all.ts +1 -0
  178. package/lib/storages3/s3.ts +576 -0
  179. package/lib/util/all.ts +5 -0
  180. package/lib/util/bintrie.ts +603 -0
  181. package/lib/util/countedhash.ts +83 -0
  182. package/lib/util/gradient.ts +108 -0
  183. package/lib/util/indexedarray.ts +80 -0
  184. package/lib/util/util.ts +695 -0
  185. 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
+ }