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

Support pganalyze tracestate to set start time of the span #475

Merged
merged 4 commits into from
Nov 21, 2023

Conversation

keiko713
Copy link
Contributor

Problems

Currently, we generate an OpenTelemetry tracing span using sample.OccurredAt and sample.RuntimeMs derived from logs. This approach generally functions well, however, it can encounter precision issues due to the log timestamp format.
Specifically, timestamps with lower precision (e.g., hundredths of a second like 12:34:56.78, seen with RDS) result in spans ending at the 100th millisecond mark. This leads to misalignment, causing the span to appear earlier or later than it should.

Solution

To solve this, we would like to introduce a new tracestate for pganalyze. With tracestate, we can provide a vendor-specific trace identification information. We will use the key pganalyze for this, while the value is a list, containing a member with semicolon (;) separated format. Each member should also have key and value, separated with colon (:).
We use a member key t to specify the start time of the query, the format should be the Unix time in second, allowing decimals to present up to nano seconds.
Here is an example of tracestate to be passed:

tracestate:pganalyze=t:1697666938.6297212;x=123;y=567

See OpenTelemetry document for how they use this tracestate. When this is used together with OpenTelemetry tracestate, this will look like:

tracestate:pganalyze=t:1697666938.6297212;x=123;y=567,ot=p:8;r:62;k1:13

PR summary

With this PR, we will add the capability for the collector to read a tracestate for pganalyze and use the time specified with t as the start time of the tracing span.

if strings.Contains(part, ":") {
keyAndValue := strings.SplitN(part, ":", 2)
if strings.TrimSpace(keyAndValue[0]) == "t" {
if startInSec, err := strconv.ParseFloat(keyAndValue[1], 64); err == nil {
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 really feel like there should be some easy way to do this, I might be simply missing something.
Also, I could try some another way, like splitting keyAndValue[1] with . then parsing both of them to int. In that way, I don't need to fight with float in test.

Copy link
Contributor

Choose a reason for hiding this comment

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

+1 for parsing this as two separate values. It looks like Go's time.Time has an internal representation that allows it more nanosecond precision than can be stored in a 64-bit float. I don't think it will really matter in practice, but we might as well get this right. And simplifying the test is a nice bonus.

Since that's going to complicate this branch, I think we should extract that to a separate function like func parseTraceStateStartTime(value string) (time.Time, error) (no strong feelings on the name, just throwing something out there).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah @lfittl sorry looks like you just approved (forgot to push) but I did some refactoring around here and put that functionality to util. Well hope you're okay with that? Or feel free to 👀 again.

Copy link
Member

Choose a reason for hiding this comment

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

@keiko713 Refactoring looks good!

@keiko713 keiko713 requested a review from a team November 17, 2023 08:00
logs/querysample/tracing.go Outdated Show resolved Hide resolved
if strings.Contains(part, ":") {
keyAndValue := strings.SplitN(part, ":", 2)
if strings.TrimSpace(keyAndValue[0]) == "t" {
if startInSec, err := strconv.ParseFloat(keyAndValue[1], 64); err == nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

+1 for parsing this as two separate values. It looks like Go's time.Time has an internal representation that allows it more nanosecond precision than can be stored in a 64-bit float. I don't think it will really matter in practice, but we might as well get this right. And simplifying the test is a nice bonus.

Since that's going to complicate this branch, I think we should extract that to a separate function like func parseTraceStateStartTime(value string) (time.Time, error) (no strong feelings on the name, just throwing something out there).

logs/querysample/tracing.go Show resolved Hide resolved
logs/querysample/tracing_test.go Outdated Show resolved Hide resolved
logs/querysample/tracing.go Outdated Show resolved Hide resolved
@keiko713 keiko713 merged commit def6956 into main Nov 21, 2023
3 checks passed
@keiko713 keiko713 deleted the support-pganalyze-tracestate branch November 21, 2023 10:27
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.

3 participants