From f5de8312c685b7550395afa6c2e8176cee7ad859 Mon Sep 17 00:00:00 2001
From: sveisvei <sveinung.rosaker@gmail.com>
Date: Thu, 27 Oct 2016 13:13:51 +0200
Subject: [PATCH] add client-metrics endpoint and in-memory client-metrics

---
 packages/unleash-api/lib/client-metrics.js | 97 ++++++++++++++++++++++
 packages/unleash-api/lib/routes/index.js   |  1 +
 packages/unleash-api/lib/routes/metrics.js | 24 ++++++
 3 files changed, 122 insertions(+)
 create mode 100644 packages/unleash-api/lib/client-metrics.js
 create mode 100644 packages/unleash-api/lib/routes/metrics.js

diff --git a/packages/unleash-api/lib/client-metrics.js b/packages/unleash-api/lib/client-metrics.js
new file mode 100644
index 0000000000..38c8409e9d
--- /dev/null
+++ b/packages/unleash-api/lib/client-metrics.js
@@ -0,0 +1,97 @@
+'use strict';
+
+module.exports = class UnleashClientMetrics {
+    constructor () {
+        this.globalCount = 0;
+        this.apps = [];
+        this.clients = {};
+        this.strategies = {};
+        this.store = {};
+    }
+
+    toJSON () {
+        return JSON.stringify(this.getState(), null, 4);
+    }
+
+    getState () {
+        // TODO this payload will be WAY to big, need to flatten the store
+        // and possibly evict/flag stale clients
+        return {
+            globalCount: this.globalCount,
+            apps: this.apps,
+            clients: this.clients,
+            strategies: this.strategies,
+            store: this.store,
+        };
+    }
+
+    addPayload (data) {
+        this.addApp(data.appName);
+        this.addClient(data.appName, data.instanceId, data.clientInitTime);
+        this.addStrategies(data.appName, data.strategies);
+        this.addStore(data.appName, data.instanceId, data.store);
+    }
+
+    addStore (appName, instanceId, instanceStore) {
+        // TODO normalize time client-server-time / NTP?
+        const normalizeTimeEntries = (entry) => Object.assign({ appName, instanceId }, entry);
+        let count = 0;
+
+        Object.keys(instanceStore).forEach((n) => {
+            if (n.startsWith('_')) {
+                return;
+            }
+            if (this.store[n]) {
+                this.store[n].yes = this.store[n].yes.concat(instanceStore[n].yes.map(normalizeTimeEntries));
+                this.store[n].no = this.store[n].no.concat(instanceStore[n].no.map(normalizeTimeEntries));
+            } else {
+                this.store[n] = {
+                    yes: instanceStore[n].yes.map(normalizeTimeEntries),
+                    no: instanceStore[n].no.map(normalizeTimeEntries),
+                };
+            }
+            count += (instanceStore[n].yes.length + instanceStore[n].no.length);
+        });
+        this.addClientCount(instanceId, count);
+    }
+
+    addStrategies (appName, strategyNames) {
+        strategyNames.forEach((name) => {
+            if (!this.strategies[name]) {
+                this.strategies[name] = {};
+            }
+            this.strategies[name][appName] = true;
+        });
+    }
+
+    addClientCount (instanceId, count) {
+        if (typeof count === 'number' && count > 0) {
+            this.globalCount += count;
+            if (this.clients[instanceId]) {
+                this.clients[instanceId].count += count;
+            }
+        }
+    }
+
+    addClient (appName, instanceId, clientInitTime) {
+        if (instanceId) {
+            if (this.clients[instanceId]) {
+                this.clients[instanceId].ping = new Date();
+            } else {
+                this.clients[instanceId] = {
+                    appName,
+                    count: 0,
+                    clientInit: clientInitTime,
+                    init: new Date(),
+                    ping: new Date(),
+                };
+            }
+        }
+    }
+
+    addApp (v) {
+        if (v && !this.apps.includes(v)) {
+            this.apps.push(v);
+        }
+    }
+};
diff --git a/packages/unleash-api/lib/routes/index.js b/packages/unleash-api/lib/routes/index.js
index 49e7bc73cc..de093eef07 100644
--- a/packages/unleash-api/lib/routes/index.js
+++ b/packages/unleash-api/lib/routes/index.js
@@ -6,4 +6,5 @@ exports.create = function (app, config) {
     require('./feature-archive')(app, config);
     require('./strategy')(app, config);
     require('./health-check')(app, config);
+    require('./metrics')(app, config);
 };
diff --git a/packages/unleash-api/lib/routes/metrics.js b/packages/unleash-api/lib/routes/metrics.js
new file mode 100644
index 0000000000..408444a6ea
--- /dev/null
+++ b/packages/unleash-api/lib/routes/metrics.js
@@ -0,0 +1,24 @@
+'use strict';
+
+const logger = require('../logger');
+const ClientMetrics = require('../client-metrics');
+
+module.exports = function (app) {
+    const metrics = new ClientMetrics();
+
+    app.get('/metrics', (req, res) => {
+        res.json(metrics.getState());
+    });
+
+    app.post('/metrics', (req, res) => {
+        // TODO: validate input and reply with http errorcode
+        try {
+            const data = JSON.parse(req.body);
+            metrics.addPayload(data);
+        } catch (e) {
+            logger.error('Error recieving metrics', e);
+        }
+
+        res.end();
+    });
+};