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

Service Map Data API at Runtime #54027

Merged
merged 25 commits into from
Jan 13, 2020

Conversation

ogupte
Copy link
Contributor

@ogupte ogupte commented Jan 6, 2020

Addresses #48996.

Implements the API for service map data which is managed at runtime in the client.

runtime-service-maps-progress-bar-2

@ogupte ogupte force-pushed the apm-48996-runtime-service-maps branch 2 times, most recently from 2ca87d1 to 8a987ea Compare January 9, 2020 18:18
@@ -62,17 +62,30 @@ export function Cytoscape({
// Trigger a custom "data" event when data changes
useEffect(() => {
if (cy) {
cy.remove(cy.nodes());
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need to do the remove? As long as each node and edge have a unique id we should be able to add elements without removing. We might want to make this component use memo to only update if the list of ids changes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

removed

cy.add(elements);
cy.trigger('data');
}
}, [cy, elements]);

useEffect(() => {
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be removed, since clicking on a node will bring up a popover.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

removed


interface ServiceMapProps {
serviceName?: string;
}

type Element =
Copy link
Contributor

Choose a reason for hiding this comment

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

@@ -37,37 +70,271 @@ ${theme.euiColorLightShade}`,
margin: `-${theme.gutterTypes.gutterLarge}`
};

const MAX_REQUESTS = 15;

function getConnectionNodeId(
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's move a bunch of this to another file.

@ogupte ogupte force-pushed the apm-48996-runtime-service-maps branch from b4d9480 to df5cd36 Compare January 11, 2020 01:54
@ogupte ogupte marked this pull request as ready for review January 11, 2020 02:08
@ogupte ogupte requested a review from a team as a code owner January 11, 2020 02:08
@ogupte ogupte added v7.6.0 release_note:skip Skip the PR/issue when compiling release notes Team:APM All issues that need APM UI Team support labels Jan 11, 2020
@elasticmachine
Copy link
Contributor

Pinging @elastic/apm-ui (Team:apm)

Copy link
Member

@dgieselaar dgieselaar left a comment

Choose a reason for hiding this comment

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

Some small suggestions, but almost there!

@@ -71,7 +71,8 @@ export const apm: LegacyPluginInitializer = kibana => {
autocreateApmIndexPattern: Joi.boolean().default(true),

// service map
serviceMapEnabled: Joi.boolean().default(false)
serviceMapEnabled: Joi.boolean().default(false),
serviceMapInitialTimeRange: Joi.number().default(60 * 1000 * 15) // last 15 minutes
Copy link
Member

Choose a reason for hiding this comment

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

I'm okay with 15 minutes, but 1hr seemed fast enough for me.

@@ -37,37 +70,271 @@ ${theme.euiColorLightShade}`,
margin: `-${theme.gutterTypes.gutterLarge}`
};

const MAX_REQUESTS = 15;
Copy link
Member

Choose a reason for hiding this comment

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

Should this still be 15?

if (renderedElements.current.length === 0) {
renderedElements.current = elements;
} else if (newData.length && !openToast.current) {
openToast.current = notifications.toasts.add({
Copy link
Member

Choose a reason for hiding this comment

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

btw, I'm not sure if we agreed on doing this. It was mostly to showcase how it looks. (FWIW, I think it's the best experience we can offer).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i agree

/* if there is an outgoing span, create a new path */
if (event['destination.address'] != null && event['destination.address'] != '') {
def outgoingLocation = new HashMap();
outgoingLocation['destination.address'] = event['destination.address'];
Copy link
Member

Choose a reason for hiding this comment

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

We might want to add the span.type here as well, for the popovers.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

span.type and span.subtype are already included in the external connection objects, are you saying they should also be part of the service connection objects as well?

Copy link
Member

Choose a reason for hiding this comment

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

I think this was an old comment that I didn't discard 😅 you can ignore it, it's there already indeed

@@ -62,6 +62,7 @@ export function Cytoscape({
// Trigger a custom "data" event when data changes
useEffect(() => {
if (cy) {
// cy.remove(cy.nodes());
Copy link
Member

Choose a reason for hiding this comment

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

is this not needed? cytoscape will dedupe automatically by id and id only? if so, we can remove this line completely right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

correct, i just forgot to remove this line


if (parent['destination.address'] != null
&& parent['destination.address'] != ""
&& parent['span.type'] == 'external'
Copy link
Member

Choose a reason for hiding this comment

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

we should add span.type == 'messaging' here, I think

Copy link
Contributor Author

Choose a reason for hiding this comment

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

so && parent['span.type'] == 'external' || parent['span.type'] == 'messaging'? is this for gRPC?

Copy link
Member

Choose a reason for hiding this comment

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

just messaging systems in general, not sure what kind of practical implementations there are

}

/* if there is an outgoing span, create a new path */
if (event['destination.address'] != null && event['destination.address'] != '') {
Copy link
Member

Choose a reason for hiding this comment

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

we should only do this for span.type == 'messaging' | 'external' IIRC


def lastLocation = basePath.size() > 0 ? basePath[basePath.size() - 1] : null;

def currentLocation = new HashMap(service);
Copy link
Member

Choose a reason for hiding this comment

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

I think we can just use def currentLocation = service; here

Comment on lines +240 to +246
if (serviceName) {
matches =
matches &&
'service.name' in node &&
node['service.name'] === serviceName;
}
Copy link
Member

Choose a reason for hiding this comment

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

This actually doesn't take the discovered services (destination.address => service.name) into account. Not sure whether that's an issue though, and we'll never get it 100% right, because an earlier or later request might have discovered a service that this specific request doesn't see.

search
]);

const forceUpdate = useCallback(() => _setUnusedState(value => !value), []);
Copy link
Member

Choose a reason for hiding this comment

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

This was kind of a hack, and we can probably make it a little better by just using setState properly. We can also do that a little later though.

Copy link
Member

Choose a reason for hiding this comment

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

Agree, would be nice to find a more elegant solution.

@ogupte ogupte force-pushed the apm-48996-runtime-service-maps branch from 0bcf5f8 to 02cae50 Compare January 13, 2020 19:56
Copy link
Contributor

@smith smith left a comment

Choose a reason for hiding this comment

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

tenor-204302475

Copy link
Member

@dgieselaar dgieselaar left a comment

Choose a reason for hiding this comment

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

LGTM, let's get it in master :)

@kibanamachine
Copy link
Contributor

💚 Build Succeeded

History

To update your PR or re-run it, just comment with:
@elasticmachine merge upstream

@ogupte ogupte merged commit b65710d into elastic:master Jan 13, 2020
ogupte added a commit to ogupte/kibana that referenced this pull request Jan 13, 2020
* [APM] Runtime service maps

* Make nodes interactive

* Don't use smaller range query on initial request

* Address feedback from Ron

* Get all services separately

* Get single service as well

* Query both transactions/spans for initial request

* Optimize 'top' query for service maps

* Use agent.name from scripted metric

* adds basic loading overlay

* filter out service map node self reference edges from being rendered

* Make service map initial load time range configurable with
`xpack.apm.serviceMapInitialTimeRange` default to last 1 hour in
milliseconds

* ensure destination.address is not missing in the composite agg when
fetching sample trace ids

* wip: added incremental data fetch & progress bar

* implement progressive loading design while blocking service map interaction during loading

* adds filter that destination.address exists before fetching sample trace ids

* reduce pairs of connections to 1 bi-directional connection with arrows on both ends of the edge

* Optimize query; add update button

* Allow user interaction after 5s, auto update in that time, otherwise
show toast for user to update the map with button

* Correctly reduce nodes/connections

* - remove non-interactive state while loading
- use cytoscape element definition types

* - readability improvements to the ServiceMap component
- only show the update map button toast after last request loads

* addresses feedback for changes to the Cytoscape component

* Add span.type/span.subtype do external nodes

* PR feedback

Co-authored-by: Dario Gieselaar <[email protected]>
ogupte added a commit that referenced this pull request Jan 13, 2020
* [APM] Runtime service maps

* Make nodes interactive

* Don't use smaller range query on initial request

* Address feedback from Ron

* Get all services separately

* Get single service as well

* Query both transactions/spans for initial request

* Optimize 'top' query for service maps

* Use agent.name from scripted metric

* adds basic loading overlay

* filter out service map node self reference edges from being rendered

* Make service map initial load time range configurable with
`xpack.apm.serviceMapInitialTimeRange` default to last 1 hour in
milliseconds

* ensure destination.address is not missing in the composite agg when
fetching sample trace ids

* wip: added incremental data fetch & progress bar

* implement progressive loading design while blocking service map interaction during loading

* adds filter that destination.address exists before fetching sample trace ids

* reduce pairs of connections to 1 bi-directional connection with arrows on both ends of the edge

* Optimize query; add update button

* Allow user interaction after 5s, auto update in that time, otherwise
show toast for user to update the map with button

* Correctly reduce nodes/connections

* - remove non-interactive state while loading
- use cytoscape element definition types

* - readability improvements to the ServiceMap component
- only show the update map button toast after last request loads

* addresses feedback for changes to the Cytoscape component

* Add span.type/span.subtype do external nodes

* PR feedback

Co-authored-by: Dario Gieselaar <[email protected]>

Co-authored-by: Dario Gieselaar <[email protected]>
gmmorris added a commit to gmmorris/kibana that referenced this pull request Jan 14, 2020
* upstream/master: (26 commits)
  Take page offset into account too (elastic#54567)
  [APM] Support error.{log,exception}.stacktrace.classname (elastic#54577)
  Np migration tsvb route validation (elastic#51850)
  [ML] MML calculator enhancements for multi-metric job wizard  (elastic#54573)
  [SIEM] Fix Inspect query 'request timestamp' value changes when curso… (elastic#54223)
  Fix chromeless NP apps not using full page width (elastic#54550)
  Remove extraneous public import to prevent failing Kibana startup (elastic#54676)
  [Uptime] Temporarily skip flakey tests (elastic#54675)
  Skip failing uptime tests
  Create UI for alerting and actions plugin (elastic#48959)
  [dev/build/sass] build stylesheets for disabled plugins too (elastic#54654)
  [SIEM] Use bulk actions API when updating or deleting rules (elastic#54521)
  Support "Deprecated" label in advanced settings (elastic#54539)
  [Maps] add text halo color and width style properties (elastic#53827)
  Service Map Data API at Runtime (elastic#54027)
  [SIEM] Detection Engine Create Rule Design Review #1 (elastic#54442)
  Skip flaky test
  [Canvas] Enable Embeddable maps (elastic#53971)
  [SIEM][Detection Engine] Increases the number or rules you can view on a single page (elastic#54628)
  uiSettings - use validation field for image field maxSize (elastic#54522)
  ...
@ogupte ogupte removed their assignment Jan 15, 2020
jkelastic pushed a commit to jkelastic/kibana that referenced this pull request Jan 17, 2020
* [APM] Runtime service maps

* Make nodes interactive

* Don't use smaller range query on initial request

* Address feedback from Ron

* Get all services separately

* Get single service as well

* Query both transactions/spans for initial request

* Optimize 'top' query for service maps

* Use agent.name from scripted metric

* adds basic loading overlay

* filter out service map node self reference edges from being rendered

* Make service map initial load time range configurable with
`xpack.apm.serviceMapInitialTimeRange` default to last 1 hour in
milliseconds

* ensure destination.address is not missing in the composite agg when
fetching sample trace ids

* wip: added incremental data fetch & progress bar

* implement progressive loading design while blocking service map interaction during loading

* adds filter that destination.address exists before fetching sample trace ids

* reduce pairs of connections to 1 bi-directional connection with arrows on both ends of the edge

* Optimize query; add update button

* Allow user interaction after 5s, auto update in that time, otherwise
show toast for user to update the map with button

* Correctly reduce nodes/connections

* - remove non-interactive state while loading
- use cytoscape element definition types

* - readability improvements to the ServiceMap component
- only show the update map button toast after last request loads

* addresses feedback for changes to the Cytoscape component

* Add span.type/span.subtype do external nodes

* PR feedback

Co-authored-by: Dario Gieselaar <[email protected]>
@smith
Copy link
Contributor

smith commented Jan 23, 2020

Marking this as test plan done since we know it does what we need in its state behind the feature flag.

@smith smith added the apm:test-plan-done Pull request that was successfully tested during the test plan label Jan 23, 2020
>
<Controls />
</Cytoscape>
</LoadingOverlay>
) : (
<PlatinumLicensePrompt />
);
Copy link
Member

@sorenlouv sorenlouv Feb 19, 2020

Choose a reason for hiding this comment

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

I stumbled upon this file, and before we release I think it would be great if we could clean it up. It very much resembles the POC state we've been in, but seeing we have settled on most aspects and are soon making a public release I think we should revisit this.

This component is currently responsible for: data fetching; polling logic, license checking; staleness checks + update button; custom loading indicator controls; custom environment handling; cytoscape specific behaviour - and probably other things I didn't spot.

},
toastLifeTimeMs: 24 * 60 * 60 * 1000,
text: toMountPoint(
<EuiButton onClick={updateMap}>
Copy link
Member

Choose a reason for hiding this comment

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

If we are able to remove the update button completely I think a lot of complexity would vanish.

if (renderedElements.current.length === 0) {
renderedElements.current = elements;
return;
}
Copy link
Member

Choose a reason for hiding this comment

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

Can you add a comment here - why are we returning early here?

pathname: '/api/apm/service-map',
params: { query: { start, end } }
});
setIsLoading(true);
Copy link
Member

@sorenlouv sorenlouv Feb 19, 2020

Choose a reason for hiding this comment

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

The loading state is already updated accordingly in callApmApi. Why the custom handling?

Copy link
Member

Choose a reason for hiding this comment

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

Correction: loading state is automatically handled if you use useFetcher instead of callApmApi. My guess is that callApmApi was chosen to make the fetch conditional. I have a gut feeling that this leads to procedural code with more edge cases. Instead I'd suggest using useFetcher and handle the conditional loading in the callback. This way we might be able to get rid of getNext since the useFetcher will automatically be invoked on every render

...uiFilters,
environment: undefined
}
});
Copy link
Member

@sorenlouv sorenlouv Feb 19, 2020

Choose a reason for hiding this comment

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

If we need environment on the backend what about getting it from uiFilters.environment? I think rewriting uiFilters like this adds complexity that we could be without afaict.

Not sure this is related but the environment selector currently doesn't work. When changing environment the service map is unaffected and it's necessary to hard refresh to see the correct service map.

const [responses, setResponses] = useState<ServiceMapAPIResponse[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [percentageLoaded, setPercentageLoaded] = useState(0);
const [, _setUnusedState] = useState(false);
Copy link
Member

Choose a reason for hiding this comment

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

The number of useState, useRef, useMemo, useEffect and useCallback hooks in this file worries me a little. I'm not saying they are doing something wrong - but it points to a looot of things going on. Would be great if we can find some sensible abstractions that make this easier to comprehend and modify without fear.

renderedElements.current = elements;
if (openToast.current) {
notifications.toasts.remove(openToast.current);
}
Copy link
Member

Choose a reason for hiding this comment

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

Do we still need to keep track of the currently open toast? Would be nice if we can remove this and just let the toast handle its own state.

setIsLoading(false);

const shouldGetNext =
responses.length + 1 < MAX_REQUESTS && data.after;
Copy link
Member

Choose a reason for hiding this comment

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

would be great if this polling logic could be encapsulated

loadServiceMaps();

// eslint-disable-next-line react-hooks/exhaustive-deps
}, [params]);
Copy link
Member

@sorenlouv sorenlouv Feb 19, 2020

Choose a reason for hiding this comment

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

If we really care about whether params change we should make it the dependency explicit and pass it through to getNext. But as I said in another comment: I think getNext forces us into an imperative mindset, whereas a hook based approach (useFetcher) will be more declarative.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
apm:test-plan-done Pull request that was successfully tested during the test plan release_note:skip Skip the PR/issue when compiling release notes Team:APM All issues that need APM UI Team support v7.6.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants