• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

suculent / thinx-device-api / #252646970

27 Oct 2017 03:17PM UTC coverage: 12.466% (+1.3%) from 11.197%
#252646970

push

suculent
added support for displaying/exporting extended SigFox attributes

37 of 1808 branches covered (2.05%)

Branch coverage included in aggregate %.

735 of 4385 relevant lines covered (16.76%)

0.17 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

9.14
/lib/thinx/statistics.js
1
/*
2
 * This THiNX-RTM API module is responsible for aggregating daily statistics.
3
 */
4

5
var Statistics = (function() {
1✔
6

7
        var app_config = require("../../conf/config.json");
1✔
8
        if (typeof(process.env.CIRCLE_USERNAME) !== "undefined") {
1!
9
                console.log("ยป Configuring for Circle CI...");
×
10
                app_config = require("../../conf/config-test.json");
×
11
        }
12
        var ROOT = app_config.project_root;
1✔
13

14
        var LOG_PATH = ROOT + "/../.pm2/logs/index-out-0.log";
1✔
15
        var TEMP_PATH = ROOT + "/statistics/stats.temp";
1✔
16
        var STATS_PATH = ROOT + "/statistics/";
1✔
17

18
        var mkdirp = require("mkdirp");
1✔
19
        var dateFormat = require("dateformat");
1✔
20
        var fs = require("fs-extra");
1✔
21
        var parse = require("parse-date");
1✔
22

23
        var once = false;
1✔
24
        var path;
25
        var exit_callback;
26
        var parser = null;
1✔
27
        var _owner = null;
1✔
28

29
        // Create statistics folder if not found on init
30
        if (!fs.existsSync(STATS_PATH)) {
1!
31
                console.log("Creating " + STATS_PATH + " from " + __dirname);
1✔
32
                mkdirp(STATS_PATH, function(err) {
1✔
33
                        if (err) console.log(err);
1!
34
                        //else console.log(STATS_PATH + ' created.');
35
                });
36
        }
37

38
        var readline = require("readline");
1✔
39
        var owners = {};
1✔
40

41
        // Defines data model and also lists known tracked keys
42
        var owner_stats_template = {
1✔
43
                APIKEY_INVALID: [0],
44
                LOGIN_INVALID: [0],
45
                APIKEY_MISUSE: [0],
46
                APIKEY_REVOKED: [0],
47
                DEVICE_NEW: [0],
48
                DEVICE_CHECKIN: [0],
49
                DEVICE_UPDATE_OK: [0], // TODO: not implemented
50
                DEVICE_UPDATE_FAIL: [0], // TODO: not implemented
51
                DEVICE_REVOCATION: [0],
52
                BUILD_STARTED: [0],
53
                BUILD_SUCCESS: [0],
54
                BUILD_FAIL: [0]
55
        };
56

57
        // This is called after parse completes. Should return
58
        // existing pre-parsed file from path or nothing if parser failed.
59
        function parse_callback(path) {
60
                if (typeof(exit_callback) !== "function") {
×
61
                        // exit_callback not set for parse_callback [coule be when called from aggregator]
62
                        return;
×
63
                }
64
                if (fs.existsSync(path)) {
×
65
                        console.log("[parse_callback] Reading stats from existing: " + path); // FIXME
×
66
                        fs.readFile(path, "utf-8", function(err, statistics) {
×
67
                                if (err) {
×
68
                                        console.log("[parse_callback] Read error: " + err);
×
69
                                        exit_callback(false, "error");
×
70
                                        return;
×
71
                                } else {
72
                                        if (!statistics) {
×
73
                                                exit_callback(false, "error");
×
74
                                        } else {
75
                                                exit_callback(true, statistics);
×
76
                                        }
77
                                }
78
                                return;
×
79
                        });
80
                } else {
81
                        console.log("[parse_callback] Stats file not found at: " + path);
×
82
                        exit_callback(false, "parse_callback_file_not_found");
×
83
                        return;
×
84
                }
85
        }
86

87
        var _private = {
1✔
88

89
                globalPath: function() {
90
                        return STATS_PATH;
×
91
                },
92

93
                ownerPath: function(owner) {
94
                        return STATS_PATH + owner;
×
95
                }
96

97
        };
98

99
        // public
100
        var _public = {
1✔
101

102
                write_stats: function(err, path, dirpath, owner_data) {
103

104
                        var write = function(path, owner_data) {
×
105
                                fs.writeFileSync(
×
106
                                        path,
107
                                        JSON.stringify(owner_data), {
108
                                                encoding: "utf-8",
109
                                                flag: "wx",
110
                                                mode: 493
111
                                        } // 493 == 0755
112
                                );
113
                        };
114

115
                        if (err) {
×
116
                                console.log("[WRITE] Write-stats error: " + err);
×
117
                        } else {
118
                                mkdirp(dirpath, function(err) {
×
119
                                        if (err) {
×
120
                                                console.log("[AGGREGATE] Write-stats mkdirp error: " +
×
121
                                                        err);
122
                                        } else {
123
                                                if (fs.existsSync(path)) {
×
124
                                                        fs.unlink(path, function(err) {
×
125
                                                                write(path, owner_data);
×
126
                                                        });
127
                                                } else {
128
                                                        write(path, owner_data);
×
129
                                                }
130
                                        }
131
                                });
132
                        }
133
                },
134

135
                /**
136
                 * Performs ETL transaction from current log
137
                 */
138

139
                parse: function(owner, today, completed_callback) {
140

141
                        if (!fs.existsSync(LOG_PATH)) {
×
142
                                console.log("[PARSE] THiNX log not found at:" + LOG_PATH);
×
143
                                return false; // no log found
×
144
                        }
145

146
                        fs.copy(LOG_PATH, TEMP_PATH, function(err) {
×
147

148
                                if (err !== null) {
×
149
                                        console.log(err);
×
150
                                        return false;
×
151
                                }
152

153
                                parser = readline.createInterface({
×
154
                                        input: fs.createReadStream(TEMP_PATH),
155
                                        output: null,
156
                                        console: false,
157
                                        terminal: false
158
                                });
159

160
                                if ((typeof(parser) === "undefined") && (parser !== null)) {
×
161
                                        console.log("[ERROR][PARSER] could not be instantiated in PARSE!");
×
162
                                        return;
×
163
                                }
164

165
                                owners[owner] = owner_stats_template;
×
166

167
                                parser.on("line", function(line) {
×
168

169
                                        if (line.indexOf("[OID:") !== 1) {
×
170

171
                                                var scanline = line.split("]");
×
172

173
                                                /* TODO: Use this to parse date... will be used for splitting day to hours
174
                                                if (scanline.length > 0) {
175
                                                        var dtemp = scanline[0].substr(1);
176
                                                } */
177

178
                                                var oid = owner;
×
179
                                                if (scanline.length > 2) {
×
180
                                                        var otemp = scanline[2];
×
181
                                                        if (otemp.indexOf("[OID:") !== -1) {
×
182
                                                                oid = otemp.substr(3).replace("[OID:", "");
×
183
                                                                if (oid != owner) {
×
184
                                                                        return; // untracked OIDs are skipped in this method
×
185
                                                                }
186
                                                        }
187
                                                }
188

189
                                                var d = new Date();
×
190
                                                var dtemp = scanline[0];
×
191
                                                // TODO: convert dtemp to date and skip if not today's
192
                                                if (dtemp.indexOf("[") === 0) {
×
193
                                                        var stringdate = dtemp.replace("[", "");
×
194
                                                        var ld;
195
                                                        try {
×
196
                                                                ld = parse(stringdate);
×
197
                                                        } catch (e) {
198
                                                                return;
×
199
                                                        }
200
                                                        var datestring = ld.getDate() + "-" + (ld.getMonth() + 1) + "-" +
×
201
                                                                ld.getFullYear();
202
                                                        var nowstring = d.getDate() + "-" + (d.getMonth() + 1) + "-" + d
×
203
                                                                .getFullYear();
204
                                                        if (datestring !== nowstring) {
×
205
                                                                if (today) {
×
206
                                                                        console.log("skipping off-date record at " + datestring);
×
207
                                                                        return;
×
208
                                                                }
209
                                                        }
210
                                                        //console.log("linedate: " + linedate.toDateString());
211
                                                }
212

213
                                                // TODO: Extract as 'owners = updateOwnersWithLine(line)'
214

215
                                                if (line.indexOf("APIKEY_INVALID") !== -1) {
×
216
                                                        owners[oid].APIKEY_INVALID[0]++;
×
217
                                                } else if (line.indexOf("LOGIN_INVALID") !== -1) {
×
218
                                                        owners[oid].LOGIN_INVALID[0]++;
×
219
                                                } else if (line.indexOf("APIKEY_MISUSE") !== -1) {
×
220
                                                        owners[oid].APIKEY_MISUSE[0]++;
×
221
                                                } else if (line.indexOf("APIKEY_REVOKED") !== -1) {
×
222
                                                        owners[oid].APIKEY_REVOKED[0]++;
×
223
                                                } else if (line.indexOf("DEVICE_NEW") !== -1) {
×
224
                                                        owners[oid].DEVICE_NEW[0]++;
×
225
                                                } else if (line.indexOf("DEVICE_CHECKIN") !== -1) {
×
226
                                                        owners[oid].DEVICE_CHECKIN[0]++;
×
227
                                                } else if (line.indexOf("DEVICE_UPDATE_OK") !== -1) {
×
228
                                                        owners[oid].DEVICE_UPDATE_OK[0]++;
×
229
                                                } else if (line.indexOf("DEVICE_UPDATE_FAIL") !== -1) {
×
230
                                                        owners[oid].DEVICE_UPDATE_FAIL[0]++;
×
231
                                                } else if (line.indexOf("DEVICE_REVOCATION") !== -1) {
×
232
                                                        owners[oid].DEVICE_REVOCATION[0]++;
×
233
                                                } else if (line.indexOf("BUILD_STARTED") !== -1) {
×
234
                                                        owners[oid].BUILD_STARTED[0]++;
×
235
                                                } else if (line.indexOf("BUILD_SUCCESS") !== -1) {
×
236
                                                        owners[oid].BUILD_SUCCESS[0] += 1;
×
237
                                                } else if (line.indexOf("BUILD_FAIL") !== -1) {
×
238
                                                        owners[oid].BUILD_FAIL[0]++;
×
239
                                                }
240
                                        }
241
                                });
242

243
                                parser.on("close", function(line) {
×
244
                                        for (var owner_id in owners) {
×
245
                                                var owner_data = owners[owner_id];
×
246
                                                var dirpath = STATS_PATH + owner_id;
×
247
                                                var path = dirpath + "/" + _public.todayPathElement() + ".json";
×
248
                                                _public.write_stats(false, path, dirpath, owner_data);
×
249
                                        }
250
                                        if (typeof(exit_callback) === "function") {
×
251
                                                exit_callback(true, owners[owner_id]);
×
252
                                        }
253
                                        if (fs.existsSync(TEMP_PATH)) {
×
254
                                                fs.unlink(TEMP_PATH);
×
255
                                        }
256
                                });
257
                        });
258
                },
259

260
                aggregate: function() {
261

262
                        if (!fs.existsSync(LOG_PATH)) {
×
263
                                console.log("[AGGREGATE] THiNX log not found at:" + LOG_PATH);
×
264
                                return true; // no log found
×
265
                        }
266

267
                        fs.copy(LOG_PATH, TEMP_PATH, function(err) {
×
268

269
                                if (err) return console.log(err);
×
270

271
                                if (err !== null) {
×
272
                                        console.log(err); // FIXME
×
273
                                        return false;
×
274
                                }
275

276
                                parser = readline.createInterface({
×
277
                                        input: fs.createReadStream(TEMP_PATH),
278
                                        output: null,
279
                                        console: false,
280
                                        terminal: false
281
                                });
282

283
                                if ((typeof(parser) === "undefined") || (parser === null)) {
×
284
                                        console.log(
×
285
                                                "[ERROR][PARSER] could not be instantiated in AGGREGATE!");
286
                                        return;
×
287
                                }
288

289
                                parser.on("line", function(line) {
×
290

291
                                        if (line.indexOf("[OID:") != 1) {
×
292

293
                                                // Split by bracket-space first...
294
                                                var scanline = line.split("]");
×
295

296
                                                /* TODO: Use this to parse date... will be used for splitting day to hours
297
                                                if (scanline.length > 0) {
298
                                                        var dtemp = scanline[0].substr(1);
299
                                                } */
300

301
                                                var oid =
302
                                                        "cedc16bb6bb06daaa3ff6d30666d91aacd6e3efbf9abbc151b4dcade59af7c12";
×
303
                                                if (scanline.length > 2) {
×
304
                                                        var otemp = scanline[2];
×
305
                                                        if (otemp.indexOf("[OID:") != -1) {
×
306
                                                                oid = otemp.substr(3).replace("[OID:", "");
×
307
                                                        }
308
                                                }
309

310
                                                if (typeof(owners[oid]) === "undefined") {
×
311
                                                        owners[oid] = owner_stats_template; // init stat object per owner
×
312
                                                }
313

314
                                                if (line.indexOf("APIKEY_INVALID") != -1) {
×
315
                                                        owners[oid].APIKEY_INVALID[0]++;
×
316
                                                } else if (line.indexOf("LOGIN_INVALID") != -1) {
×
317
                                                        owners[oid].LOGIN_INVALID[0]++;
×
318
                                                } else if (line.indexOf("APIKEY_MISUSE") != -1) {
×
319
                                                        owners[oid].APIKEY_MISUSE[0]++;
×
320
                                                } else if (line.indexOf("APIKEY_REVOKED") != -1) {
×
321
                                                        owners[oid].APIKEY_REVOKED[0]++;
×
322
                                                } else if (line.indexOf("DEVICE_NEW") != -1) {
×
323
                                                        owners[oid].DEVICE_NEW[0]++;
×
324
                                                } else if (line.indexOf("DEVICE_CHECKIN") != -1) {
×
325
                                                        owners[oid].DEVICE_CHECKIN[0]++;
×
326
                                                } else if (line.indexOf("DEVICE_UPDATE_OK") != -1) {
×
327
                                                        owners[oid].DEVICE_UPDATE_OK[0]++;
×
328
                                                } else if (line.indexOf("DEVICE_UPDATE_FAIL") != -1) {
×
329
                                                        owners[oid].DEVICE_UPDATE_FAIL[0]++;
×
330
                                                } else if (line.indexOf("DEVICE_REVOCATION") != -1) {
×
331
                                                        owners[oid].DEVICE_REVOCATION[0]++;
×
332
                                                } else if (line.indexOf("BUILD_STARTED") != -1) {
×
333
                                                        owners[oid].BUILD_STARTED[0]++;
×
334
                                                } else if (line.indexOf("BUILD_SUCCESS") != -1) {
×
335
                                                        owners[oid].BUILD_SUCCESS[0] += 1;
×
336
                                                } else if (line.indexOf("BUILD_FAIL") != -1) {
×
337
                                                        owners[oid].BUILD_FAIL[0]++;
×
338
                                                }
339
                                        }
340
                                });
341

342
                                parser.on("close", function(line) {
×
343
                                        for (var owner_id in owners) {
×
344
                                                var owner_data = owners[owner_id];
×
345
                                                var dirpath = STATS_PATH + owner_id;
×
346
                                                path = dirpath + "/" + _public.todayPathElement() + ".json";
×
347
                                                _public.write_stats(false, path, dirpath, owner_data);
×
348
                                        }
349

350
                                        if (_owner !== null) {
×
351
                                                path = STATS_PATH + _owner + "/" + _public.todayPathElement() +
×
352
                                                        ".json";
353
                                        }
354

355
                                        // Expects global callback being set already
356
                                        if ((typeof(parse_callback) === "function") && parse_callback !==
×
357
                                                null) {
358
                                                parse_callback(path);
×
359
                                        }
360
                                        if (fs.existsSync(TEMP_PATH)) {
×
361
                                                fs.unlink(TEMP_PATH);
×
362
                                        }
363
                                });
364
                        });
365
                        return true;
×
366
                },
367

368
                /**
369
                 * Returns today data created by ETL if available
370
                 * @param {string} owner - restrict to owner
371
                 * @param {function} callback (err, statistics) - async return callback, returns statistics or error
372
                 */
373

374
                today: function(owner, callback) {
375

376
                        exit_callback = callback;
×
377
                        _owner = owner;
×
378

379
                        var xpath = _private.ownerPath(owner) + "/" +
×
380
                                _public.todayPathElement() + ".json";
381
                        console.log("xpath: " + xpath); // FIXME
×
382
                        this.path = path;
×
383
                        path = xpath;
×
384

385
                        if (!fs.existsSync(path)) {
×
386
                                console.log("[STATS] Statistics not found...");
×
387
                                if (once === false) {
×
388
                                        console.log("[TODAY-N-ONCE] Parsing today data..."); // FIXME
×
389
                                        once = true;
×
390
                                        _public.aggregate();
×
391
                                } else {
392
                                        exit_callback(false, "no_stats_found");
×
393
                                }
394
                        } else {
395
                                once = false;
×
396
                                console.log("[STATS] Fetching recent statistics..."); // FIXME
×
397
                                var today = true;
×
398
                                _public.parse(owner, today, parse_callback(path));
×
399
                        }
400
                },
401

402
                /**
403
                 * Returns weekly data created by ETL if available
404
                 * @param {string} owner - restrict to owner
405
                 * @param {function} callback (err, statistics) - async return callback, returns statistics or error
406
                 */
407

408
                week: function(owner, callback) {
409

410
                        // console.log("[OID:" + owner + "] [STATS_WEEKLY_BAGR]");
411

412
                        var atLeastOneFileFound = false;
×
413

414
                        var results = {};
×
415

416
                        for (var d = 7; d > 0; d--) {
×
417

418
                                var wpath = _private.ownerPath(owner) + "/" + _public.weekPathElement(
×
419
                                                d) +
420
                                        ".json";
421

422
                                if (fs.existsSync(wpath)) {
×
423
                                        atLeastOneFileFound = true;
×
424
                                        var jsonData = fs.readFileSync(wpath);
×
425

426
                                        var data = JSON.parse(jsonData);
×
427

428
                                        if (typeof(data) === "undefined") {
×
429
                                                continue;
×
430
                                        }
431

432
                                        var skeys = Object.keys(data);
×
433
                                        for (var kindex in skeys) {
×
434
                                                var keyname = skeys[kindex];
×
435
                                                if (typeof(results[keyname]) === "undefined") {
×
436
                                                        results[keyname] = [];
×
437
                                                }
438
                                                var keydata = data[keyname][0];
×
439
                                                if (keydata) {
×
440
                                                        results[keyname].push(keydata);
×
441
                                                } else {
442
                                                        results[keyname].push(0);
×
443
                                                }
444
                                        }
445
                                }
446
                        }
447

448
                        callback(atLeastOneFileFound, results);
×
449

450
                },
451

452
                todayPathElement: function() {
453
                        var today = dateFormat(new Date(), "isoDate");
×
454
                        return today;
×
455
                },
456

457
                weekPathElement: function(daysBack) {
458
                        return dateFormat(new Date((Date.now() - (86400000 * daysBack))),
×
459
                                "isoDate");
460
                }
461

462
        };
463

464
        return _public;
1✔
465

466
})();
467

468
exports.parse = Statistics.parse;
1✔
469
exports.aggregate = Statistics.aggregate;
1✔
470
exports
1✔
471
        .today = Statistics.today;
472
exports.week = Statistics.week;
1✔
473

474
// test
475
exports.todayPathElement = Statistics.todayPathElement;
1✔
476
exports.write_stats =
1✔
477
        Statistics.write_stats;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc