Skip to content
This repository was archived by the owner on Sep 17, 2025. It is now read-only.

Drop negative measurements on record#389

Merged
c24t merged 13 commits intocensus-instrumentation:masterfrom
c24t:drop-non-positive-distb-buckets
Nov 15, 2018
Merged

Drop negative measurements on record#389
c24t merged 13 commits intocensus-instrumentation:masterfrom
c24t:drop-non-positive-distb-buckets

Conversation

@c24t
Copy link
Member

@c24t c24t commented Nov 8, 2018

This diff prevents sets of measurements from being recorded to view data objects if any measurement in the set is negative, prevents MeasurementMaps from being recorded if they've ever contained negative value measurements, and removes 0- and negative-valued bucket boundaries from DistributionAggregations.

See the java implementation for the justification for these changes.

Fixes #376

@c24t c24t self-assigned this Nov 8, 2018
@c24t c24t requested review from mayurkale22 and songy23 November 8, 2018 00:26
for ii, bound in enumerate(agg_data.bounds):
cum_count += agg_data.counts_per_bucket[ii]
points[str(bound)] = cum_count
labels = desc['labels'] if points is None else None
Copy link
Member Author

Choose a reason for hiding this comment

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

points is never null here, but I didn't touch it because I don't know what prometheus expects. @mayurkale22 let me know if labels should be null when there are no points in the distribution and I'll change it.

@mayurkale22
Copy link
Member

On a side note, I think we should add CHANGELOG.md now, to document all the notable changes about the versions. Same as: https://github.com/census-instrumentation/opencensus-java/blob/master/CHANGELOG.md. This PR could be a breaking change if users are recording negative value for any measure. What do you think?

c24t added a commit to c24t/opencensus-python that referenced this pull request Nov 8, 2018
c24t added a commit that referenced this pull request Nov 8, 2018
@c24t c24t mentioned this pull request Nov 8, 2018
@c24t
Copy link
Member Author

c24t commented Nov 8, 2018

On a side note, I think we should add CHANGELOG.md now...

I think adding a changelog is a great idea, and we should push a version update once these changes are merged. We have some work to do to check that the code added since the last release is ready to ship though.

read from current execution context.
"""
if tag_map_tags is None:
tag_map_tags = execution_context.get_current_tag_map()
Copy link
Member Author

Choose a reason for hiding this comment

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

Unrelated to this PR, I'm assuming we want to get this at call time and not module load time.

"non-negative")
logger.info("Measure '{}' has negative value ({}), refusing "
"to record measurements from {}"
.format(measure.name, value, self))
Copy link
Member Author

Choose a reason for hiding this comment

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

Not that we're doing it other places in the library, but I think it's a good idea to include more context at a more verbose logging level so users can turn up specific loggers to see what's going wrong in detail. Reviewers: let me know what you think.

"""associates the measure of type Int with the given value"""
if value < 0:
# Should be an error in a later release.
logger.warning("Cannot record negative values")
Copy link
Member Author

Choose a reason for hiding this comment

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

Note that we're breaking with the java impl here in that we don't mark the map invalid until record time.

return
for measure, value in self.measurement_map.items():
if value < 0:
self._invalid = True
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 set this in put method, no need to iterate on map here.

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't have a strong preference, but this is intentional since I don't think it makes sense to raise until the user tries to record.

Copy link
Member

Choose a reason for hiding this comment

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

I don't think it makes sense to raise until the user tries to record.

Agreed. But my point was if you set self._invalid = True in the put method, during record time you just have to check if (self._invalid == True) instead of iterating on map.

Copy link
Member Author

Choose a reason for hiding this comment

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

Got it, but this would mean marking the map invalid if the user puts the same measure twice before recording, first with a negative value and second positive. Obviously they shouldn't do this, but there's no reason in principle to invalidate the map if they do. It also lets us log the particular negative value when record is called (vs. doing it earlier in put).

elif isinstance(agg_data,
aggregation_data_module.DistributionAggregationData):

assert(agg_data.bounds == sorted(agg_data.bounds))
Copy link
Member

Choose a reason for hiding this comment

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

We should do same in the stats/stackdriver exporter?

Copy link
Member Author

Choose a reason for hiding this comment

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

Here? I don't think we need this assertion everywhere we use distribution aggregations, I've only got it in the prometheus exporter since guaranteeing the bounds were sorted let me simplify the code a bit.

@c24t
Copy link
Member Author

c24t commented Nov 12, 2018

Something to consider for later when we're making other breaking changes: we might want to remove the MeasurementMap class altogether and replace it with a record(tags, measurement, timestamp, attachments) API method where measurement is a d Measure-keyed dict.

if not all(boundaries[ii] < boundaries[ii + 1]
for ii in range(len(boundaries) - 1)):
raise ValueError("bounds must be sorted in increasing order")
for ii, bb in enumerate(boundaries):

Choose a reason for hiding this comment

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

We could do the above check during this loop too, and then we only have to iterate through the list of boundaries once?

Copy link
Member Author

Choose a reason for hiding this comment

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

This loop should normally break on the first item, I think the slight readability improvement is worth it here.

"length")
else:
assert all(cc >= 0 for cc in counts_per_bucket)
assert len(counts_per_bucket) == len(bounds) + 1

Choose a reason for hiding this comment

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

Asserts aren't guaranteed to run in production, since some people might run Python without assertions, but we always raise ValueErrors or AssertionErrors for these, I think.

Choose a reason for hiding this comment

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

Sorry, I should have elaborated. Caveat #1 from this blog post mentions it. The assert docs also mention it, but not necessarily emphasising how they shouldn't be used.

Copy link
Member Author

Choose a reason for hiding this comment

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

I tried to use asserts in a way consistent with the blog. An assertion failure here should mean a programming error since we only ever expect to call DistributionAggregationData.__init__ from DistributionAggregation.__init__, where we actually do raise a ValueError if the user gives us unsorted bounds.

I.e. precondition checks for API methods should raise Type/ValueErrors (and etc.), but precondition checks in internal-only code can assert instead since bad user input should never cause them to fail. The logic doesn't change if the assertions are removed, and they should only fail if there's a bug in the library.

That said, there's no bright line separating the API from the internals here, and we don't lose anything by changing these to ValueErrors (other than signaling the difference between expected and unexpected errors). If you've got a strong preference I don't mind changing it.

Copy link
Member

@mayurkale22 mayurkale22 left a comment

Choose a reason for hiding this comment

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

LGTM. Next PR should be -> Exporter/Stats/Stackdriver: Add 0 count for underflow/first bucket

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants