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

Prometheus: Fill missing steps with null values #43622

Merged
merged 5 commits into from
Jan 5, 2022

Conversation

ivanahuckova
Copy link
Member

@ivanahuckova ivanahuckova commented Jan 3, 2022

What this PR does / why we need it:

Previously (before backend migration) on frontend we filled out missing steps with null values. This PR implements this missing functionality. I've decided to implement this on backend so the results of prometheus queries are same everywhere (Dashboard, Explore, Alerting) and we don't have to rely o frontend implementation.

Which issue(s) this PR fixes:

Fixes #42993

How to test:

  1. Run make devenv sources=prometheus
  2. Create dashboard with query counters_logins{server="backend-01"} and time range Last 5 min and step 10s
  3. Stop devenv_fake-prometheus-data_1 in docker and wait ~1 min. Then start it again
  4. Where data points are missing, you should see empty/non-connected space:
    image

On current main, the lines are connected (incorrect behaviour/bug):
image

@@ -73,8 +73,8 @@ func (s *Service) executeTimeSeriesQuery(ctx context.Context, req *backend.Query
timeRange := apiv1.Range{
Step: query.Step,
// Align query range to step. It rounds start and end down to a multiple of step.
Start: time.Unix(int64(math.Floor((float64(query.Start.Unix()+query.UtcOffsetSec)/query.Step.Seconds()))*query.Step.Seconds()-float64(query.UtcOffsetSec)), 0),
End: time.Unix(int64(math.Floor((float64(query.End.Unix()+query.UtcOffsetSec)/query.Step.Seconds()))*query.Step.Seconds()-float64(query.UtcOffsetSec)), 0),
Start: alignTimeRange(query.Start, query.Step, query.UtcOffsetSec),
Copy link
Member Author

@ivanahuckova ivanahuckova Jan 3, 2022

Choose a reason for hiding this comment

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

Just a refactoring. As we want to use aligning at multiple places, we created function for this.

Copy link
Contributor

Choose a reason for hiding this comment

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

This is an awesome improvement.

timeField := data.NewFieldFromFieldType(data.FieldTypeTime, len(v.Values))
valueField := data.NewFieldFromFieldType(data.FieldTypeNullableFloat64, len(v.Values))
baseTimestamp := alignTimeRange(query.Start, query.Step, query.UtcOffsetSec).UnixMilli()
datapointsCount := int((query.End.UnixMilli()-query.Start.UnixMilli())/query.Step.Milliseconds()) + 1
Copy link
Member Author

Choose a reason for hiding this comment

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

Number of data points that we use inNewFieldFromFieldType as we no longer can use len(v.Values).

value := float64(k.Value)
timeField := data.NewFieldFromFieldType(data.FieldTypeTime, datapointsCount)
valueField := data.NewFieldFromFieldType(data.FieldTypeNullableFloat64, datapointsCount)
idx := 0
Copy link
Member Author

Choose a reason for hiding this comment

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

Also here, we can't rely on index from for _, pair := range v.Values { and we have to keep the track ourself.

Copy link
Contributor

Choose a reason for hiding this comment

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

This all looks good.

Nit: Now that there's more going on, pulling this out to a function that returns timeField, valueField := ... might read a little easier.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I totally agree. I am going to leave this for now though as @toddtreece is working on improving performance and this part of code might (probably will) change.

value := float64(pair.Value)

for t := baseTimestamp; t < timestamp; t += query.Step.Milliseconds() {
timeField.Set(idx, time.Unix(0, t*1000000).UTC())
Copy link
Member Author

Choose a reason for hiding this comment

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

If we set no value in valueField for that index, it default to nil (which is what we want).

Copy link
Contributor

@MasslessParticle MasslessParticle left a comment

Choose a reason for hiding this comment

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

Looks good. I think we can improve the readability just a bit.

@@ -73,8 +73,8 @@ func (s *Service) executeTimeSeriesQuery(ctx context.Context, req *backend.Query
timeRange := apiv1.Range{
Step: query.Step,
// Align query range to step. It rounds start and end down to a multiple of step.
Start: time.Unix(int64(math.Floor((float64(query.Start.Unix()+query.UtcOffsetSec)/query.Step.Seconds()))*query.Step.Seconds()-float64(query.UtcOffsetSec)), 0),
End: time.Unix(int64(math.Floor((float64(query.End.Unix()+query.UtcOffsetSec)/query.Step.Seconds()))*query.Step.Seconds()-float64(query.UtcOffsetSec)), 0),
Start: alignTimeRange(query.Start, query.Step, query.UtcOffsetSec),
Copy link
Contributor

Choose a reason for hiding this comment

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

This is an awesome improvement.

value := float64(k.Value)
timeField := data.NewFieldFromFieldType(data.FieldTypeTime, datapointsCount)
valueField := data.NewFieldFromFieldType(data.FieldTypeNullableFloat64, datapointsCount)
idx := 0
Copy link
Contributor

Choose a reason for hiding this comment

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

This all looks good.

Nit: Now that there's more going on, pulling this out to a function that returns timeField, valueField := ... might read a little easier.

require.Equal(t, res[0].Fields[0].At(1), time.Unix(2, 0).UTC())
require.Equal(t, res[0].Fields[0].At(2), time.Unix(3, 0).UTC())
require.Equal(t, res[0].Fields[1].Len(), 4)
require.Equal(t, res[0].Fields[1].At(1), nilPointer)
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: These can just be require.Nil(t, ...)

@ivanahuckova ivanahuckova merged commit d7d6c10 into main Jan 5, 2022
@ivanahuckova ivanahuckova deleted the ivana/prom-add-null-for-empty branch January 5, 2022 10:40
grafanabot pushed a commit that referenced this pull request Jan 5, 2022
* Prometheus: Add empty points when data points missing

* Remove newline

* Add comments

* Improve/Fix test

* Remove unused variable

(cherry picked from commit d7d6c10)
ivanahuckova added a commit that referenced this pull request Jan 5, 2022
* Prometheus: Add empty points when data points missing

* Remove newline

* Add comments

* Improve/Fix test

* Remove unused variable

(cherry picked from commit d7d6c10)

Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com>
@PayBas
Copy link

PayBas commented Jan 24, 2022

@ivanahuckova is this the new intended behavior with stacking?

image

image

image

Prior to 8.3.4 the stacked graph was gapless.

@ivanahuckova
Copy link
Member Author

@PayBas yes this is a correct behaviour. Before the 8.3 there were gaps but in 8.3. we have introduced bug that removed gaps. So 8.3.4 brings it back. 🙂

@PayBas
Copy link

PayBas commented Jan 26, 2022

@ivanahuckova thank you for your reply. I am a little confused however. The bottom 2 images (which are the same graph/panel) shows that "Connect null values: Always" will correctly show uninterrupted lines, whereas if we also turn "Stack series" to either "Normal" or "100%", it will re-introduce gaps.

I'm concerned this PR has had an unexpected side-effect on the stacking rendering. Shouldn't stacked graphs render uninterrupted when using "Connect null values: Always"?

What I'm saying is: my panel settings were showing correctly all the way from 7.0.0 right until 8.3.3. And now since 8.3.4 I cannot get it to render gapless anymore.

@ivanahuckova
Copy link
Member Author

@PayBas yes, might be a regression. Could you please create separate issue for this. Thank you!

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

Successfully merging this pull request may close these issues.

Null values always connected in time series panel in 8.3.1
5 participants