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

Use more consistent time representation throughout the engine #8143

Merged

Conversation

pierrechevalier83
Copy link
Contributor

To represent timestamps and durations, we used to use a mix of various
strategies:

  • f64 representing a number of seconds
  • time::Timespec
  • protobuf::well_known_type::Timestamp

A span of time was sometimes represented as

  • a start and an end timestamp, or
  • a start and a duration

This lead to many little helper functions converting from one type of
time representation to another, which caused clutter.

There were valid reasons for alternating between different
representations. For instance, we sometimes wanted a timestamp that
could be serialized and an f64 filled that role.
We sometimes wanted a timestamp that was more strongly typed, and a
time::Timespec filled that role, despite coming from a deprecated
crate.

In general, we should use std::time for time operations, but these
types don't lend themselves to serialization as they explicitely avoid
exposing their internals through serialization or otherwise to avoid
misleading programmers into serializing a timestamp, checking it on a
different machine with a different timezone and ending up with broken
assumptions.

Since we do want to serialize durations (since EPOCH, so a timestamp or
since another duration), create a simple struct called
concrete_time::Duration that can seemlessly be converted from and into
a std::time::Duration and that can also be serialized.

For convenience, also create a TimeSpan that encapsulates the idea of
starting at some point and ending at some other point. Give it
constructors that make sense given the contexts in which we populate
timespans (from protobuf and from measurements).

Also change the ffi boundary to push the float representation of time as
far as possible, to the py_zipkin interface; so the python code can more
conveniently compare timestamps for equality in tests.

Note that brfs was purposefully omitted from the homogeneisation effort
as it only used time::Timespec without any conversions and that is
what the fuse API expects.

Copy link
Member

@hrfuller hrfuller left a comment

Choose a reason for hiding this comment

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

lgtm

span.stop()

def from_secs_and_nanos_to_float(secs, nanos):
return secs + nanos/NUM_NANOSECS_IN_SEC
Copy link
Member

Choose a reason for hiding this comment

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

feel free to ignore, but some parens around the division would make me feel good.

c = self._ffi.from_handle(context_handle)
return c.to_value(u64)

@_extern_decl('Handle', ['ExternContext*', 'uint32_t'])
Copy link
Member

Choose a reason for hiding this comment

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

So, because python doesn't really differentiate between these types, there isn't really much advantage to having all of these methods. You could probably drop uint32_t and cast on the rust side to widen things to 64.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's fair enough. I saw this as a tad more explicit, but it's true that it may be seen as unnecessary. I'll follow your advice.

@@ -15,6 +15,7 @@

logger = logging.getLogger(__name__)

NUM_NANOSECS_IN_SEC = 1000000000.0
Copy link
Member

Choose a reason for hiding this comment

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

The _IN_SEC suffix here looks like we're describing the unit of this variable, which is a bit confusing. Maybe NANOSECONDS_PER_SECOND or something?

Copy link
Member

@hrfuller hrfuller Aug 6, 2019

Choose a reason for hiding this comment

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

It might be cleaner just to define a const NANOSECOND = 10^-9 then you would multiply instead of divide.

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 kind of like Stu's suggestion of NANOSECONDS_PER_SECONDS a bit better because the name is explicit. Will go with it.

@pierrechevalier83
Copy link
Contributor Author

Actioned the code review comment and fixed a python formatting error. (I edited the existing commits out of habit, although with our squash-on-merge strategy, I may as well have created new commits). The diff can be seen in the github UI by checking the "force-pushed" hyperlink.

Copy link
Contributor

@blorente blorente left a comment

Choose a reason for hiding this comment

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

LGTM, modulo the comments.

/// A timespan
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
pub struct TimeSpan {
/// Duration since the UNIX_EPOCH
Copy link
Contributor

Choose a reason for hiding this comment

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

I found this comment confusing (in part because didn't read the names of the bound variables). I interpreted it as "Duration from UNIX_EPOCH until the end of this timespan".

Suggested change
/// Duration since the UNIX_EPOCH
/// Duration from the UNIX_EPOCH to the start of this TimeSpan.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Uhm. Not sure about this. Comments apply to the line directly below them, so I thing avoiding the repetition is better. If I mention "the start of this TimeSpan" and then rename start or TimeSpan for some reason, I'll have to edit the comment to keep in sync, which the compiler won't help me with.

duration: duration.into(),
});
if time_span.is_none() {
warn!(
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this just crash? I'll look through the usages, but it doesn't look like returning None is what we want to do.

I think I'd prefer just crashing, or returning a Result<Timespan, String> or something like this.

Would also be okay to leave it as a TODO:

Suggested change
warn!(
// TODO Make this return a Result<> or panic instead of just warning.
warn!(

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 see your point, and it may be worth doing in a separate PR. The reason I'm only warning here is because this change is a simple refactoring, so I'm not aiming to change the behaviour too significantly. You can find the old behaviour in src/rust/engine/process_execution/src/remote.rs (duplicated a few times).

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 could return an Err from here and warn at the call site, though.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Addressed in an extra commit

@pierrechevalier83
Copy link
Contributor Author

LGTM, modulo the comments.

Thanks for the code review 😄 I addressed the comments

@pierrechevalier83
Copy link
Contributor Author

One of the CI failures is legit: forgot to run (and update) the tests after my last add-on commit. Fix coming.

Copy link
Contributor

@illicitonion illicitonion left a comment

Choose a reason for hiding this comment

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

Looks great, thanks!

Are we actually using the serde-ness anywhere? If not, may be worth skipping it until we need it (because compile times).

@pierrechevalier83
Copy link
Contributor Author

Are we actually using the serde-ness anywhere? If not, may be worth skipping it until we need it (because compile times).

Yes, we are. For instance, we serialize metadata to json in fs_util.

@stuhood
Copy link
Member

stuhood commented Aug 7, 2019

The failures in travis look like a corrupted 3rdparty artifact in the cache. I've cleared the caches for this PR and will restart.

pierrechevalier83 and others added 4 commits August 8, 2019 11:32
To represent timestamps and durations, we used to use a mix of various
strategies:
* `f64` representing a number of seconds
* `time::Timespec`
* `protobuf::well_known_type::Timestamp`

A span of time was sometimes represented as
* a start and an end timestamp, or
* a start and a duration

This lead to many little helper functions converting from one type of
time representation to another, which caused clutter.

There were valid reasons for alternating between different
representations. For instance, we sometimes wanted a timestamp that
could be serialized and an `f64` filled that role.
We sometimes wanted a timestamp that was more strongly typed, and a
`time::Timespec` filled that role, despite coming from a deprecated
crate.

In general, we should use `std::time` for time operations, but these
types don't lend themselves to serialization as they explicitely avoid
exposing their internals through serialization or otherwise to avoid
misleading programmers into serializing a timestamp, checking it on a
different machine with a different timezone and ending up with broken
assumptions.

Since we do want to serialize durations (since EPOCH, so a timestamp or
since another duration), create a simple struct called
`concrete_time::Duration` that can seemlessly be converted from and into
a `std::time::Duration` and that can also be serialized.

For convenience, also create a `TimeSpan` that encapsulates the idea of
starting at some point and ending at some other point. Give it
constructors that make sense given the contexts in which we populate
timespans (from protobuf and from measurements).

Also change the ffi boundary to push the float representation of time as
far as possible, to the py_zipkin interface; so the python code can more
conveniently compare timestamps for equality in tests.

Note that brfs was purposefully omitted from the homogeneisation effort
as it only used `time::Timespec` without any conversions and that is
what the `fuse` API expects.
Let the caller decide how to deal with the error case (by warning)
@stuhood
Copy link
Member

stuhood commented Aug 8, 2019

Huge number of network flakes. Cleared caches for those and restarted.

@stuhood stuhood merged commit 69f07f0 into pantsbuild:master Aug 8, 2019
@pierrechevalier83
Copy link
Contributor Author

Huge number of network flakes. Cleared caches for those and restarted.

Thanks for this 👍

@pierrechevalier83 pierrechevalier83 deleted the pchevalier/concrete_time branch August 9, 2019 09:28
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.

6 participants