Skip to content

Commit

Permalink
[telemetry] Analytics Package (elastic#41113)
Browse files Browse the repository at this point in the history
* kbn-analytics

* kbn-analytics

* expose provider

* add logger

* performance and stats reporters

* finalize ui stats metric

* functional tests

* remove readme file for now

* update readme

* add types file into to tsconfigs

* Update packages/kbn-analytics/src/report.ts

Co-Authored-By: Josh Dover <me@joshdover.com>

* fix typechecks

* use enum instead of strings for metric types

* getUiStatsReporter -> createUiStatsReporter

* fix special typo in README

* remove unused stop method

* fix tests

* default debug to false

* use chrome.getInjected

* add METRIC_TYPE to jest module mocks

* mock create fn

* handle enabled:false

* init ui_metric in test setup env

* regenerator runtime

* transform-regenerator

* update lock file

* update babel configs

* runtime dep

* add regenerator

* babel configs

* use env-preset

* merge conflicts

* fix workpad telemetry tests

* regeneratorRuntime attempt to fix number 30000

* env targets

* remove module config

* try again

* try without regenerator

* use kbn/babel-preset/webpack_preset only

* runtime

* just use typescript

* update tsconfig

* Caches trackers by app value for infra useTrackMetric hook
  • Loading branch information
Bamieh committed Jul 23, 2019
1 parent f766eca commit 9d7cf53
Show file tree
Hide file tree
Showing 78 changed files with 895 additions and 323 deletions.
2 changes: 2 additions & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@
/x-pack/plugins/security/ @elastic/kibana-security

# Kibana Stack Services
/packages/kbn-analytics/ @elastic/kibana-stack-services
/src/legacy/core_plugins/ui_metric/ @elastic/kibana-stack-services
/x-pack/legacy/plugins/telemetry @elastic/kibana-stack-services
/x-pack/legacy/plugins/alerting @elastic/kibana-stack-services
/x-pack/legacy/plugins/actions @elastic/kibana-stack-services
Expand Down
21 changes: 21 additions & 0 deletions packages/kbn-analytics/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "@kbn/analytics",
"private": true,
"version": "1.0.0",
"description": "Kibana Analytics tool",
"main": "target/index.js",
"types": "target/index.d.ts",
"author": "Ahmad Bamieh <ahmadbamieh@gmail.com>",
"license": "Apache-2.0",
"scripts": {
"build": "tsc",
"kbn:bootstrap": "yarn build",
"kbn:watch": "yarn build --watch"
},
"devDependencies": {
"typescript": "3.5.1"
},
"dependencies": {
"@kbn/dev-utils": "1.0.0"
}
}
22 changes: 22 additions & 0 deletions packages/kbn-analytics/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

export { createReporter, ReportHTTP, Reporter, ReporterConfig } from './reporter';
export { UiStatsMetricType, METRIC_TYPE } from './metrics';
export { Report, ReportManager } from './report';
37 changes: 37 additions & 0 deletions packages/kbn-analytics/src/metrics/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { UiStatsMetric, UiStatsMetricType } from './ui_stats';

export {
UiStatsMetric,
createUiStatsMetric,
UiStatsMetricReport,
UiStatsMetricType,
} from './ui_stats';
export { Stats } from './stats';

export type Metric = UiStatsMetric<UiStatsMetricType>;
export type MetricType = keyof typeof METRIC_TYPE;

export enum METRIC_TYPE {
COUNT = 'count',
LOADED = 'loaded',
CLICK = 'click',
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,9 @@
* under the License.
*/

export { registerUiMetricUsageCollector } from './collector';
export interface Stats {
min: number;
max: number;
sum: number;
avg: number;
}
53 changes: 53 additions & 0 deletions packages/kbn-analytics/src/metrics/ui_stats.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { Stats } from './stats';
import { METRIC_TYPE } from './';

export type UiStatsMetricType = METRIC_TYPE.CLICK | METRIC_TYPE.LOADED | METRIC_TYPE.COUNT;
export interface UiStatsMetricConfig<T extends UiStatsMetricType> {
type: T;
appName: string;
eventName: string;
count?: number;
}

export interface UiStatsMetric<T extends UiStatsMetricType = UiStatsMetricType> {
type: T;
appName: string;
eventName: string;
count: number;
}

export function createUiStatsMetric<T extends UiStatsMetricType>({
type,
appName,
eventName,
count = 1,
}: UiStatsMetricConfig<T>): UiStatsMetric<T> {
return { type, appName, eventName, count };
}

export interface UiStatsMetricReport {
key: string;
appName: string;
eventName: string;
type: UiStatsMetricType;
stats: Stats;
}
93 changes: 93 additions & 0 deletions packages/kbn-analytics/src/report.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { UnreachableCaseError } from './util';
import { Metric, Stats, UiStatsMetricReport, METRIC_TYPE } from './metrics';

export interface Report {
uiStatsMetrics: {
[key: string]: UiStatsMetricReport;
};
}

export class ReportManager {
public report: Report;
constructor(report?: Report) {
this.report = report || ReportManager.createReport();
}
static createReport() {
return { uiStatsMetrics: {} };
}
public clearReport() {
this.report = ReportManager.createReport();
}
public isReportEmpty(): boolean {
return Object.keys(this.report.uiStatsMetrics).length === 0;
}
private incrementStats(count: number, stats?: Stats): Stats {
const { min = 0, max = 0, sum = 0 } = stats || {};
const newMin = Math.min(min, count);
const newMax = Math.max(max, count);
const newAvg = newMin + newMax / 2;
const newSum = sum + count;

return {
min: newMin,
max: newMax,
avg: newAvg,
sum: newSum,
};
}
assignReports(newMetrics: Metric[]) {
newMetrics.forEach(newMetric => this.assignReport(this.report, newMetric));
}
static createMetricKey(metric: Metric): string {
switch (metric.type) {
case METRIC_TYPE.CLICK:
case METRIC_TYPE.LOADED:
case METRIC_TYPE.COUNT: {
const { appName, type, eventName } = metric;
return `${appName}-${type}-${eventName}`;
}
default:
throw new UnreachableCaseError(metric.type);
}
}
private assignReport(report: Report, metric: Metric) {
switch (metric.type) {
case METRIC_TYPE.CLICK:
case METRIC_TYPE.LOADED:
case METRIC_TYPE.COUNT: {
const { appName, type, eventName, count } = metric;
const key = ReportManager.createMetricKey(metric);
const existingStats = (report.uiStatsMetrics[key] || {}).stats;
this.report.uiStatsMetrics[key] = {
key,
appName,
eventName,
type,
stats: this.incrementStats(count, existingStats),
};
return;
}
default:
throw new UnreachableCaseError(metric.type);
}
}
}
114 changes: 114 additions & 0 deletions packages/kbn-analytics/src/reporter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { wrapArray } from './util';
import { Metric, UiStatsMetric, createUiStatsMetric } from './metrics';

import { Storage, ReportStorageManager } from './storage';
import { Report, ReportManager } from './report';

export interface ReporterConfig {
http: ReportHTTP;
storage?: Storage;
checkInterval?: number;
debug?: boolean;
storageKey?: string;
}

export type ReportHTTP = (report: Report) => Promise<void>;

export class Reporter {
checkInterval: number;
private interval: any;
private http: ReportHTTP;
private reportManager: ReportManager;
private storageManager: ReportStorageManager;
private debug: boolean;

constructor(config: ReporterConfig) {
const { http, storage, debug, checkInterval = 10000, storageKey = 'analytics' } = config;

this.http = http;
this.checkInterval = checkInterval;
this.interval = null;
this.storageManager = new ReportStorageManager(storageKey, storage);
const storedReport = this.storageManager.get();
this.reportManager = new ReportManager(storedReport);
this.debug = !!debug;
}

private saveToReport(newMetrics: Metric[]) {
this.reportManager.assignReports(newMetrics);
this.storageManager.store(this.reportManager.report);
}

private flushReport() {
this.reportManager.clearReport();
this.storageManager.store(this.reportManager.report);
}

public start() {
if (!this.interval) {
this.interval = setTimeout(() => {
this.interval = null;
this.sendReports();
}, this.checkInterval);
}
}

private log(message: any) {
if (this.debug) {
// eslint-disable-next-line
console.debug(message);
}
}

public reportUiStats(
appName: string,
type: UiStatsMetric['type'],
eventNames: string | string[],
count?: number
) {
const metrics = wrapArray(eventNames).map(eventName => {
if (this) this.log(`${type} Metric -> (${appName}:${eventName}):`);
const report = createUiStatsMetric({ type, appName, eventName, count });
this.log(report);
return report;
});
this.saveToReport(metrics);
}

public async sendReports() {
if (!this.reportManager.isReportEmpty()) {
try {
await this.http(this.reportManager.report);
this.flushReport();
} catch (err) {
this.log(`Error Sending Metrics Report ${err}`);
}
}
this.start();
}
}

export function createReporter(reportedConf: ReporterConfig) {
const reporter = new Reporter(reportedConf);
reporter.start();
return reporter;
}
Loading

0 comments on commit 9d7cf53

Please sign in to comment.