Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Telemetry for Visualizations by type #28793

Merged
merged 16 commits into from
Jan 30, 2019
Merged
2 changes: 2 additions & 0 deletions x-pack/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

import { xpackMain } from './plugins/xpack_main';
import { coreTelemetry } from './plugins/core_telemetry';
import { graph } from './plugins/graph';
import { monitoring } from './plugins/monitoring';
import { reporting } from './plugins/reporting';
Expand Down Expand Up @@ -39,6 +40,7 @@ import { uptime } from './plugins/uptime';
module.exports = function (kibana) {
return [
xpackMain(kibana),
coreTelemetry(kibana),
graph(kibana),
monitoring(kibana),
reporting(kibana),
Expand Down
9 changes: 9 additions & 0 deletions x-pack/plugins/core_telemetry/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

export const PLUGIN_ID = 'core_telemetry';
export const VIS_TELEMETRY_TASK = 'vis_telemetry';
export const VIS_USAGE_TYPE = 'visualization_types';
tsullivan marked this conversation as resolved.
Show resolved Hide resolved
66 changes: 66 additions & 0 deletions x-pack/plugins/core_telemetry/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

export interface IVisState {
tsullivan marked this conversation as resolved.
Show resolved Hide resolved
type: string;
}

export interface IVisualization {
visState: string;
}

export interface ISavedObjectDoc {
_id: string;
_source: {
visualization: IVisualization;
type: 'visualization';
};
}

export interface IESQueryResponse {
hits: {
hits: ISavedObjectDoc[];
};
}

export interface ITaskInstance {
state: {
runs: number;
stats: any;
};
error?: any;
}

export interface IHapiServer {
taskManager: {
registerTaskDefinitions: (opts: any) => void;
schedule: (opts: any) => Promise<void>;
fetch: (
opts: any
) => Promise<{
docs: ITaskInstance[];
}>;
};
plugins: {
xpack_main: any;
elasticsearch: {
getCluster: (
cluster: string
) => {
callWithInternalUser: () => Promise<IESQueryResponse>;
};
};
};
usage: {
collectorSet: {
register: (collector: any) => void;
makeUsageCollector: (collectorOpts: any) => void;
};
};
config: () => {
get: (prop: string) => any;
};
}
21 changes: 21 additions & 0 deletions x-pack/plugins/core_telemetry/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { registerCollectors } from './server/lib/collectors';
import { registerTasks, scheduleTasks } from './server/lib/tasks';

export const coreTelemetry = (kibana) => {
tsullivan marked this conversation as resolved.
Show resolved Hide resolved
return new kibana.Plugin({
id: 'core_telemetry',
require: ['elasticsearch', 'xpack_main', 'task_manager'],

init(server) {
registerCollectors(server);
registerTasks(server);
scheduleTasks(server);
}
});
};
12 changes: 12 additions & 0 deletions x-pack/plugins/core_telemetry/server/lib/collectors/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { IHapiServer } from '../../../';
import { getUsageCollector } from './visualizations/get_usage_collector';

export function registerCollectors(server: IHapiServer) {
getUsageCollector(server);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import sinon from 'sinon';
import { IHapiServer } from '../../../../';
import {
getMockCallWithInternal,
getMockKbnServer,
getMockTaskFetch,
} from '../../../../test_utils';
import { getUsageCollector } from './get_usage_collector';

describe('getVisualizationsCollector#fetch', () => {
let mockKbnServer: IHapiServer;

beforeEach(() => {
mockKbnServer = getMockKbnServer(getMockCallWithInternal(), getMockTaskFetch());
});

test('can return empty stats', async () => {
const { type, fetch } = getUsageCollector(mockKbnServer);
expect(type).toBe('visualization_types');
const fetchResult = await fetch();
expect(fetchResult).toEqual({});
});

test('provides known stats', async () => {
const mockTaskFetch = getMockTaskFetch([
{
state: {
runs: 1,
stats: { comic_books: { total: 16, max: 12, min: 2, avg: 6 } },
},
},
]);
mockKbnServer = getMockKbnServer(getMockCallWithInternal(), mockTaskFetch);

const { type, fetch } = getUsageCollector(mockKbnServer);
expect(type).toBe('visualization_types');
const fetchResult = await fetch();
expect(fetchResult).toEqual({ comic_books: { avg: 6, max: 12, min: 2, total: 16 } });
});

describe('Error handling', () => {
// In real life, the CollectorSet calls fetch and handles errors
test('defers the errors', async () => {
const mockTaskFetch = sinon.stub();
mockTaskFetch.rejects(new Error('BOOM'));
mockKbnServer = getMockKbnServer(getMockCallWithInternal(), mockTaskFetch);

const { fetch } = getUsageCollector(mockKbnServer);
await expect(fetch()).rejects.toMatchObject(new Error('BOOM'));
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { get } from 'lodash';
import { IHapiServer } from '../../../../';
import { PLUGIN_ID, VIS_TELEMETRY_TASK, VIS_USAGE_TYPE } from '../../../../constants';

export function getUsageCollector(server: IHapiServer) {
const { taskManager } = server;
return {
type: VIS_USAGE_TYPE,
fetch: async () => {
let docs;
try {
({ docs } = await taskManager.fetch({
bool: { filter: { term: { _id: `${PLUGIN_ID}-${VIS_TELEMETRY_TASK}` } } },
}));
} catch (err) {
if (err.constructor === Error && err.toString().indexOf('Error: NotInitialized') === 0) {
tsullivan marked this conversation as resolved.
Show resolved Hide resolved
// it's fine
docs = {};
} else {
throw err;
}
}

// get the accumulated state from the recurring task
return get(docs, '[0].state.stats');
},
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { IHapiServer } from '../../../../';
import { getUsageCollector } from './get_usage_collector';

export function getVisualizationsCollector(server: IHapiServer): void {
const { usage } = server;
const collector = usage.collectorSet.makeUsageCollector(getUsageCollector(server));
server.usage.collectorSet.register(collector);
tsullivan marked this conversation as resolved.
Show resolved Hide resolved
}
39 changes: 39 additions & 0 deletions x-pack/plugins/core_telemetry/server/lib/tasks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { IHapiServer } from '../../../';
import { PLUGIN_ID, VIS_TELEMETRY_TASK } from '../../../constants';
import { visualizationsTaskRunner } from './visualizations/task_runner';

export function registerTasks(server: IHapiServer) {
const { taskManager } = server;

taskManager.registerTaskDefinitions({
[VIS_TELEMETRY_TASK]: {
title: 'X-Pack telemetry calculator for Visualizations',
type: VIS_TELEMETRY_TASK,
numWorkers: 10, // use the max so no other tasks run concurrently
tsullivan marked this conversation as resolved.
Show resolved Hide resolved
createTaskRunner({ taskInstance, kbnServer }: { kbnServer: any; taskInstance: any }) {
return {
run: visualizationsTaskRunner(taskInstance, kbnServer),
};
},
},
});
}

export function scheduleTasks(server: IHapiServer) {
const { taskManager } = server;
const { kbnServer } = server.plugins.xpack_main.status.plugin;

kbnServer.afterPluginsInit(() => {
taskManager.schedule({
id: `${PLUGIN_ID}-${VIS_TELEMETRY_TASK}`,
taskType: VIS_TELEMETRY_TASK,
state: { stats: {}, runs: 0 },
});
});
}
Loading