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

Servicemap POC #42120

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@
"core-js": "2.6.9",
"css-loader": "2.1.1",
"custom-event-polyfill": "^0.3.0",
"cytoscape": "^3.8.1",
"d3": "3.5.17",
"d3-cloud": "1.2.5",
"del": "^4.0.0",
Expand Down Expand Up @@ -211,6 +212,7 @@
"react": "^16.8.0",
"react-addons-shallow-compare": "15.6.2",
"react-color": "^2.13.8",
"react-cytoscapejs": "^1.1.0",
"react-dom": "^16.8.0",
"react-grid-layout": "^0.16.2",
"react-hooks-testing-library": "^0.5.0",
Expand Down
12 changes: 10 additions & 2 deletions src/legacy/core_plugins/apm_oss/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,12 @@ export default function apmOss(kibana) {
spanIndices: Joi.string().default('apm-*'),
metricsIndices: Joi.string().default('apm-*'),
onboardingIndices: Joi.string().default('apm-*'),
apmAgentConfigurationIndex: Joi.string().default('.apm-agent-configuration')
apmAgentConfigurationIndex: Joi.string().default('.apm-agent-configuration'),

serviceMapIndexPattern: Joi.string().default('apm-*'),
serviceMapDestinationIndex: Joi.string(),
serviceMapDestinationPipeline: Joi.string(),

}).default();
},

Expand All @@ -49,7 +54,10 @@ export default function apmOss(kibana) {
'transactionIndices',
'spanIndices',
'metricsIndices',
'onboardingIndices'
'onboardingIndices',
'serviceMapIndexPattern',
'serviceMapDestinationIndex',
'serviceMapDestinationPipeline'
].map(type => server.config().get(`apm_oss.${type}`))));
}
});
Expand Down
97 changes: 90 additions & 7 deletions x-pack/legacy/plugins/apm/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,18 @@ import {
import { LegacyPluginInitializer } from '../../../../src/legacy/types';
import mappings from './mappings.json';
import { plugin } from './server/new-platform/index';
import { TaskManager, RunContext } from '../legacy/plugins/task_manager';
import { serviceMapRun } from './server/lib/servicemap';

export const apm: LegacyPluginInitializer = kibana => {
return new kibana.Plugin({
require: ['kibana', 'elasticsearch', 'xpack_main', 'apm_oss'],
require: [
'kibana',
'elasticsearch',
'xpack_main',
'apm_oss',
'task_manager'
],
id: 'apm',
configPrefix: 'xpack.apm',
publicDir: resolve(__dirname, 'public'),
Expand Down Expand Up @@ -104,13 +112,88 @@ export const apm: LegacyPluginInitializer = kibana => {
}
});

const initializerContext = {} as PluginInitializerContext;
const core = {
http: {
server
// fires off the job
// needed this during debugging
server.route({
method: 'GET',
path: '/api/apm/servicemap',
options: {
tags: ['access:apm']
},
handler: req => {
return serviceMapRun(this.kbnServer, this.kbnServer.config);
}
} as InternalCoreSetup;
plugin(initializerContext).setup(core);
});

const { taskManager } = server;
if (taskManager) {
// console.log('registering task');
taskManager.registerTaskDefinitions({
// serviceMap is the task type, and must be unique across the entire system
serviceMap: {
// Human friendly name, used to represent this task in logs, UI, etc
title: 'ServiceMapTask',

// Optional, human-friendly, more detailed description
description: 'Extract connections in traces for service maps',

// Optional, how long, in minutes, the system should wait before
// a running instance of this task is considered to be timed out.
// This defaults to 5 minutes.
timeout: '5m',

// The serviceMap task occupies 2 workers, so if the system has 10 worker slots,
// 5 serviceMap tasks could run concurrently per Kibana instance. This value is
// overridden by the `override_num_workers` config value, if specified.
numWorkers: 1,

// The createTaskRunner function / method returns an object that is responsible for
// performing the work of the task. context: { taskInstance, kbnServer }, is documented below.
createTaskRunner({ kbnServer, taskInstance }: RunContext) {
// Perform the work of the task. The return value should fit the TaskResult interface, documented
// below. Invalid return values will result in a logged warning.
return {
async run() {
const { state } = taskInstance;

const { mostRecent } = await serviceMapRun(
kbnServer,
kbnServer.config,
state.lastRun
);

return {
state: {
count: (state.count || 0) + 1,
lastRun: mostRecent
}
};
}
};
}
}
});

this.kbnServer.afterPluginsInit(() => {
// console.log('ahout to schedule');
const task = taskManager.schedule({
id: 'servicemap-processor',
taskType: 'serviceMap',
interval: '1m',
scope: ['apm']
});
// .catch(e => console.log('Err scheduling', e));
// console.log('scheduled', JSON.stringify(task));
});

const initializerContext = {} as PluginInitializerContext;
const core = {
http: {
server
}
} as InternalCoreSetup;
plugin(initializerContext).setup(core);
}
}
});
};
11 changes: 11 additions & 0 deletions x-pack/legacy/plugins/apm/public/components/app/Home/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ import { HistoryTabs, IHistoryTab } from '../../shared/HistoryTabs';
import { SetupInstructionsLink } from '../../shared/Links/SetupInstructionsLink';
import { ServiceOverview } from '../ServiceOverview';
import { TraceOverview } from '../TraceOverview';
import { ServiceMap } from '../ServiceMap';

import { APMLink } from '../../shared/Links/APMLink';
import { useUrlParams } from '../../../hooks/useUrlParams';

const homeTabs: IHistoryTab[] = [
{
Expand All @@ -35,6 +38,14 @@ const homeTabs: IHistoryTab[] = [
}),
render: () => <TraceOverview />,
name: 'traces'
},
{
path: '/servicemap',
title: i18n.translate('xpack.apm.home.tracesTabLabel', {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be a new key instead of tracesTabLabel?

defaultMessage: 'Service Map'
}),
render: () => <ServiceMap global={true} />,
name: 'servicemap'
}
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,15 @@ export const routes: BreadcrumbRoute[] = [
}),
name: RouteName.TRACES
},
{
exact: true,
path: '/servicemap',
component: Home,
breadcrumb: i18n.translate('xpack.apm.breadcrumb.tracesTitle', {
defaultMessage: 'Service Map'
}),
name: RouteName.SERVICEMAP
},
{
exact: true,
path: '/settings',
Expand Down Expand Up @@ -126,5 +135,12 @@ export const routes: BreadcrumbRoute[] = [
breadcrumb: ({ match }) =>
legacyDecodeURIComponent(match.params.transactionName) || '',
name: RouteName.TRANSACTION_NAME
},
{
exact: true,
path: '/:serviceName/servicemap',
component: ServiceDetails,
breadcrumb: null,
name: RouteName.SERVICE_SERVICEMAP
}
];
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ export enum RouteName {
SERVICES = 'services',
TRACES = 'traces',
SERVICE = 'service',
SERVICEMAP = 'servicemap',
TRANSACTIONS = 'transactions',
SERVICE_SERVICEMAP = 'service_servicemap',
ERRORS = 'errors',
ERROR = 'error',
METRICS = 'metrics',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { HistoryTabs } from '../../shared/HistoryTabs';
import { ErrorGroupOverview } from '../ErrorGroupOverview';
import { TransactionOverview } from '../TransactionOverview';
import { ServiceMetrics } from '../ServiceMetrics';
import { ServiceMap } from '../ServiceMap';

interface Props {
transactionTypes: string[];
Expand Down Expand Up @@ -61,9 +62,18 @@ export function ServiceDetailTabs({
render: () => <ServiceMetrics agentName={agentName} />,
name: 'metrics'
};

const serviceMapTab = {
title: i18n.translate('xpack.apm.serviceDetails.mapTabLabel', {
defaultMessage: 'Service Map'
}),
path: `/${serviceName}/servicemap`,
render: () => <ServiceMap layout="breadthfirst" />,
name: 'servicemap'
};
const tabs = isRumAgent
? [transactionsTab, errorsTab]
: [transactionsTab, errorsTab, metricsTab];
? [transactionsTab, errorsTab, serviceMapTab]
: [transactionsTab, errorsTab, serviceMapTab, metricsTab];

return <HistoryTabs tabs={tabs} />;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* 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 React from 'react';
import CytoscapeComponent from 'react-cytoscapejs';
import { IUrlParams } from '../../../context/UrlParamsContext/types';

function elementId(serviceName: string, environment?: string) {
return serviceName + '/' + (environment ? '/' + environment : '');
}

function label(serviceName: string, environment?: string) {
return serviceName + (environment ? '/' + environment : '');
}

// interface Props {
// connections: any;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI cytoscape has very good DefinitelyTyped definitions which we can use to our advantage later, since you can deal with some pretty complex objects.

Not sure where to ask this, so I'll ask it here: Are there reason's we're not using the components that make up the Graph capabilities already in x-pack?

Copy link
Member

@sorenlouv sorenlouv Aug 29, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there reasons we're not using the components that make up the Graph capabilities already in x-pack?

Without having looked into it much I don't think the Graph plugin is easily embeddable in APM UI. Afaict Graph is based on Angular while APM UI uses React. Mixing those frameworks is possible but something we should think twice about doing.

Secondly, I think our use case requires a lot of custom UI work and we would probably spend more time fighting the capabilities and visual appearance of Graph instead of building it from scratch. We already had a meeting with other teams who also need graph visualizations and we found common ground in using the same lib (cytoscape) but for now nothing more than that.

// }
export function MapOfServices({ connections, layout }: any) {
const services: { [s: string]: object } = {};
const conns: Array<{ data: object }> = [];
let destNodeID;
connections.forEach(c => {
if (c['callee.name']) {
destNodeID = elementId(c['callee.name'], c['callee.environment']);
const node = {
data: {
id: destNodeID,
label: label(c['callee.name'], c['callee.environment']),
color: 'black'
}
};
services[destNodeID] = node;
} else {
destNodeID = elementId(c['destination.address']);
services[destNodeID] = {
data: {
id: destNodeID,
label: label(c['destination.address'])
}
};
}
const sourceNodeID = elementId(c['service.name'], c['service.environment']);

services[sourceNodeID] = {
data: {
id: sourceNodeID,
label: label(c['service.name'], c['service.environment']),
color: 'black'
}
};

conns.push({
data: {
source: sourceNodeID,
target: destNodeID,
label: c['connection.subtype']
}
});
});
const elements = Object.values(services).concat(conns);
// const elements = [];
// { data: { id: 'one', label: 'Node 1' }},
// { data: { id: 'two', label: 'Node 2' }},
// { data: { source: 'one', target: 'two', label: 'Edge from Node1 to Node2' } }
// ];
const stylesheet = [
{
selector: 'node',
style: {
label: 'data(label)' // maps to data.label
}
},
{
selector: 'node[color]',
style: {
'background-color': 'data(color)'
}
},
{
selector: 'edge',
style: {
width: 2,
label: 'data(label)', // maps to data.label
'curve-style': 'bezier',
'target-arrow-shape': 'triangle'
}
}
];

return (
<CytoscapeComponent
elements={elements}
layout={{ name: layout }}
stylesheet={stylesheet}
style={{ width: '1024px', height: '600px' }}
/>
);
}
Loading