-
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
Service Map Data API at Runtime #54027
Conversation
2ca87d1
to
8a987ea
Compare
@@ -62,17 +62,30 @@ export function Cytoscape({ | |||
// Trigger a custom "data" event when data changes | |||
useEffect(() => { | |||
if (cy) { | |||
cy.remove(cy.nodes()); |
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.
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.
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.
removed
cy.add(elements); | ||
cy.trigger('data'); | ||
} | ||
}, [cy, elements]); | ||
|
||
useEffect(() => { |
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.
This should be removed, since clicking on a node will bring up a popover.
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.
removed
|
||
interface ServiceMapProps { | ||
serviceName?: string; | ||
} | ||
|
||
type Element = |
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.
Not sure this is necessary with the built-in types in https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/cytoscape/index.d.ts.
@@ -37,37 +70,271 @@ ${theme.euiColorLightShade}`, | |||
margin: `-${theme.gutterTypes.gutterLarge}` | |||
}; | |||
|
|||
const MAX_REQUESTS = 15; | |||
|
|||
function getConnectionNodeId( |
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.
Let's move a bunch of this to another file.
b4d9480
to
df5cd36
Compare
Pinging @elastic/apm-ui (Team:apm) |
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.
Some small suggestions, but almost there!
x-pack/legacy/plugins/apm/index.ts
Outdated
@@ -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 |
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'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; |
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.
Should this still be 15?
if (renderedElements.current.length === 0) { | ||
renderedElements.current = elements; | ||
} else if (newData.length && !openToast.current) { | ||
openToast.current = notifications.toasts.add({ |
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, 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).
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 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']; |
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.
We might want to add the span.type
here as well, for the popovers.
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.
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?
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 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()); |
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.
is this not needed? cytoscape will dedupe automatically by id and id only? if so, we can remove this line completely right?
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.
correct, i just forgot to remove this line
|
||
if (parent['destination.address'] != null | ||
&& parent['destination.address'] != "" | ||
&& parent['span.type'] == 'external' |
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.
we should add span.type == 'messaging'
here, I think
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.
so && parent['span.type'] == 'external' || parent['span.type'] == 'messaging'
? is this for gRPC?
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.
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'] != '') { |
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.
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); |
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 think we can just use def currentLocation = service;
here
if (serviceName) { | ||
matches = | ||
matches && | ||
'service.name' in node && | ||
node['service.name'] === serviceName; | ||
} |
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.
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), []); |
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.
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.
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.
Agree, would be nice to find a more elegant solution.
`xpack.apm.serviceMapInitialTimeRange` default to last 1 hour in milliseconds
fetching sample trace ids
…action during loading
- only show the update map button toast after last request loads
0bcf5f8
to
02cae50
Compare
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.
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.
LGTM, let's get it in master :)
💚 Build SucceededHistory
To update your PR or re-run it, just comment with: |
* [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]>
* [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]>
* 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) ...
* [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]>
Marking this as test plan done since we know it does what we need in its state behind the feature flag. |
> | ||
<Controls /> | ||
</Cytoscape> | ||
</LoadingOverlay> | ||
) : ( | ||
<PlatinumLicensePrompt /> | ||
); |
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 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}> |
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.
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; | ||
} |
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.
Can you add a comment here - why are we returning early here?
pathname: '/api/apm/service-map', | ||
params: { query: { start, end } } | ||
}); | ||
setIsLoading(true); |
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.
The loading state is already updated accordingly in callApmApi
. Why the custom handling?
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.
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 | ||
} | ||
}); |
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.
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); |
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.
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); | ||
} |
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.
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; |
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.
would be great if this polling logic could be encapsulated
loadServiceMaps(); | ||
|
||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, [params]); |
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.
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.
Addresses #48996.
Implements the API for service map data which is managed at runtime in the client.