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

TraceView - Optimization of queries #2349

Merged
merged 19 commits into from
Feb 12, 2025
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@
cy.get('[data-test-subj="searchAutocompleteTextArea"]').click();
cy.focused().type(' source = ');
cy.focused().type('{enter}');
cy.get('[data-test-subj="createAndSetButton"]').click({ force: true });

Check warning on line 224 in .cypress/integration/app_analytics_test/app_analytics.spec.js

View workflow job for this annotation

GitHub Actions / Lint

Do not use force on click and type calls
cy.get('.euiTableRow').should('have.length.lessThan', 1);
cy.get('[data-test-subj="applicationTitle"]').should('contain', nameThree);
cy.get('.euiBreadcrumb[href="#/"]').click();
Expand Down Expand Up @@ -308,8 +308,8 @@
cy.get('[data-test-subj="Number of connected servicesDescriptionList"]').should('contain', '3');
cy.get('[data-text="Errors"]').eq(1).click(); // Selecting errors tab within flyout
cy.get('.ytitle').contains('Error rate').should('exist');
cy.get('[data-test-subj="dataGridRowCell"]').eq(0).click({ force: true }); // absolutely doesn't click no matter what unless theres a double click

Check warning on line 311 in .cypress/integration/app_analytics_test/app_analytics.spec.js

View workflow job for this annotation

GitHub Actions / Lint

Do not use force on click and type calls
cy.get('button[data-test-subj="spanId-link"]').eq(0).click({ force: true });

Check warning on line 312 in .cypress/integration/app_analytics_test/app_analytics.spec.js

View workflow job for this annotation

GitHub Actions / Lint

Do not use force on click and type calls
cy.get('[data-test-subj="spanDetailFlyout"]').contains('Span detail').should('be.visible');
cy.get('[data-test-subj="ServiceDescriptionList"]').should('contain', 'authentication');
cy.get('[data-test-subj="euiFlyoutCloseButton"]').click();
Expand All @@ -322,7 +322,7 @@
cy.get('[title="03f9c770db5ee2f1caac0afc36db49ba"]').click();
cy.get('[data-test-subj="traceDetailFlyoutTitle"]').should('be.visible');
cy.get('[data-test-subj="traceDetailFlyout"]').within(($flyout) => {
cy.get('[data-test-subj="LatencyDescriptionList"]').should('contain', '224.99');
cy.get('[data-test-subj="LatencyDescriptionList"]').should('contain', '225.00');
});
cy.get('[data-test-subj="euiFlyoutCloseButton"]').click();
cy.get('[data-test-subj="traceDetailFlyout"]').should('not.exist');
Expand Down Expand Up @@ -396,7 +396,7 @@
cy.get('.euiTab[id="availability-panel"]').click();

cy.get('[data-test-subj="comboBoxInput"]').click();
cy.get('[data-test-subj="comboBoxOptionsList "] button span')

Check warning on line 399 in .cypress/integration/app_analytics_test/app_analytics.spec.js

View workflow job for this annotation

GitHub Actions / Lint

Do not use force on click and type calls
.contains('Time series')
.click({ force: true });
cy.focused().type('{enter}');
Expand Down Expand Up @@ -440,7 +440,7 @@
cy.get('[data-test-subj="main-content-visTab"]').click();
cy.get('.euiTab[id="availability-panel"]').click();
cy.get('[data-test-subj="comboBoxInput"]').click();
cy.get('[data-test-subj="comboBoxOptionsList "] button span')

Check warning on line 443 in .cypress/integration/app_analytics_test/app_analytics.spec.js

View workflow job for this annotation

GitHub Actions / Lint

Do not use force on click and type calls
.contains('Time series')
.click({ force: true });
cy.get('[data-test-subj="addAvailabilityButton"]').click();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,17 +97,17 @@

it('Renders data grid, flyout and filters', () => {
cy.get('.panel-title-count').contains('(11)').should('exist');
cy.get('.euiButton__text[title="Span list"]').click({ force: true });

Check warning on line 100 in .cypress/integration/trace_analytics_test/trace_analytics_traces.spec.js

View workflow job for this annotation

GitHub Actions / Lint

Do not use force on click and type calls
cy.contains('2 columns hidden').should('exist');

cy.get('.euiLink').contains(SPAN_ID).trigger('mouseover', { force: true });

Check warning on line 103 in .cypress/integration/trace_analytics_test/trace_analytics_traces.spec.js

View workflow job for this annotation

GitHub Actions / Lint

Do not use force on click and type calls
cy.get('button[data-datagrid-interactable="true"]').eq(0).click({ force: true });

Check warning on line 104 in .cypress/integration/trace_analytics_test/trace_analytics_traces.spec.js

View workflow job for this annotation

GitHub Actions / Lint

Do not use force on click and type calls
cy.get('button[data-datagrid-interactable="true"]').eq(0).click({ force: true }); // first click doesn't go through eui data grid

Check warning on line 105 in .cypress/integration/trace_analytics_test/trace_analytics_traces.spec.js

View workflow job for this annotation

GitHub Actions / Lint

Do not use force on click and type calls

cy.contains('Span detail').should('exist');
cy.contains('Span attributes').should('exist');
cy.get('.euiTextColor').contains('Span ID').trigger('mouseover');
cy.get('.euiButtonIcon[aria-label="span-flyout-filter-icon"').click({ force: true });

Check warning on line 110 in .cypress/integration/trace_analytics_test/trace_analytics_traces.spec.js

View workflow job for this annotation

GitHub Actions / Lint

Do not use force on click and type calls

cy.get('.euiBadge__text').contains('spanId: ').should('exist');
cy.contains('Spans (1)').should('exist');
Expand Down Expand Up @@ -282,4 +282,21 @@
cy.contains('Time spent by service').should('exist');
cy.get("[data-test-subj='span-gantt-chart-panel']").should('exist');
});

it('Checks tree view for specific traceId in Jaeger mode', () => {
cy.contains('15b0b4004a651c4c').click();
cy.get('[data-test-subj="globalLoadingIndicator"]').should('not.exist');

cy.get('.euiButtonGroup').contains('Tree view').click();
cy.get("[data-test-subj='treeExpandAll']").should('exist');
cy.get("[data-test-subj='treeCollapseAll']").should('exist');

// Waiting time for render to complete
cy.get("[data-test-subj='treeExpandAll']").click();
cy.get("[data-test-subj='treeCollapseAll']").click();

cy.get("[data-test-subj='treeViewExpandArrow']").should('have.length', 1);
cy.get("[data-test-subj='treeExpandAll']").click();
cy.get("[data-test-subj='treeViewExpandArrow']").should('have.length.greaterThan', 1);
});
});

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ import { HttpStart } from '../../../../../../../src/core/public';
import { TraceAnalyticsMode } from '../../../../../common/types/trace_analytics';
import { ServiceBreakdownPanel } from '../../../trace_analytics/components/traces/service_breakdown_panel';
import { SpanDetailPanel } from '../../../trace_analytics/components/traces/span_detail_panel';
import {
handlePayloadRequest,
handleServicesPieChartRequest,
handleTraceViewRequest,
} from '../../../trace_analytics/requests/traces_request_handler';
import { handlePayloadRequest } from '../../../trace_analytics/requests/traces_request_handler';
import { getListItem } from '../../helpers/utils';
import {
getOverviewFields,
getServiceBreakdownData,
} from '../../../trace_analytics/components/traces/trace_view_helpers';

interface TraceDetailRenderProps {
traceId: string;
Expand Down Expand Up @@ -78,6 +78,7 @@ export const TraceDetailRender = ({
mode={mode}
dataSourceMDSId={dataSourceMDSId}
isApplicationFlyout={true}
payloadData={payloadData}
/>
<EuiSpacer size="xs" />
<EuiHorizontalRule margin="s" />
Expand All @@ -95,10 +96,29 @@ export const TraceDetailRender = ({
}, [traceId, fields, serviceBreakdownData, colorMap, payloadData]);

useEffect(() => {
handleTraceViewRequest(traceId, http, fields, setFields, mode);
handleServicesPieChartRequest(traceId, http, setServiceBreakdownData, setColorMap, mode);
handlePayloadRequest(traceId, http, payloadData, setPayloadData, mode);
}, [traceId]);

useEffect(() => {
if (!payloadData) return;

try {
const parsedPayload = JSON.parse(payloadData);
const overview = getOverviewFields(parsedPayload, mode);
if (overview) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Question: Do we have a reset mechanism for these setters? If we previously loaded data with an overview, but a new payload arrives without an overview for some reason, it seems like the overview wouldn't be updated here, potentially leaving it with stale data. Should we handle this case explicitly?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

That one specifically is for application analytics and the useEffect triggers on payload change. As long as it is a valid payload the overview will get the new information from it. If the payload is successful from handlePayloadRequest causing new data the overview will always be generated from that information.

setFields(overview);
}

const {
serviceBreakdownData: queryServiceBreakdownData,
colorMap: queryColorMap,
} = getServiceBreakdownData(parsedPayload, mode);
setServiceBreakdownData(queryServiceBreakdownData);
setColorMap(queryColorMap);
} catch (error) {
console.error('Error processing payloadData:', error);
}
}, [payloadData, mode]);

return renderContent;
};
61 changes: 61 additions & 0 deletions public/components/trace_analytics/components/common/constants.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit. Should we consider moving this common folder one level up? It seems like each logical component should have its own common folder.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think having it on the same level as our helper_functions makes sense as this would be for constants shared between trace_analytics including both traces and services as we continue to refactor.

* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

// Conversion factor for nanoseconds to milliseconds
export const NANOS_TO_MS = 1e6;

export const MILI_TO_SEC = 1000;

export const pieChartColors = [
'#7492e7',
'#c33d69',
'#2ea597',
'#8456ce',
'#e07941',
'#3759ce',
'#ce567c',
'#9469d6',
'#4066df',
'#da7596',
];

export interface Span {
traceId: string;
spanId: string;
traceState: string;
parentSpanId: string;
name: string;
kind: string;
startTime: string;
endTime: string;
durationInNanos: number;
serviceName: string;
events: any[];
links: any[];
droppedAttributesCount: number;
droppedEventsCount: number;
droppedLinksCount: number;
traceGroup: string;
traceGroupFields: {
endTime: string;
durationInNanos: number;
statusCode: number;
};
status: {
code: number;
};
instrumentationLibrary: {
name: string;
version: string;
};
}

export interface ParsedHit {
_index: string;
_id: string;
_score: number;
_source: Span;
sort?: any[];
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { FieldCapResponse } from '../../../common/types';
import { serviceMapColorPalette } from './color_palette';
import { FilterType } from './filters/filters';
import { ServiceObject } from './plots/service_map';
import { NANOS_TO_MS, ParsedHit } from './constants';

const missingJaegerTracesConfigurationMessage = `The indices required for trace analytics (${JAEGER_INDEX_NAME} and ${JAEGER_SERVICE_INDEX_NAME}) do not exist or you do not have permission to access them.`;

Expand Down Expand Up @@ -615,3 +616,39 @@ export const generateServiceUrl = (

return url;
};

/*
* Parse an ISO timestamp with up to nanosecond precision.
* For example, "2025-01-28T03:12:37.293990144Z" will be converted
* to a number representing the total nanoseconds since the Unix epoch.
*/
export function parseIsoToNano(iso: string): number {
const match = iso.match(/^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})(?:\.(\d+))?Z$/);
if (!match) {
throw new Error(`Invalid ISO timestamp: ${iso}`);
}
// Parse the base part using Date.parse (which gives ms)
const baseMs = new Date(match[1] + 'Z').getTime();
// Get the fractional part (if any), pad to 9 digits for nanosecond precision
let fraction = match[2] || '0';
fraction = fraction.padEnd(9, '0'); // ensure it has 9 digits
return baseMs * NANOS_TO_MS + Number(fraction);
}

export const parseHits = (payloadData: string): ParsedHit[] => {
try {
const parsed = JSON.parse(payloadData);
let hits: ParsedHit[] = [];

if (parsed.hits && Array.isArray(parsed.hits.hits)) {
hits = parsed.hits.hits;
} else if (Array.isArray(parsed)) {
hits = parsed;
}

return hits;
} catch (error) {
console.error('Error processing payloadData:', error);
return [];
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ exports[`Service breakdown panel component renders service breakdown panel 1`] =
data={
Array [
Object {
"hoverinfo": "label+percent",
"hovertemplate": "%{label}<br>%{value:.2f}%<extra></extra>",
"labels": Array [
"inventory",
Expand Down Expand Up @@ -177,6 +178,7 @@ exports[`Service breakdown panel component renders service breakdown panel 1`] =
data={
Array [
Object {
"hoverinfo": "label+percent",
"hovertemplate": "%{label}<br>%{value:.2f}%<extra></extra>",
"labels": Array [
"inventory",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ exports[`SpanDetailPanel component renders correctly with default props 1`] = `
<EuiFlexItem>
<PanelTitle
title="Spans"
totalItems={0.5}
totalItems={0}
/>
</EuiFlexItem>
<EuiFlexItem
Expand All @@ -42,7 +42,6 @@ exports[`SpanDetailPanel component renders correctly with default props 1`] = `
>
<EuiButtonGroup
idSelected="timeline"
isDisabled={false}
legend="Select view of spans"
onChange={[Function]}
options={
Expand Down Expand Up @@ -74,26 +73,7 @@ exports[`SpanDetailPanel component renders correctly with default props 1`] = `
grow={false}
>
<Plt
data={
Array [
Object {
"hoverinfo": "none",
"marker": Object {
"color": "#fff",
},
"orientation": "h",
"showlegend": false,
"type": "bar",
"width": 0.4,
"x": Array [
10,
],
"y": Array [
"service1",
],
},
]
}
data={Array []}
layout={
Object {
"dragmode": "select",
Expand All @@ -116,7 +96,7 @@ exports[`SpanDetailPanel component renders correctly with default props 1`] = `
},
"type": "rect",
"x0": 0,
"x1": 22,
"x1": 0,
"xref": "x",
"y0": 0,
"y1": 1,
Expand All @@ -128,7 +108,7 @@ exports[`SpanDetailPanel component renders correctly with default props 1`] = `
"color": "#91989c",
"range": Array [
0,
22,
0,
],
"showline": true,
"side": "top",
Expand All @@ -153,31 +133,10 @@ exports[`SpanDetailPanel component renders correctly with default props 1`] = `
}
>
<Plt
data={
Array [
Object {
"hoverinfo": "none",
"marker": Object {
"color": "#fff",
},
"orientation": "h",
"showlegend": false,
"text": "10.00 ms",
"textposition": "outside",
"type": "bar",
"width": 0.4,
"x": Array [
10,
],
"y": Array [
"service1",
],
},
]
}
data={Array []}
layout={
Object {
"height": 85,
"height": 60,
"margin": Object {
"b": 30,
"l": 150,
Expand All @@ -191,7 +150,7 @@ exports[`SpanDetailPanel component renders correctly with default props 1`] = `
"color": "#91989c",
"range": Array [
0,
22,
0,
],
"showline": true,
"side": "top",
Expand All @@ -200,12 +159,8 @@ exports[`SpanDetailPanel component renders correctly with default props 1`] = `
"yaxis": Object {
"fixedrange": true,
"showgrid": false,
"ticktext": Array [
"",
],
"tickvals": Array [
"service1",
],
"ticktext": Array [],
"tickvals": Array [],
},
}
}
Expand Down
Loading
Loading