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

Otel collector mappings to APM agent #6

Conversation

miloszmarcinkowski
Copy link

@miloszmarcinkowski miloszmarcinkowski commented Sep 18, 2024

Closes elastic#192749

Summary

This is part of an effort (elastic#192606) in replacing the usage of _source with fields while migrating APM to Otel data collector.

In this PR, we're adding mappers which expose only fields used in APM. This assures we stay type safe and we provide only necessary fields.

@miloszmarcinkowski miloszmarcinkowski force-pushed the 192749-refactor-the-normalize-function-into-per-data-structure-serialization-logic branch 5 times, most recently from f70eafd to a85fbf0 Compare September 20, 2024 11:57
Transaction | Span | APMError,
'observer'
>;
const hit = serviceVersionMapping(response.hits.hits[0]?.fields);
Copy link

Choose a reason for hiding this comment

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

If this returns APMError, special care will need to be taken. I'm not sure that fields is capable of returning fields from error.exception.stacktrace. We may have to depend on _source in this situation.

Copy link

Choose a reason for hiding this comment

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

Here's an example query for demonstration.

GET /_search 
{
        "fields": ["error.exception.message","error.exception.stacktrace.exclude_from_grouping"],
            "query": {
        "exists": {
            "field": "error.exception.message"
        }  
    
    },
    "size": 1
}

Copy link
Author

@miloszmarcinkowski miloszmarcinkowski Sep 25, 2024

Choose a reason for hiding this comment

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

I don't know if that's even a problem since we Pick only observer property from the type when doing assertion. And, this is the only usage as far I can see: https://github.com/jennypavlova/kibana/pull/6/files/f52184082c369d7f63e03b250d0a09c212644f48#diff-8c8160096093f32c6f9dd260780403b55fe20f94f2c99de76cc9d54c872cb4e8R701

I'm not entirely sure why we assert type to Transaction | Span | APMError. I couldn't even find a way to trigger this code on my local environment. That's why I left todo comment to double-check it later, but I think I'm out of ideas here. Do you have any suggestion?

@@ -174,6 +174,7 @@ export async function getServiceMetadataDetails({
const response = await apmEventClient.search('get_service_metadata_details', params);

const fields = response.hits.hits[0]?.fields;
// todo: missing `fields` property?
Copy link

Choose a reason for hiding this comment

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

?

Copy link
Author

Choose a reason for hiding this comment

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

I haven't found fields property in get_service_metadata_details when running with otel-apm-e2e-poc

I'm not sure whether we shouldn't use fields property here or there isn't enough data in ES or there is some backend work needed here to get this field. What do you think @bryce-b?

image

Copy link

Choose a reason for hiding this comment

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

I see. I'll take a closer look. It's unclear if it's only using the aggregations and fields are not required.

Copy link

@bryce-b bryce-b Sep 24, 2024

Choose a reason for hiding this comment

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

The issue looks like the fields selected in the query were copied from the _source values, which are root attributes.

      fields: [SERVICE, AGENT, HOST, CONTAINER, KUBERNETES, CLOUD, LABEL_TELEMETRY_AUTO_VERSION],

which are the values:

  "fields": [
    "service",
    "agent",
    "host",
    "container",
    "kubernetes",
    "cloud",
    "labels.telemetry_auto_version"
  ],

We should either use fields: ['*'], or specify the leaf attributes we want populated : eg: service.node.name, service.environment, service.framework.name, etc. instead of just service.

links: [
{
trace: {
// todo(milosz): confirm `span.links.trace.id` format
Copy link

Choose a reason for hiding this comment

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

span links can potentially be an array of many. span.links.trace.id and span.links.span.id will need to be merged and iterated over and added to span.links[]. Below is a screenshot showing their structure in discover.
Screenshot 2024-09-23 at 08 22 48

Copy link
Author

Choose a reason for hiding this comment

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

How did you get those? I see only single values in span.links.span.id and span.links.trace.id when running synthtrace scenario (span_links.ts). Did you make some aggregations?

However, even for single values in span.links.span.id and span.links.trace.id indeed the mapping is incorrect because we expect an array of { trace: { id: string }; span: { id: string }; } so we can get multiple values. With that in mind, I'm not sure how this data will be represented in fields property.

My understanding is that in source fields we might get:

{
  span: {
    link: [
      { trace: { id: 'aaa', span: { id: 'aaa' } } },
      { trace: { id: 'bbb', span: { id: 'bbb' } } },
      { trace: { id: 'ccc', span: { id: 'ccc' } } },
    ],
  },
}

So what format does expect in fields in this situation? I guess this is the problem with all array of object properties, I'm not sure how this is solved 😅

};

// todo(milosz): test with otel APM POC, mapping format wasn't confirmed with `fields`
export const spanMapping = (fields: Partial<Record<string, unknown[]>>): Span => {
Copy link

Choose a reason for hiding this comment

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

I noticed that SpanRaw has a couple of additional fields not represented here, such as stacktrace. Do those need to be represented here?

Copy link
Author

Choose a reason for hiding this comment

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

While checking it I found that get_span can give completely different response depending on span. When I first created mapping I based it on response from edge-lite-oblt cluster, now I checked otel-apm-e2e-poc and received different response format. I covered two possible responses in the updated mapping, but I wonder if there are other unknown yet responses for that one. BTW I haven't found stacktrace in neither of two.

Copy link
Author

Choose a reason for hiding this comment

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

get_span in otel-apm-e2e-poc:
Screenshot 2024-09-25 at 16 52 38

get_span in edge-lite-oblt:
Screenshot 2024-09-25 at 16 55 25

@@ -103,7 +103,7 @@ export async function getServiceAgent({
return {};
}

const { agent, service, cloud } = normalizeFields(response.hits.hits[0].fields) as ServiceAgent;
const { agent, service, cloud } = serviceAgentName(response.hits.hits[0].fields) as ServiceAgent;
Copy link
Owner

Choose a reason for hiding this comment

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

In most places where I used as we can replace if with return type in the mapper- so the serviceAgentName return type should be ServiceAgent so we can get rid of the casting.


export const CHILD_ID = 'child.id';

export const LOG_LEVEL = 'log.level';

Copy link
Owner

Choose a reason for hiding this comment

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

The Otel-specific fields (under attributes and resource.attributes should not be part of the new data mapping - we use those fields only in the metadata table (we need to confirm if we show them anywhere else). We should keep the mappers close to the APM types

cc: @AlexanderWert and @felixbarny I remember we discussed that, let me know if I am missing something here

Choose a reason for hiding this comment

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

I was expecting the mapping in es_fields_mappings.ts to not produce Record<string, unknown[]> but instead types such as TransactionRaw

@@ -58,8 +56,7 @@ export async function getLinkedParentsOfSpan({
},
});

const fields = response.hits.hits[0]?.fields;
const fieldsNorm = normalizeFields(fields) as unknown as TransactionRaw | SpanRaw | undefined;
const fieldsNorm = linkedParentsOfSpanMapping(response.hits.hits[0]?.fields);
Copy link
Owner

Choose a reason for hiding this comment

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

@AlexanderWert I remember we discussed that we don't support the span links ATM so should we find a way to "skip" them or "hide" the tab in case of otel data (Together with @miloszmarcinkowski we found another UI Error we have here because of SpanLink so I am wondering how to handle that in this case)

Choose a reason for hiding this comment

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

yes, let's just hide stuff related to Links in the UI and NOT change the query logic for SpanLinks for now

@miloszmarcinkowski miloszmarcinkowski force-pushed the 192749-refactor-the-normalize-function-into-per-data-structure-serialization-logic branch from f3c89ca to 20a7b79 Compare September 26, 2024 15:42

export const CHILD_ID = 'child.id';

export const LOG_LEVEL = 'log.level';

Choose a reason for hiding this comment

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

I was expecting the mapping in es_fields_mappings.ts to not produce Record<string, unknown[]> but instead types such as TransactionRaw

client: {
ip: normalizeValue<string>(fields[CLIENT_IP]),
},
http: {

Choose a reason for hiding this comment

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

We should not be mapping specific request and response headers. It looks like any other header would be lost here. Instead, we should just generically map fields named http.request.headers.*/http.response.headers.*.

Comment on lines +391 to +353
labels: {
some_resource_attribute: normalizeValue<string>(fields[LABEL_SOME_RESOURCE_ATTRIBUTE]),
},

Choose a reason for hiding this comment

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

Similar to the comment about HTTP headers, we should not hard-code specific labels and instead map any fields matching labels.*.

};
};

export const transactionMapping = (fields: Partial<Record<string, unknown[]>>) => {

Choose a reason for hiding this comment

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

Can we make this returning TransactionRaw instead of Record<string, unknown[]>?

Copy link
Author

Choose a reason for hiding this comment

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

Thank you for review @felixbarny!

Record<string, unknown[]> is the parameter type, not return type. If the return type is missing, this means that I couldn't match it with any existing type interface, but typescript does automatically define an implicit type based on the return object format. So, we're left with correct typing at the end.

Today, I have a plan to check all mappings for a possible interface type that will match the returning type.

@miloszmarcinkowski miloszmarcinkowski force-pushed the 192749-refactor-the-normalize-function-into-per-data-structure-serialization-logic branch from 294ac0e to 8666c00 Compare September 27, 2024 15:34
bryce-b and others added 9 commits September 27, 2024 10:00
…ructure-serialization-logic' into bryce-192749-refactor-the-normalize-function-into-per-data-structure-serialization-logic

# Conflicts:
#	x-pack/plugins/observability_solution/apm/server/utils/es_fields_mappings.ts
…tion-into-per-data-structure-serialization-logic

added span-link normalization and fixed errors in getTransaction
@miloszmarcinkowski
Copy link
Author

miloszmarcinkowski commented Sep 30, 2024

@bryce-b PR take away:

  • Created logic to handle Span links. This will be address in second phase, for now use old _source
  • Missing namespace in Kubernetes icon popover issue

…apm-ui-replacing-_source-with-fields' into 192749-refactor-the-normalize-function-into-per-data-structure-serialization-logic

# Conflicts:
#	x-pack/plugins/observability_solution/apm/common/correlations/types.ts
#	x-pack/plugins/observability_solution/apm/server/routes/errors/get_error_groups/get_error_group_main_statistics.ts
#	x-pack/plugins/observability_solution/apm/server/routes/errors/get_error_groups/get_error_sample_details.ts
#	x-pack/plugins/observability_solution/apm/server/routes/services/get_service_metadata_details.ts
@miloszmarcinkowski miloszmarcinkowski marked this pull request as ready for review September 30, 2024 11:24
@jennypavlova jennypavlova merged commit 634b899 into jennypavlova:192606-poc-otel-data-with-apm-ui-replacing-_source-with-fields Sep 30, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants