-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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
Service Map Data API at Runtime #54027
Merged
ogupte
merged 25 commits into
elastic:master
from
ogupte:apm-48996-runtime-service-maps
Jan 13, 2020
Merged
Changes from all commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
7c96d36
[APM] Runtime service maps
dgieselaar 19f8e47
Make nodes interactive
dgieselaar 61e5e99
Don't use smaller range query on initial request
dgieselaar 4acf9b1
Address feedback from Ron
dgieselaar f7b3cbe
Get all services separately
dgieselaar ea9d314
Get single service as well
dgieselaar f99addc
Query both transactions/spans for initial request
dgieselaar d27c531
Optimize 'top' query for service maps
dgieselaar 14970d7
Use agent.name from scripted metric
dgieselaar 5f1df25
adds basic loading overlay
ogupte 8ca834a
filter out service map node self reference edges from being rendered
ogupte 08398d1
Make service map initial load time range configurable with
ogupte 56d60c4
ensure destination.address is not missing in the composite agg when
ogupte 141c4d2
wip: added incremental data fetch & progress bar
ogupte 7a0026a
implement progressive loading design while blocking service map inter…
ogupte 66fd5b9
adds filter that destination.address exists before fetching sample tr…
ogupte 1ccdb58
reduce pairs of connections to 1 bi-directional connection with arrow…
ogupte fb8bfbd
Optimize query; add update button
dgieselaar 35184fe
Allow user interaction after 5s, auto update in that time, otherwise
ogupte 99f32e2
Correctly reduce nodes/connections
dgieselaar 55cfdf2
- remove non-interactive state while loading
ogupte 5301125
- readability improvements to the ServiceMap component
ogupte b02b28c
addresses feedback for changes to the Cytoscape component
ogupte 3ccffad
Add span.type/span.subtype do external nodes
dgieselaar 02cae50
PR feedback
ogupte File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
6 changes: 6 additions & 0 deletions
6
x-pack/legacy/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
/* | ||
* 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 ServiceConnectionNode { | ||
'service.name': string; | ||
'service.environment': string | null; | ||
'agent.name': string; | ||
} | ||
export interface ExternalConnectionNode { | ||
'destination.address': string; | ||
'span.type': string; | ||
'span.subtype': string; | ||
} | ||
|
||
export type ConnectionNode = ServiceConnectionNode | ExternalConnectionNode; | ||
|
||
export interface Connection { | ||
source: ConnectionNode; | ||
destination: ConnectionNode; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
66 changes: 66 additions & 0 deletions
66
x-pack/legacy/plugins/apm/public/components/app/ServiceMap/LoadingOverlay.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. | ||
*/ | ||
|
||
import theme from '@elastic/eui/dist/eui_theme_light.json'; | ||
import React from 'react'; | ||
import { EuiProgress, EuiText, EuiSpacer } from '@elastic/eui'; | ||
import styled from 'styled-components'; | ||
import { i18n } from '@kbn/i18n'; | ||
|
||
const Container = styled.div` | ||
position: relative; | ||
`; | ||
|
||
const Overlay = styled.div` | ||
position: absolute; | ||
top: 0; | ||
z-index: 1; | ||
display: flex; | ||
flex-direction: column; | ||
align-items: center; | ||
width: 100%; | ||
padding: ${theme.gutterTypes.gutterMedium}; | ||
`; | ||
|
||
const ProgressBarContainer = styled.div` | ||
width: 50%; | ||
max-width: 600px; | ||
`; | ||
|
||
interface Props { | ||
children: React.ReactNode; | ||
isLoading: boolean; | ||
percentageLoaded: number; | ||
} | ||
|
||
export const LoadingOverlay = ({ | ||
children, | ||
isLoading, | ||
percentageLoaded | ||
}: Props) => ( | ||
<Container> | ||
{isLoading && ( | ||
<Overlay> | ||
<ProgressBarContainer> | ||
<EuiProgress | ||
value={percentageLoaded} | ||
max={100} | ||
color="primary" | ||
size="m" | ||
/> | ||
</ProgressBarContainer> | ||
<EuiSpacer size="s" /> | ||
<EuiText size="s" textAlign="center"> | ||
{i18n.translate('xpack.apm.loadingServiceMap', { | ||
defaultMessage: | ||
'Loading service map... This might take a short while.' | ||
})} | ||
</EuiText> | ||
</Overlay> | ||
)} | ||
{children} | ||
</Container> | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
158 changes: 158 additions & 0 deletions
158
x-pack/legacy/plugins/apm/public/components/app/ServiceMap/get_cytoscape_elements.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
/* | ||
* 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 { ValuesType } from 'utility-types'; | ||
import { sortBy, isEqual } from 'lodash'; | ||
import { Connection, ConnectionNode } from '../../../../common/service_map'; | ||
import { ServiceMapAPIResponse } from '../../../../server/lib/service_map/get_service_map'; | ||
import { getAPMHref } from '../../shared/Links/apm/APMLink'; | ||
|
||
function getConnectionNodeId(node: ConnectionNode): string { | ||
if ('destination.address' in node) { | ||
// use a prefix to distinguish exernal destination ids from services | ||
return `>${node['destination.address']}`; | ||
} | ||
return node['service.name']; | ||
} | ||
|
||
function getConnectionId(connection: Connection) { | ||
return `${getConnectionNodeId(connection.source)}~${getConnectionNodeId( | ||
connection.destination | ||
)}`; | ||
} | ||
export function getCytoscapeElements( | ||
responses: ServiceMapAPIResponse[], | ||
search: string | ||
) { | ||
const discoveredServices = responses.flatMap( | ||
response => response.discoveredServices | ||
); | ||
|
||
const serviceNodes = responses | ||
.flatMap(response => response.services) | ||
.map(service => ({ | ||
...service, | ||
id: service['service.name'] | ||
})); | ||
|
||
// maps destination.address to service.name if possible | ||
function getConnectionNode(node: ConnectionNode) { | ||
let mappedNode: ConnectionNode | undefined; | ||
|
||
if ('destination.address' in node) { | ||
mappedNode = discoveredServices.find(map => isEqual(map.from, node))?.to; | ||
} | ||
|
||
if (!mappedNode) { | ||
mappedNode = node; | ||
} | ||
|
||
return { | ||
...mappedNode, | ||
id: getConnectionNodeId(mappedNode) | ||
}; | ||
} | ||
|
||
// build connections with mapped nodes | ||
const connections = responses | ||
.flatMap(response => response.connections) | ||
.map(connection => { | ||
const source = getConnectionNode(connection.source); | ||
const destination = getConnectionNode(connection.destination); | ||
|
||
return { | ||
source, | ||
destination, | ||
id: getConnectionId({ source, destination }) | ||
}; | ||
}) | ||
.filter(connection => connection.source.id !== connection.destination.id); | ||
|
||
const nodes = connections | ||
.flatMap(connection => [connection.source, connection.destination]) | ||
.concat(serviceNodes); | ||
|
||
type ConnectionWithId = ValuesType<typeof connections>; | ||
type ConnectionNodeWithId = ValuesType<typeof nodes>; | ||
|
||
const connectionsById = connections.reduce((connectionMap, connection) => { | ||
return { | ||
...connectionMap, | ||
[connection.id]: connection | ||
}; | ||
}, {} as Record<string, ConnectionWithId>); | ||
|
||
const nodesById = nodes.reduce((nodeMap, node) => { | ||
return { | ||
...nodeMap, | ||
[node.id]: node | ||
}; | ||
}, {} as Record<string, ConnectionNodeWithId>); | ||
|
||
const cyNodes = (Object.values(nodesById) as ConnectionNodeWithId[]).map( | ||
node => { | ||
let data = {}; | ||
|
||
if ('service.name' in node) { | ||
data = { | ||
href: getAPMHref( | ||
`/services/${node['service.name']}/service-map`, | ||
search | ||
), | ||
agentName: node['agent.name'] || node['agent.name'] | ||
}; | ||
} | ||
|
||
return { | ||
group: 'nodes' as const, | ||
data: { | ||
id: node.id, | ||
label: | ||
'service.name' in node | ||
? node['service.name'] | ||
: node['destination.address'], | ||
...data | ||
} | ||
}; | ||
} | ||
); | ||
|
||
// instead of adding connections in two directions, | ||
// we add a `bidirectional` flag to use in styling | ||
const dedupedConnections = (sortBy( | ||
Object.values(connectionsById), | ||
// make sure that order is stable | ||
'id' | ||
) as ConnectionWithId[]).reduce< | ||
Array<ConnectionWithId & { bidirectional?: boolean }> | ||
>((prev, connection) => { | ||
const reversedConnection = prev.find( | ||
c => | ||
c.destination.id === connection.source.id && | ||
c.source.id === connection.destination.id | ||
); | ||
|
||
if (reversedConnection) { | ||
reversedConnection.bidirectional = true; | ||
return prev; | ||
} | ||
|
||
return prev.concat(connection); | ||
}, []); | ||
|
||
const cyEdges = dedupedConnections.map(connection => { | ||
return { | ||
group: 'edges' as const, | ||
data: { | ||
id: connection.id, | ||
source: connection.source.id, | ||
target: connection.destination.id, | ||
bidirectional: connection.bidirectional ? true : undefined | ||
} | ||
}; | ||
}, []); | ||
|
||
return [...cyNodes, ...cyEdges]; | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
btw, have we tried removing the text and having just the loader? just a thought.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've been going off the design issue #54246 which includes the text.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ogupte @dgieselaar Not sure where we stand on the load optimizations yet, but if we go more than 5 seconds on load, I think it's still worth showing a message to the user was is happening behind the scenes, why the map is locked. The progress bar probably isn't enough?