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

promql(histograms): Change sample total calculation for histograms #12609

Merged
merged 3 commits into from
Oct 18, 2023

Conversation

marctc
Copy link
Contributor

@marctc marctc commented Jul 28, 2023

  • This PR changes the way of number samples are calculated when doing query operations with
    histograms.

  • Added method for FloatHistograms that calculates the size in bytes of the whole struct
    and its fields.

  • Added method that calculates number of samples (4 bytes per sample) of histogram in
    HPoint struct.

Fixes #11555

@marctc marctc force-pushed the query_max_samples_float_histogram branch 2 times, most recently from f60d015 to fd4895f Compare July 31, 2023 11:07
Copy link
Member

@beorn7 beorn7 left a comment

Choose a reason for hiding this comment

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

Thank you very much.

Very cool calculation of the actual size of a histogram. However, as explained in the comment, we have to do something less expensive and safer (which ideally still yields the same result).

promql/value.go Outdated
@@ -168,6 +168,21 @@ func (p HPoint) MarshalJSON() ([]byte, error) {
return json.Marshal([...]interface{}{float64(p.T) / 1000, h})
}

// histogramSamples returns the number of samples representing the histogram.
// each sample is 4 bytes.
func (p HPoint) histogramSamples() int {
Copy link
Member

Choose a reason for hiding this comment

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

I would call it more like histogramSize and explain it as "measured in relation to a conventional FPoint i.e. 16 bytes.

promql/value.go Outdated
// histogramSamples returns the number of samples representing the histogram.
// each sample is 4 bytes.
func (p HPoint) histogramSamples() int {
return p.H.Size() / 4
Copy link
Member

Choose a reason for hiding this comment

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

I think it should be (p.H.Size() + 8) / 16.

In TotalSamples below, we count each FPoint as "one sample". One FPoint is 16 bytes, a 64bit int timestamp and a 64bit float sample value. So "one sample" so far has been 16 bytes.

For an HPoint, we have the same timestamp (8 bytes), and then a number of bytes in the histogram, the sum of which we need to divide by the 16 bytes we considered "one sample" so far.

promql/value.go Outdated
}

// totalHistogramSamples returns the total number of samples in the given slice of histograms.
func totalHistogramSamples(histograms []HPoint) int {
Copy link
Member

Choose a reason for hiding this comment

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

Same naming thoughts as above.

model/histogram/float_histogram.go Show resolved Hide resolved
@beorn7
Copy link
Member

beorn7 commented Aug 15, 2023

I believe

ev.currentSamples += len(result)
and
tempNumSamples += len(result)
need an update, too. Which means we have to dive into the Vector and check if it is a float sample or a histogram sample.

@@ -267,7 +282,7 @@ func (m Matrix) String() string {
func (m Matrix) TotalSamples() int {
Copy link
Member

Choose a reason for hiding this comment

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

The doc comment here should be updated to reflect that histograms are taken into account in a weighted fashion.

@beorn7
Copy link
Member

beorn7 commented Aug 15, 2023

Another code path that needs to change: https://github.com/prometheus/prometheus/blob/d6e1b1acdb1231f5bb694bc482b568ccea2b40af/promql/engine.go#L1339C6-L1339C6

totalSamples += len(s.Floats) + len(s.Histograms)

@beorn7
Copy link
Member

beorn7 commented Aug 15, 2023

And another one:

prometheus/promql/engine.go

Lines 1813 to 1824 in d6e1b1a

if len(mat[i].Floats) > 0 {
mat[i].Floats = append(mat[i].Floats, FPoint{
T: ts,
F: mat[i].Floats[0].F,
})
} else {
mat[i].Histograms = append(mat[i].Histograms, HPoint{
T: ts,
H: mat[i].Histograms[0].H,
})
}
ev.currentSamples++

@beorn7
Copy link
Member

beorn7 commented Aug 15, 2023

And another one:

prometheus/promql/engine.go

Lines 1862 to 1869 in d6e1b1a

vec = append(vec, Sample{
Metric: s.Labels(),
T: t,
F: f,
H: h,
})
ev.currentSamples++

@beorn7
Copy link
Member

beorn7 commented Aug 15, 2023

Another one:

prometheus/promql/engine.go

Lines 2034 to 2036 in d6e1b1a

for drop = 0; histograms[drop].T < mint; drop++ { // nolint:revive
}
ev.currentSamples -= drop

@beorn7
Copy link
Member

beorn7 commented Aug 15, 2023

Sorry, this whole sample tracking now appears way more complicated than I have anticipated.
There is also the QuerySamples type, which we need to double-check if it is always updated with the weighted sample count. It should also get updates in its doc comments to reflect how histograms are weighted in the sample count.

@marctc marctc requested a review from beorn7 August 24, 2023 14:23
@marctc marctc force-pushed the query_max_samples_float_histogram branch from 380076c to ff69c62 Compare August 24, 2023 14:27
@marctc marctc force-pushed the query_max_samples_float_histogram branch from b9189f0 to a2b2106 Compare August 25, 2023 15:05
@beorn7
Copy link
Member

beorn7 commented Sep 7, 2023

Apologies for long delays. Still fighting my way through the review backlog…

Copy link
Member

@beorn7 beorn7 left a comment

Choose a reason for hiding this comment

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

Very good. I only have a few naming and comment nits, and I think the size calculation needs a fix. See my comments. Maybe I'm missing something.
Otherwise, this looks fine.

Apologies again for the review delay.

// NOTE: this is only valid for 64 bit architectures.
func (fh *FloatHistogram) Size() int {
// Size of each slice separately
posSpanSize := len(fh.PositiveSpans) * 8 // 8 bytes - int64 * 2
Copy link
Member

Choose a reason for hiding this comment

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

int64 * 2 isn't really a Span. It's int32 + uint32. Which still results in 8 bytes, so the number is correct.

func (fh *FloatHistogram) Size() int {
// Size of each slice separately
posSpanSize := len(fh.PositiveSpans) * 8 // 8 bytes - int64 * 2
negSpanSize := len(fh.NegativeSpans) * 8 // 8 bytes - int64 * 2
Copy link
Member

Choose a reason for hiding this comment

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

Same here.


// Total size of the struct

// fh is 32 bytes
Copy link
Member

Choose a reason for hiding this comment

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

Where are these coming from?

// fh.NegativeSpans is 24 bytes
// fh.PositiveBuckets is 24 bytes
// fh.NegativeBuckets is 24 bytes
structSize := 165
Copy link
Member

Choose a reason for hiding this comment

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

If I run the following on my (amd64) machine:

	fh := histogram.FloatHistogram{}
	fmt.Println(unsafe.Sizeof(fh))

I get 136 bytes.

Which makes sense to me. All your numbers above seem fine, except the fh is 32 bytes, which I don't understand. If I add them all up, I get 133 bytes. However, the 1 byte CounterResetHint takes effectively 4 bytes because of alignment in the struct, so 133 + 3 = 136.

So the question is where you got the 32 bytes from at the top?

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 honestly can't remember....perhaps when I had the function calculating everything automatically? You are right, I'm getting the same result, changing now.

promql/value.go Show resolved Hide resolved
promql/value.go Outdated
return (p.H.Size() + 8) / 16
}

// totalHistogramSize returns the total number of samples in the given slice of histograms.
Copy link
Member

Choose a reason for hiding this comment

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

histograms → HPoints

promql/value.go Outdated Show resolved Hide resolved
promql/value.go Outdated
@@ -168,6 +168,23 @@ func (p HPoint) MarshalJSON() ([]byte, error) {
return json.Marshal([...]interface{}{float64(p.T) / 1000, h})
}

// histogramSize returns the number of samples representing the histogram.
Copy link
Member

Choose a reason for hiding this comment

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

How about: "histogramSize returns the size of the HPoint compared to the size of an FPoint. Or in other words: This HPoint is n times larger than an FPoint, where n is the returned number."

promql/value.go Outdated
}

// totalHistogramSize returns the total number of samples in the given slice of histograms.
func totalHistogramSize(histograms []HPoint) int {
Copy link
Member

Choose a reason for hiding this comment

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

I would call this totalHPointSize as it includes the timestamp and the pointer to the actual histogram.

@beorn7
Copy link
Member

beorn7 commented Oct 14, 2023

@marctc is this still on your radar? I think it's very little left to do, and it would be nice to get this in soon.

@marctc
Copy link
Contributor Author

marctc commented Oct 16, 2023

@marctc is this still on your radar? I think it's very little left to do, and it would be nice to get this in soon.

Yep, looking into it today 👍

@marctc marctc force-pushed the query_max_samples_float_histogram branch from cbe95f9 to 27d9497 Compare October 16, 2023 13:05
Copy link
Member

@beorn7 beorn7 left a comment

Choose a reason for hiding this comment

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

One remaining substantial change (structSize is still 3 bytes off), and otherwise just comment and naming nits. Almost there…

@@ -334,6 +334,33 @@ func (h *FloatHistogram) Equals(h2 *FloatHistogram) bool {
return true
}

// Size returns the size of the whole fields histogram in bytes.
Copy link
Member

Choose a reason for hiding this comment

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

This should make clear that it includes the pointer to the histogram.

How about "Size returns the total size of the FloatHistogram, which includes the size of the pointer to FloatHistogram, all its fields, and all elements contained in slices."

// Total size of the struct

// fh is 8 bytes
// fh.CounterResetHint is 1 byte
Copy link
Member

Choose a reason for hiding this comment

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

But it takes 4 bytes because af alignment.

// fh.NegativeSpans is 24 bytes
// fh.PositiveBuckets is 24 bytes
// fh.NegativeBuckets is 24 bytes
structSize := 141
Copy link
Member

Choose a reason for hiding this comment

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

So this should be 144. (I suggested 136 in the last round of review, but I was not aware that your intention is to include the size of the pointer to FloatHistogram).


// Total size of the struct

// fh is 8 bytes
Copy link
Member

Choose a reason for hiding this comment

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

Nit: This is a sentence and should end with a period. (Same everywhere else. Generally, I would even finish longer comments that aren't formally sentences with a period, like the "Total size of the struct" above. Comments are plain English and should have punctuation as if you are writing an English text.)

promql/value.go Outdated
@@ -264,10 +296,11 @@ func (m Matrix) String() string {
}

// TotalSamples returns the total number of samples in the series within a matrix.
// It takes into account the number of samples in the histograms using the histogramSize method.
Copy link
Member

Choose a reason for hiding this comment

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

I would just repeat the same sentence as you have done above for func (vec Vector) TotalSamples(), i.e.

// Float samples have a weight of 1 in this number, while histogram samples have a higher
// weight according to their size compared with the size of a float sample.
// See HPoint.histogramSize for details.

promql/value.go Outdated
// The total size is calculated considering the histogram timestamp (p.T - 8 bytes),
// and then a number of bytes in the histogram.
// This sum is divided by 16, as samples are 16 bytes.
func (p HPoint) histogramSize() int {
Copy link
Member

Choose a reason for hiding this comment

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

Sorry for more naming nits. But why not call this just size()? It returns the size of the whole HPoint, not just of the histogram that is part of the HPoint.

Signed-off-by: Marc Tuduri <marctc@protonmail.com>
@marctc marctc force-pushed the query_max_samples_float_histogram branch from 27d9497 to 98bb3d0 Compare October 18, 2023 09:52
@marctc marctc requested a review from beorn7 October 18, 2023 09:52
Signed-off-by: Marc Tuduri <marctc@protonmail.com>
Signed-off-by: Marc Tuduri <marctc@protonmail.com>
@marctc marctc force-pushed the query_max_samples_float_histogram branch from 98bb3d0 to 1ce066e Compare October 18, 2023 09:53
Copy link
Member

@beorn7 beorn7 left a comment

Choose a reason for hiding this comment

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

Thank you very much. And sorry for this being way more involved that I initially thought.

@beorn7 beorn7 merged commit 4d50e5d into prometheus:main Oct 18, 2023
24 checks passed
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.

promql (histograms): Reevaluate weight of native histograms relevant for --query.max-samples
2 participants