From 44f5d5edd41441a7cdbaab71eb2cd3351b8a0d28 Mon Sep 17 00:00:00 2001 From: sveisvei 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(); + }); +};