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

add optional fields manually #4750

Conversation

codeboten
Copy link
Contributor

@codeboten codeboten commented Jan 25, 2022

Description:
The following PR manually adds support for Min/Max optional fields to ExponentialHistogramDataPoint/HistogramDataPoint. Some things to note:

  • Once this PR is merged, we will no longer be able to use genproto to update the proto files as this is editing them manually.
  • Ideally in the future we would move all the fields to serialize/deserialize directly from pdata, that being said I didn't want to start with such a major refactor here.
  • Cleanup to remove proto generation code could also be done here, if we want to move forward with this change, I will open a separate PR to address this.

I believe once there is a pattern for a given field type, this approach should be easy enough to follow.

Link to tracking Issue: #4258

Testing: Added tests for serializing/deserializing the data. I plan on doing some manual tests with a mock server that uses the proto as generated by the google proto library. This has been done: #4750 (comment)

@codeboten codeboten changed the title Codeboten/add optional fields manually add optional fields manually Jan 25, 2022
Comment on lines 31 to 34
// IsEmpty returns true if id doesn't contain at least one non-zero byte.
func (t OptionalDouble) IsEmpty() bool {
return t.orig.IsEmpty()
}
Copy link
Member

Choose a reason for hiding this comment

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

Would be good to be kind of in sync with generated protobuf code, is this how they call the func?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The generated protos checks for nil values. You can see the generated protos here

codeboten@7811abd#diff-7276c207e936f474133fad60cb485d044f4a8cc3931300c5b82b34fa2a238abdR810-R821

Here's an example of GetMin:

func (x *HistogramDataPoint) GetMin() float64 {
	if x != nil && x.Min != nil {
		return *x.Min
	}
	return 0
}

It uses the following to marshal:

if m.Max != nil {
		i -= 8
		binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(*m.Max))))
		i--
		dAtA[i] = 0x61
	}
	if m.Min != nil {
		i -= 8
		binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(*m.Min))))
		i--
		dAtA[i] = 0x59
	}

Following the IsEmpty pattern was done to remain consistent with the trace/span ID custom types already present in pdata.

Comment on lines 1369 to 1426
// Min returns the min associated with this HistogramDataPoint.
func (ms HistogramDataPoint) Min() OptionalDouble {
return OptionalDouble{orig: ((*ms.orig).Min)}
}

// SetMin replaces the min associated with this HistogramDataPoint.
func (ms HistogramDataPoint) SetMin(v OptionalDouble) {
(*ms.orig).Min = v.orig
}
Copy link
Member

Choose a reason for hiding this comment

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

Another approach:

// Min returns the min associated with this HistogramDataPoint.
func (ms HistogramDataPoint) Min() float64 {
	return (*ms.orig).Min.DoubleValue
}

func (ms HistogramDataPoint) HasMin() bool {
	!(*ms.orig).Min.IsEmpty()
}


func (ms HistogramDataPoint) SetMin(v float64) {
	(*ms.orig).Min.DoubleValue = v
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Are you suggesting we do away with the OptionalDouble type in pdata? I don't have a strong preference to keeping it and this approach would give us essentially the same functionality without an additional type.

Copy link
Member

Choose a reason for hiding this comment

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

That is my idea.. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just tried it and I agree it's better. I've added the HasMin HasMax methods to metrics.go instead of creating a new optional field generator. We can always add one later if needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Decided to add a template for optional fields after all, since this will be re-used for min/max and sum in the near future. PTAL @bogdandrutu

@codeboten codeboten force-pushed the codeboten/add-optional-fields-manually branch from cab5b78 to 68ed192 Compare February 2, 2022 18:04
@codecov
Copy link

codecov bot commented Feb 2, 2022

Codecov Report

Merging #4750 (620032e) into main (a7ef079) will decrease coverage by 0.01%.
The diff coverage is 88.88%.

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #4750      +/-   ##
==========================================
- Coverage   90.70%   90.69%   -0.02%     
==========================================
  Files         179      180       +1     
  Lines       10566    10636      +70     
==========================================
+ Hits         9584     9646      +62     
- Misses        763      771       +8     
  Partials      219      219              
Impacted Files Coverage Δ
model/pdata/generated_metrics.go 96.33% <71.42%> (-0.74%) ⬇️
internal/otlptext/databuffer.go 100.00% <100.00%> (ø)
model/internal/data/optional.go 100.00% <100.00%> (ø)
component/componenttest/nop_exporter.go 100.00% <0.00%> (ø)
component/componenttest/nop_receiver.go 100.00% <0.00%> (ø)
component/componenttest/nop_extension.go 100.00% <0.00%> (ø)
component/componenttest/nop_processor.go 100.00% <0.00%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update a7ef079...620032e. Read the comment docs.

@codeboten codeboten force-pushed the codeboten/add-optional-fields-manually branch from dbf8f28 to 620032e Compare February 4, 2022 17:42
@codeboten
Copy link
Contributor Author

To validate the proto works as expected, I tested this PR against a Python gRPC server which used the generated code from open-telemetry/opentelemetry-proto#279. Here's the export request with min/max fields:

      histogram {
        data_points {
          count: 10
          bucket_counts: 21
          bucket_counts: 32
          min: 0.1
          max: 100.0
        }
      }

And here's another with only one of the fields:

      histogram {
        data_points {
          count: 10
          bucket_counts: 21
          bucket_counts: 32
          max: 100.0
        }
      }

@codeboten
Copy link
Contributor Author

I believe to move this PR forward, I can split it into two PRs:

  1. all the internals to support optionals (this is everything in pdata except adding the min/max fields). This could be merged before the protos to make adding the fields easy once the proto PRs merge.
  2. the code that adds min/max + otlp serialization. This PR could either exist in draft mode or I can wait to open until the proto is merged.

Thoughts?

"math"
)

type OptionalDouble struct {
Copy link
Member

Choose a reason for hiding this comment

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

This is "OptionalFixed64" correct (is not the int64 from proto is the fixed64)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Correct, this is a fixed64 field.

Comment on lines +2495 to +2508
if !m.Max.IsEmpty() {
i -= m.Max.Size()
if _, err := m.Max.MarshalTo(dAtA[i:]); err != nil {
return 0, err
}
dAtA[i] = 0x61
}
if !m.Min.IsEmpty() {
i -= m.Min.Size()
if _, err := m.Min.MarshalTo(dAtA[i:]); err != nil {
return 0, err
}
dAtA[i] = 0x59
}
Copy link
Member

Choose a reason for hiding this comment

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

Is this manually written? How can we maintain this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Correct, the process here would be that it would have to be added back to the proto after make genproto is run. Any thoughts on how to best address this? I suppose we could add this marshal/unmarshal methods outside of the generated code, and remove the function as part of genproto?

I'm open to alternatives here.

Copy link
Member

Choose a reason for hiding this comment

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

Can we automate the patching as part of make genproto step? Also extract the manually written as much as possible as a func and move to a regular (non-generated) file and call it from here (minimize the line count of manual code present in this auto-generated file).

Copy link
Member

Choose a reason for hiding this comment

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

Also add a comment clearly saying where does the manual code in this file start and where it ends. This should help to locate the right place to manually patch after re-generation and if auto-patching fails.

@github-actions
Copy link
Contributor

This PR was marked stale due to lack of activity. It will be closed in 14 days.

@github-actions github-actions bot added the Stale label Feb 24, 2022
@codeboten
Copy link
Contributor Author

Closing in favour of #4929

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants