1
0
mirror of https://github.com/Unleash/unleash.git synced 2024-10-23 20:07:40 +02:00
unleash.unleash/lib/services/client-metrics/index.js

276 lines
8.7 KiB
JavaScript
Raw Normal View History

/* eslint-disable no-param-reassign */
'use strict';
2016-11-04 16:16:55 +01:00
const Projection = require('./projection.js');
const TTLList = require('./ttl-list.js');
const appSchema = require('./metrics-schema');
const NotFoundError = require('../../error/notfound-error');
const { clientMetricsSchema } = require('./client-metrics-schema');
const { clientRegisterSchema } = require('./register-schema');
const { APPLICATION_CREATED } = require('../../event-type');
2016-11-04 16:16:55 +01:00
module.exports = class ClientMetricsService {
constructor(
{
clientMetricsStore,
strategyStore,
featureToggleStore,
clientApplicationsStore,
clientInstanceStore,
eventStore,
},
{ getLogger },
) {
this.globalCount = 0;
2016-11-04 16:16:55 +01:00
this.apps = {};
this.strategyStore = strategyStore;
this.toggleStore = featureToggleStore;
this.clientAppStore = clientApplicationsStore;
this.clientInstanceStore = clientInstanceStore;
this.clientMetricsStore = clientMetricsStore;
2016-11-07 09:13:19 +01:00
this.lastHourProjection = new Projection();
this.lastMinuteProjection = new Projection();
this.eventStore = eventStore;
2016-11-07 09:13:19 +01:00
this.lastHourList = new TTLList({
interval: 10000,
});
this.logger = getLogger('services/client-metrics/index.js');
2016-11-07 09:13:19 +01:00
this.lastMinuteList = new TTLList({
interval: 10000,
expireType: 'minutes',
expireAmount: 1,
});
2017-06-28 10:17:14 +02:00
this.lastHourList.on('expire', toggles => {
2016-11-04 16:16:55 +01:00
Object.keys(toggles).forEach(toggleName => {
2017-06-28 10:17:14 +02:00
this.lastHourProjection.substract(
toggleName,
this.createCountObject(toggles[toggleName]),
2017-06-28 10:17:14 +02:00
);
2016-11-07 09:13:19 +01:00
});
});
2017-06-28 10:17:14 +02:00
this.lastMinuteList.on('expire', toggles => {
2016-11-07 09:13:19 +01:00
Object.keys(toggles).forEach(toggleName => {
2017-06-28 10:17:14 +02:00
this.lastMinuteProjection.substract(
toggleName,
this.createCountObject(toggles[toggleName]),
2017-06-28 10:17:14 +02:00
);
2016-11-04 16:16:55 +01:00
});
});
2017-06-28 10:17:14 +02:00
clientMetricsStore.on('metrics', m => this.addPayload(m));
}
async registerClientMetrics(data, clientIp) {
const value = await clientMetricsSchema.validateAsync(data);
const toggleNames = Object.keys(value.bucket.toggles);
await this.toggleStore.lastSeenToggles(toggleNames);
await this.clientMetricsStore.insert(value);
await this.clientInstanceStore.insert({
appName: value.appName,
instanceId: value.instanceId,
clientIp,
});
}
async upsertApp(value, clientIp) {
try {
const app = await this.clientAppStore.getApplication(value.appName);
await this.updateRow(value, app);
} catch (error) {
if (error instanceof NotFoundError) {
await this.clientAppStore.insertNewRow(value);
await this.eventStore.store({
type: APPLICATION_CREATED,
createdBy: clientIp,
data: value,
});
}
}
}
async registerClient(data, clientIp) {
const value = await clientRegisterSchema.validateAsync(data);
value.clientIp = clientIp;
await this.upsertApp(value, clientIp);
await this.clientInstanceStore.insert(value);
this.logger.info(
`New client registration: appName=${value.appName}, instanceId=${value.instanceId}`,
);
}
2017-06-28 10:17:14 +02:00
getAppsWithToggles() {
const apps = [];
Object.keys(this.apps).forEach(appName => {
const seenToggles = Object.keys(this.apps[appName].seenToggles);
const metricsCount = this.apps[appName].count;
2016-12-04 14:09:37 +01:00
apps.push({ appName, seenToggles, metricsCount });
});
return apps;
}
2017-06-28 10:17:14 +02:00
getSeenTogglesByAppName(appName) {
return this.apps[appName]
? Object.keys(this.apps[appName].seenToggles)
: [];
}
async getSeenApps() {
const seenApps = this.getSeenAppsPerToggle();
const applications = await this.clientAppStore.getApplications();
const metaData = applications.reduce((result, entry) => {
// eslint-disable-next-line no-param-reassign
result[entry.appName] = entry;
return result;
}, {});
Object.keys(seenApps).forEach(key => {
seenApps[key] = seenApps[key].map(entry => {
if (metaData[entry.appName]) {
return { ...entry, ...metaData[entry.appName] };
}
return entry;
});
});
return seenApps;
}
async getApplications(query) {
return this.clientAppStore.getApplications(query);
}
async getApplication(appName) {
const seenToggles = this.getSeenTogglesByAppName(appName);
const [
application,
instances,
strategies,
features,
] = await Promise.all([
this.clientAppStore.getApplication(appName),
this.clientInstanceStore.getByAppName(appName),
this.strategyStore.getStrategies(),
this.toggleStore.getFeatures(),
]);
return {
appName: application.appName,
createdAt: application.createdAt,
description: application.description,
url: application.url,
color: application.color,
icon: application.icon,
strategies: application.strategies.map(name => {
const found = strategies.find(f => f.name === name);
return found || { name, notFound: true };
}),
instances,
seenToggles: seenToggles.map(name => {
const found = features.find(f => f.name === name);
return found || { name, notFound: true };
}),
links: {
self: `/api/applications/${application.appName}`,
},
};
}
2017-06-28 10:17:14 +02:00
getSeenAppsPerToggle() {
2016-12-05 13:53:53 +01:00
const toggles = {};
2016-12-05 13:27:08 +01:00
Object.keys(this.apps).forEach(appName => {
2017-11-11 08:43:08 +01:00
Object.keys(this.apps[appName].seenToggles).forEach(
seenToggleName => {
if (!toggles[seenToggleName]) {
toggles[seenToggleName] = [];
}
toggles[seenToggleName].push({ appName });
},
2017-11-11 08:43:08 +01:00
);
2016-12-05 13:27:08 +01:00
});
2016-12-05 13:53:53 +01:00
return toggles;
2016-12-05 13:27:08 +01:00
}
2017-06-28 10:17:14 +02:00
getTogglesMetrics() {
2016-11-07 09:13:19 +01:00
return {
lastHour: this.lastHourProjection.getProjection(),
lastMinute: this.lastMinuteProjection.getProjection(),
};
2016-11-04 16:16:55 +01:00
}
2017-06-28 10:17:14 +02:00
addPayload(data) {
const { appName, bucket } = data;
2016-12-04 14:09:37 +01:00
const app = this.getApp(appName);
this.addBucket(app, bucket);
}
2017-06-28 10:17:14 +02:00
getApp(appName) {
this.apps[appName] = this.apps[appName] || {
seenToggles: {},
count: 0,
};
return this.apps[appName];
}
createCountObject(entry) {
let yes = typeof entry.yes === 'number' ? entry.yes : 0;
let no = typeof entry.no === 'number' ? entry.no : 0;
2019-01-25 13:05:25 +01:00
if (entry.variants) {
Object.entries(entry.variants).forEach(([key, value]) => {
if (key === 'disabled') {
no += value;
} else {
yes += value;
}
});
}
return { yes, no };
}
2017-06-28 10:17:14 +02:00
addBucket(app, bucket) {
let count = 0;
2016-11-04 16:16:55 +01:00
// TODO stop should be createdAt
const { stop, toggles } = bucket;
2016-10-27 15:16:27 +02:00
const toggleNames = Object.keys(toggles);
2017-06-28 10:17:14 +02:00
toggleNames.forEach(n => {
const countObj = this.createCountObject(toggles[n]);
this.lastHourProjection.add(n, countObj);
this.lastMinuteProjection.add(n, countObj);
count += countObj.yes + countObj.no;
});
2016-11-04 16:16:55 +01:00
2016-11-07 09:13:19 +01:00
this.lastHourList.add(toggles, stop);
this.lastMinuteList.add(toggles, stop);
2016-11-04 16:16:55 +01:00
this.globalCount += count;
2016-12-27 21:03:50 +01:00
app.count += count;
this.addSeenToggles(app, toggleNames);
}
2017-06-28 10:17:14 +02:00
addSeenToggles(app, toggleNames) {
2016-12-04 14:09:37 +01:00
toggleNames.forEach(t => {
app.seenToggles[t] = true;
});
}
2016-11-13 18:14:29 +01:00
async deleteApplication(appName) {
await this.clientInstanceStore.deleteForApplication(appName);
await this.clientAppStore.deleteApplication(appName);
}
async createApplication(input) {
const applicationData = await appSchema.validateAsync(input);
await this.clientAppStore.upsert(applicationData);
}
2017-06-28 10:17:14 +02:00
destroy() {
2016-11-13 18:14:29 +01:00
this.lastHourList.destroy();
this.lastMinuteList.destroy();
}
};