diff --git a/dashboards-observability/public/components/trace_analytics/components/common/helper_functions.tsx b/dashboards-observability/public/components/trace_analytics/components/common/helper_functions.tsx index 270349f6d..91721cbaf 100644 --- a/dashboards-observability/public/components/trace_analytics/components/common/helper_functions.tsx +++ b/dashboards-observability/public/components/trace_analytics/components/common/helper_functions.tsx @@ -135,26 +135,25 @@ export function getServiceMapGraph( }; } - const message = - { latency: 'Average latency: ', error_rate: 'Error rate: ', throughput: 'Throughput: ' }[ - idSelected - ] + - (value! >= 0 - ? value + (idSelected === 'latency' ? 'ms' : idSelected === 'error_rate' ? '%' : '') - : 'N/A'); + let hover = service; + hover += `\n\nAverage latency: ${map[service].latency! >= 0 ? map[service].latency + 'ms' : 'N/A'}`; + hover += `\nError rate: ${map[service].error_rate! >= 0 ? map[service].error_rate + '%' : 'N/A'}`; + hover += `\nThroughput: ${map[service].throughput! >= 0 ? map[service].throughput : 'N/A'}`; + if (map[service].throughputPerMinute != null) + hover += ` (${map[service].throughputPerMinute} per minute)`; return { id: map[service].id, label: service, size: service === currService ? 30 : 15, - title: `${service}\n\n${message}`, + title: hover, ...styleOptions, }; }); const edges: Array<{ from: number; to: number; color: string }> = []; const edgeColor = uiSettingsService.get('theme:darkMode') ? '255, 255, 255' : '0, 0, 0'; Object.keys(map).map((service) => { - map[service].targetServices.map((target) => { + map[service].targetServices.filter((target) => map[target]).map((target) => { edges.push({ from: map[service].id, to: map[target].id, diff --git a/dashboards-observability/public/components/trace_analytics/components/common/plots/service_map.tsx b/dashboards-observability/public/components/trace_analytics/components/common/plots/service_map.tsx index 0ba45912c..2ebfa9870 100644 --- a/dashboards-observability/public/components/trace_analytics/components/common/plots/service_map.tsx +++ b/dashboards-observability/public/components/trace_analytics/components/common/plots/service_map.tsx @@ -35,6 +35,7 @@ export interface ServiceObject { latency?: number; error_rate?: number; throughput?: number; + throughputPerMinute?: number; relatedServices?: string[]; // services appear in the same traces this service appears }; } @@ -52,7 +53,7 @@ export function ServiceMap({ setIdSelected: (newId: 'latency' | 'error_rate' | 'throughput') => void; addFilter?: (filter: FilterType) => void; currService?: string; - page: 'app' | 'appCreate' | 'dashboard' | 'traces' | 'services' | 'serviceView' | 'detailFlyout'; + page: 'app' | 'appCreate' | 'dashboard' | 'traces' | 'services' | 'serviceView' | 'detailFlyout' | 'traceView'; }) { const [invalid, setInvalid] = useState(false); const [network, setNetwork] = useState(null); diff --git a/dashboards-observability/public/components/trace_analytics/components/services/__tests__/__snapshots__/services.test.tsx.snap b/dashboards-observability/public/components/trace_analytics/components/services/__tests__/__snapshots__/services.test.tsx.snap index af4754f94..066083e5a 100644 --- a/dashboards-observability/public/components/trace_analytics/components/services/__tests__/__snapshots__/services.test.tsx.snap +++ b/dashboards-observability/public/components/trace_analytics/components/services/__tests__/__snapshots__/services.test.tsx.snap @@ -1121,6 +1121,541 @@ exports[`Services component renders empty services page 1`] = ` + +
+ + + +
+ + +
+ + Service map + +
+
+
+ +
+ + +
+
+ + +
+ + + + + + +
+
+
+ + +
+ + + + + + +
+
+
+ + +
+ + + + + + +
+
+
+
+
+
+ +
+
+ +
+ +
+ +
+ Focus on +
+
+
+
+ +
+ + +
+
+ + + + +
+ + + + + +
+
+
+
+
+
+
+
+
+
+ +
+ +
+ + +
+ + + No data matches the selected filter. Clear the filter and/or increase the time range to see more results. + + } + title={ +

+ No matches +

+ } + > +
+ + + +

+ No matches +

+
+ +
+ + +
+ +
+ No data matches the selected filter. Clear the filter and/or increase the time range to see more results. +
+
+
+
+ + +
+ + +
+ + +
+
+ + `; @@ -2190,5 +2725,544 @@ exports[`Services component renders services page 1`] = `
+ +
+ + + +
+ + +
+ + Service map + +
+
+
+ +
+ + +
+
+ + +
+ + + + + + +
+
+
+ + +
+ + + + + + +
+
+
+ + +
+ + + + + + +
+
+
+
+
+
+ +
+
+ +
+ +
+ +
+ Focus on +
+
+
+
+ +
+ + +
+
+ + + + +
+ + + + + +
+
+
+
+
+
+
+
+
+
+ +
+ +
+ + +
+ + + No data matches the selected filter. Clear the filter and/or increase the time range to see more results. + + } + title={ +

+ No matches +

+ } + > +
+ + + +

+ No matches +

+
+ +
+ + +
+ +
+ No data matches the selected filter. Clear the filter and/or increase the time range to see more results. +
+
+
+
+ + +
+ + +
+ + +
+
+ + `; diff --git a/dashboards-observability/public/components/trace_analytics/components/services/services.tsx b/dashboards-observability/public/components/trace_analytics/components/services/services.tsx index 9cec5be87..a5e1b9ae1 100644 --- a/dashboards-observability/public/components/trace_analytics/components/services/services.tsx +++ b/dashboards-observability/public/components/trace_analytics/components/services/services.tsx @@ -10,6 +10,7 @@ import { handleServicesRequest } from '../../requests/services_request_handler'; import { FilterType } from '../common/filters/filters'; import { getValidFilterFields } from '../common/filters/filter_helpers'; import { filtersToDsl } from '../common/helper_functions'; +import { ServiceMap, ServiceObject } from '../common/plots/service_map'; import { SearchBar } from '../common/search_bar'; import { ServicesTable } from './services_table'; @@ -25,6 +26,10 @@ interface ServicesProps extends TraceAnalyticsComponentDeps { export function Services(props: ServicesProps) { const { appId, appName, parentBreadcrumb, page, switchToEditViz } = props; const [tableItems, setTableItems] = useState([]); + const [serviceMap, setServiceMap] = useState({}); + const [serviceMapIdSelected, setServiceMapIdSelected] = useState< + 'latency' | 'error_rate' | 'throughput' + >('latency'); const [redirect, setRedirect] = useState(true); const [loading, setLoading] = useState(false); const appServices = page === 'app'; @@ -80,7 +85,14 @@ export function Services(props: ServicesProps) { props.page, appServices ? props.appConfigs : [] ); - await handleServicesRequest(props.http, DSL, tableItems, setTableItems, null, serviceQuery); + await handleServicesRequest( + props.http, + DSL, + tableItems, + setTableItems, + setServiceMap, + serviceQuery + ); setLoading(false); }; @@ -136,6 +148,14 @@ export function Services(props: ServicesProps) { openServiceFlyout={props.openServiceFlyout} switchToTrace={props.switchToTrace} /> + + ); } diff --git a/dashboards-observability/public/components/trace_analytics/components/traces/__tests__/__snapshots__/span_detail_panel.test.tsx.snap b/dashboards-observability/public/components/trace_analytics/components/traces/__tests__/__snapshots__/span_detail_panel.test.tsx.snap index bd546d11c..d16db248a 100644 --- a/dashboards-observability/public/components/trace_analytics/components/traces/__tests__/__snapshots__/span_detail_panel.test.tsx.snap +++ b/dashboards-observability/public/components/trace_analytics/components/traces/__tests__/__snapshots__/span_detail_panel.test.tsx.snap @@ -75,7 +75,7 @@ exports[`Service breakdown panel component renders service breakdown panel 1`] = > - (0) + (1)
@@ -327,10 +327,53 @@ exports[`Service breakdown panel component renders service breakdown panel 1`] = } > HTTP GET 4dec6080-61af-11eb-aee3-ef2f84ffa4a2", + ], + }, + Object { + "hovertemplate": "%{x}", + "marker": Object { + "color": "#7492e7", + }, + "orientation": "h", + "text": Array [ + "Error", + ], + "textfont": Object { + "color": Array [ + "#c14125", + ], + }, + "textposition": "outside", + "type": "bar", + "width": 0.4, + "x": Array [ + 19.91, + ], + "y": Array [ + "inventory
HTTP GET 4dec6080-61af-11eb-aee3-ef2f84ffa4a2", + ], + }, + ] + } layout={ Object { - "height": 60, + "height": 110, "margin": Object { "b": 30, "l": 260, @@ -342,7 +385,7 @@ exports[`Service breakdown panel component renders service breakdown panel 1`] = "color": "#91989c", "range": Array [ 0, - 0, + 23.892, ], "showline": true, "side": "top", @@ -350,8 +393,12 @@ exports[`Service breakdown panel component renders service breakdown panel 1`] = }, "yaxis": Object { "showgrid": false, - "ticktext": Array [], - "tickvals": Array [], + "ticktext": Array [ + "inventory
HTTP GET ", + ], + "tickvals": Array [ + "inventory
HTTP GET 4dec6080-61af-11eb-aee3-ef2f84ffa4a2", + ], }, } } @@ -365,13 +412,56 @@ exports[`Service breakdown panel component renders service breakdown panel 1`] = "displayModeBar": false, } } - data={Array []} + data={ + Array [ + Object { + "hoverinfo": "none", + "marker": Object { + "color": "rgba(0, 0, 0, 0)", + }, + "orientation": "h", + "showlegend": false, + "type": "bar", + "width": 0.4, + "x": Array [ + 0, + ], + "y": Array [ + "inventory
HTTP GET 4dec6080-61af-11eb-aee3-ef2f84ffa4a2", + ], + }, + Object { + "hovertemplate": "%{x}", + "marker": Object { + "color": "#7492e7", + }, + "orientation": "h", + "text": Array [ + "Error", + ], + "textfont": Object { + "color": Array [ + "#c14125", + ], + }, + "textposition": "outside", + "type": "bar", + "width": 0.4, + "x": Array [ + 19.91, + ], + "y": Array [ + "inventory
HTTP GET 4dec6080-61af-11eb-aee3-ef2f84ffa4a2", + ], + }, + ] + } debug={false} layout={ Object { "autosize": true, "barmode": "stack", - "height": 60, + "height": 110, "hovermode": "closest", "legend": Object { "orientation": "h", @@ -389,7 +479,7 @@ exports[`Service breakdown panel component renders service breakdown panel 1`] = "color": "#91989c", "range": Array [ 0, - 0, + 23.892, ], "showline": true, "side": "top", @@ -397,8 +487,12 @@ exports[`Service breakdown panel component renders service breakdown panel 1`] = }, "yaxis": Object { "showgrid": false, - "ticktext": Array [], - "tickvals": Array [], + "ticktext": Array [ + "inventory
HTTP GET ", + ], + "tickvals": Array [ + "inventory
HTTP GET 4dec6080-61af-11eb-aee3-ef2f84ffa4a2", + ], }, } } diff --git a/dashboards-observability/public/components/trace_analytics/components/traces/__tests__/__snapshots__/trace_view.test.tsx.snap b/dashboards-observability/public/components/trace_analytics/components/traces/__tests__/__snapshots__/trace_view.test.tsx.snap index 281ea3781..bc35bb4ef 100644 --- a/dashboards-observability/public/components/trace_analytics/components/traces/__tests__/__snapshots__/trace_view.test.tsx.snap +++ b/dashboards-observability/public/components/trace_analytics/components/traces/__tests__/__snapshots__/trace_view.test.tsx.snap @@ -130,7 +130,15 @@ exports[`Trace view component renders trace view 1`] = ` > @@ -148,6 +156,13 @@ exports[`Trace view component renders trace view 1`] = ` margin="m" /> + + diff --git a/dashboards-observability/public/components/trace_analytics/components/traces/span_detail_panel.tsx b/dashboards-observability/public/components/trace_analytics/components/traces/span_detail_panel.tsx index 0764aa456..04d20db96 100644 --- a/dashboards-observability/public/components/trace_analytics/components/traces/span_detail_panel.tsx +++ b/dashboards-observability/public/components/trace_analytics/components/traces/span_detail_panel.tsx @@ -28,8 +28,9 @@ export function SpanDetailPanel(props: { colorMap: any; page?: string; openSpanFlyout?: any; + data: { gantt: any[], table: any[], ganttMaxX: number }; + setData: (data: { gantt: any[], table: any[], ganttMaxX: number }) => void; }) { - const [data, setData] = useState({ gantt: [], table: [], ganttMaxX: 0 }); const storedFilters = sessionStorage.getItem('TraceAnalyticsSpanFilters'); const fromApp = props.page === 'app'; const [spanFilters, setSpanFilters] = useState>( @@ -66,7 +67,7 @@ export function SpanDetailPanel(props: { if (_.isEmpty(props.colorMap)) return; const refreshDSL = spanFiltersToDSL(); setDSL(refreshDSL); - handleSpansGanttRequest(props.traceId, props.http, setData, props.colorMap, refreshDSL); + handleSpansGanttRequest(props.traceId, props.http, props.setData, props.colorMap, refreshDSL); }, 150); const spanFiltersToDSL = () => { @@ -134,9 +135,9 @@ export function SpanDetailPanel(props: { }; }; - const layout = useMemo(() => getSpanDetailLayout(data.gantt, data.ganttMaxX), [ - data.gantt, - data.ganttMaxX, + const layout = useMemo(() => getSpanDetailLayout(props.data.gantt, props.data.ganttMaxX), [ + props.data.gantt, + props.data.ganttMaxX, ]); const [currentSpan, setCurrentSpan] = useState(''); @@ -211,7 +212,7 @@ export function SpanDetailPanel(props: { - + {toggleIdSelected === 'timeline' ? ( { return ( <> @@ -130,13 +134,56 @@ export function TraceView(props: TraceViewProps) { const [serviceBreakdownData, setServiceBreakdownData] = useState([]); const [payloadData, setPayloadData] = useState(''); const [colorMap, setColorMap] = useState({}); + const [ganttData, setGanttData] = useState<{ gantt: any[]; table: any[]; ganttMaxX: number }>({ + gantt: [], + table: [], + ganttMaxX: 0, + }); + const [serviceMap, setServiceMap] = useState({}); + const [traceFilteredServiceMap, setTraceFilteredServiceMap] = useState({}); + const [serviceMapIdSelected, setServiceMapIdSelected] = useState< + 'latency' | 'error_rate' | 'throughput' + >('latency'); const refresh = async () => { + const DSL = filtersToDsl([], '', 'now', 'now', page); handleTraceViewRequest(props.traceId, props.http, fields, setFields); - handleServicesPieChartRequest(props.traceId, props.http, setServiceBreakdownData, setColorMap); handlePayloadRequest(props.traceId, props.http, payloadData, setPayloadData); + handleServicesPieChartRequest(props.traceId, props.http, setServiceBreakdownData, setColorMap); + handleServiceMapRequest(props.http, DSL, serviceMap, setServiceMap); }; + useEffect(() => { + if (!Object.keys(serviceMap).length || !ganttData.table.length) return; + const services: any = {}; + ganttData.table.forEach((service: any) => { + if (!services[service.service_name]) { + services[service.service_name] = { + latency: 0, + errors: 0, + throughput: 0, + }; + } + services[service.service_name].latency += service.latency; + if (service.error) services[service.service_name].errors++; + services[service.service_name].throughput++; + }); + const filteredServiceMap: ServiceObject = {}; + Object.entries(services).forEach(([serviceName, service]: [string, any]) => { + filteredServiceMap[serviceName] = serviceMap[serviceName]; + filteredServiceMap[serviceName].latency = _.round(service.latency / service.throughput, 2); + filteredServiceMap[serviceName].error_rate = _.round( + (service.errors / service.throughput) * 100, + 2 + ); + filteredServiceMap[serviceName].throughput = service.throughput; + filteredServiceMap[serviceName].destServices = filteredServiceMap[ + serviceName + ].destServices.filter((destService) => services[destService]); + }); + setTraceFilteredServiceMap(filteredServiceMap); + }, [serviceMap, ganttData]); + useEffect(() => { props.chrome.setBreadcrumbs([ props.parentBreadcrumb, @@ -172,7 +219,13 @@ export function TraceView(props: TraceViewProps) { - + @@ -190,6 +243,15 @@ export function TraceView(props: TraceViewProps) { ) : null} + + + diff --git a/dashboards-observability/public/components/trace_analytics/requests/services_request_handler.ts b/dashboards-observability/public/components/trace_analytics/requests/services_request_handler.ts index 3ceca5c87..11e5cbbb5 100644 --- a/dashboards-observability/public/components/trace_analytics/requests/services_request_handler.ts +++ b/dashboards-observability/public/components/trace_analytics/requests/services_request_handler.ts @@ -13,6 +13,8 @@ import { getServicesQuery, } from './queries/services_queries'; import { handleDslRequest } from './request_handler'; +import dateMath from '@elastic/datemath'; +import moment from 'moment'; export const handleServicesRequest = async ( http, @@ -53,6 +55,14 @@ export const handleServicesRequest = async ( }; export const handleServiceMapRequest = async (http, DSL, items?, setItems?, currService?) => { + let minutesInDateRange: number; + const startTime = DSL?.custom?.timeFilter?.[0]?.range?.startTime; + if (startTime) { + const gte = dateMath.parse(startTime.gte)!; + const lte = dateMath.parse(startTime.lte)!; + minutesInDateRange = lte.diff(gte, 'minutes', true); + } + const map: ServiceObject = {}; let id = 1; await handleDslRequest(http, null, getServiceNodesQuery()) @@ -115,6 +125,8 @@ export const handleServiceMapRequest = async (http, DSL, items?, setItems?, curr map[bucket.key].latency = bucket.average_latency.value; map[bucket.key].error_rate = _.round(bucket.error_rate.value, 2) || 0; map[bucket.key].throughput = bucket.doc_count; + if (minutesInDateRange != null) + map[bucket.key].throughputPerMinute = _.round(bucket.doc_count / minutesInDateRange, 2); }); if (currService) { diff --git a/dashboards-observability/test/constants.ts b/dashboards-observability/test/constants.ts index 04bb4f23b..bbd534073 100644 --- a/dashboards-observability/test/constants.ts +++ b/dashboards-observability/test/constants.ts @@ -148,7 +148,7 @@ export const TEST_SERVICE_MAP_GRAPH = { id: 1, label: 'order', size: 15, - title: 'order\n\nAverage latency: 90.1ms', + title: 'order\n\nAverage latency: 90.1ms\nError rate: 4.17%\nThroughput: 48', borderWidth: 0, color: 'rgba(158, 134, 192, 1)', }, @@ -156,7 +156,7 @@ export const TEST_SERVICE_MAP_GRAPH = { id: 2, label: 'analytics-service', size: 15, - title: 'analytics-service\n\nAverage latency: 12.99ms', + title: 'analytics-service\n\nAverage latency: 12.99ms\nError rate: 0%\nThroughput: 37', borderWidth: 0, color: 'rgba(210, 202, 224, 1)', }, @@ -164,7 +164,7 @@ export const TEST_SERVICE_MAP_GRAPH = { id: 3, label: 'database', size: 15, - title: 'database\n\nAverage latency: 49.54ms', + title: 'database\n\nAverage latency: 49.54ms\nError rate: 3.77%\nThroughput: 53', borderWidth: 0, color: 'rgba(187, 171, 212, 1)', }, @@ -172,7 +172,7 @@ export const TEST_SERVICE_MAP_GRAPH = { id: 4, label: 'frontend-client', size: 15, - title: 'frontend-client\n\nAverage latency: 207.71ms', + title: 'frontend-client\n\nAverage latency: 207.71ms\nError rate: 7.41%\nThroughput: 27', borderWidth: 0, color: 'rgba(78, 42, 122, 1)', }, @@ -180,7 +180,7 @@ export const TEST_SERVICE_MAP_GRAPH = { id: 5, label: 'inventory', size: 15, - title: 'inventory\n\nAverage latency: 183.52ms', + title: 'inventory\n\nAverage latency: 183.52ms\nError rate: 3.23%\nThroughput: 31', borderWidth: 0, color: 'rgba(95, 61, 138, 1)', }, @@ -188,7 +188,7 @@ export const TEST_SERVICE_MAP_GRAPH = { id: 6, label: 'authentication', size: 15, - title: 'authentication\n\nAverage latency: 139.09ms', + title: 'authentication\n\nAverage latency: 139.09ms\nError rate: 8.33%\nThroughput: 12', borderWidth: 0, color: 'rgba(125, 95, 166, 1)', }, @@ -196,7 +196,7 @@ export const TEST_SERVICE_MAP_GRAPH = { id: 7, label: 'payment', size: 15, - title: 'payment\n\nAverage latency: 134.36ms', + title: 'payment\n\nAverage latency: 134.36ms\nError rate: 9.09%\nThroughput: 11', borderWidth: 0, color: 'rgba(129, 99, 169, 1)', }, @@ -204,7 +204,7 @@ export const TEST_SERVICE_MAP_GRAPH = { id: 8, label: 'recommendation', size: 15, - title: 'recommendation\n\nAverage latency: 176.97ms', + title: 'recommendation\n\nAverage latency: 176.97ms\nError rate: 6.25%\nThroughput: 16', borderWidth: 0, color: 'rgba(100, 66, 143, 1)', },