transceivers: Implement double-polling for transceiver temperatures - #2668
transceivers: Implement double-polling for transceiver temperatures#2668jamesmunns wants to merge 11 commits into
Conversation
The previous impl was insufficiently broad, and did not allow for types like `core::cell::Cell` to be placed in the ClaimOnceCell. I believe the impl was copied from StaticCell, which needs to have a more restrictive implementation as the user is not given a full `&mut T`.
jamesmunns
left a comment
There was a problem hiding this comment.
Added some commentary.
| DisableFailed(usize, LogicalPortMask), | ||
| ClearDisabledPorts(LogicalPortMask), | ||
| SeqError(SeqError), | ||
| TemperatureGlitch(usize, MilliCelsiusFixed), |
There was a problem hiding this comment.
I was going to ask what the usize means here (I am guessing it's the port and not a timestamp?) and propose we made it a named field so its meaning would be obvious when looking at the ringbuf. But, I see that that there are a bunch of similarly shaped ringbuf entries here already. I'd kinda like to see them all become named fields, but 🤷♀️
There was a problem hiding this comment.
Incidentally, it occurs to me --- and this is probably best saved for another PR --- it would be nice if we used an enum of port numbers rather than usize here, so that the ringbuf events could add #[count(children)] for the port. The counters crate doesn't support #[count(children)] on integer fields since well, we would have to generate a table of usize::MAX counters (or dynamically allocate them which kind of defeats the purpose!)
| DisableFailed(usize, LogicalPortMask), | ||
| ClearDisabledPorts(LogicalPortMask), | ||
| SeqError(SeqError), | ||
| TemperatureGlitch(usize, MilliCelsiusFixed), |
There was a problem hiding this comment.
Incidentally, it occurs to me --- and this is probably best saved for another PR --- it would be nice if we used an enum of port numbers rather than usize here, so that the ringbuf events could add #[count(children)] for the port. The counters crate doesn't support #[count(children)] on integer fields since well, we would have to generate a table of usize::MAX counters (or dynamically allocate them which kind of defeats the purpose!)
| } | ||
| } | ||
|
|
||
| /// Implement the ringbuf trait on LogicalPort to allow for per-port metrics |
| impl Count for LogicalPort { | ||
| type Counters = [AtomicU32; NUM_PORTS as usize]; | ||
|
|
||
| #[allow(clippy::declare_interior_mutable_const)] | ||
| const NEW_COUNTERS: Self::Counters = | ||
| [const { AtomicU32::new(0) }; NUM_PORTS as usize]; | ||
|
|
||
| fn count(&self, counters: &Self::Counters) { | ||
| // This should never happen, but just in case. | ||
| let Some(ctr) = counters.get(self.0 as usize) else { | ||
| return; | ||
| }; | ||
| ctr.fetch_add(1, Ordering::Relaxed); | ||
| } | ||
| } |
There was a problem hiding this comment.
this is cute; I wonder if we might want the counters crate to have a macro or something for saying "yes, this looks like a u32, but it will always be in some range, so you can derive counters for it without having to worry"; elsewhere, I've tended to just use repr(N) enums for this sort of thing, like this thing:
hubris/drv/psc-seq-server/src/main.rs
Lines 390 to 403 in 0d1ba04
but that then requires some weirdish boilerplate for converting between the enum and integers if you also want to index arrays or whatever.
very much not a blocker for this PR, but I wonder if we might throw together a little newtype-integer-counter derive or something that works like this.
| /// Keep counters for how often each port has experienced temperature glitches. | ||
| /// | ||
| /// This *does not* reset when ports are disabled or qsfp xcvrs are removed or | ||
| /// re-added. We just pass-through to the existing LogicalPort impl of Count. | ||
| impl Count for TempGlitch { | ||
| type Counters = <LogicalPort as Count>::Counters; | ||
|
|
||
| #[allow(clippy::declare_interior_mutable_const)] | ||
| const NEW_COUNTERS: Self::Counters = <LogicalPort as Count>::NEW_COUNTERS; | ||
|
|
||
| #[inline] | ||
| fn count(&self, counters: &Self::Counters) { | ||
| self.port.count(counters); | ||
| } | ||
| } |
There was a problem hiding this comment.
Oh, huh, I think this is necessary because the counters crate doesn't support deriving Count for a struct, even if it has count(Children) on a field that implements Count? It could be worth adding that to better support cases like this (not in this PR, I'll go make a ticket).
| /// and since this is just for debugging, we hope that a reasonable person | ||
| /// would realize that the delta between the two entries is zero. I really | ||
| /// didn't feel like making this an Option, or using u8::MAX as the index, | ||
| /// or even adding 1 to the port and then using NonZero or something. |
There was a problem hiding this comment.
Yeah, I agree that this is pretty reasonable; the offset one where we use NonZero, in particular, actually sounds like kind of a bad idea, because the primary consumer of ringbufs, humility, does not have any idea that we have done such an offsetting and will end up just lying to the reader about which port it is, unless they went and read the code to learn about this.
With that said, I'm not actually sure if making it an Option and initializing the ringbuf to None is actually all that bad, since we don't actually really work with the entries all that meaningfully? It might make things a bit less confusing for a reader...
| struct TempGlitch { | ||
| port: LogicalPort, | ||
| first: Celsius, | ||
| second: Celsius, | ||
| } |
There was a problem hiding this comment.
i wonder if we might also want to include a timestamp in these, so that a reader can get some sense of how frequently they occur/whether they are clustered temporally or not?
| fn get_temperature_once( | ||
| &self, | ||
| port: LogicalPort, | ||
| m: &ThermalModel, | ||
| ) -> Result<Celsius, TempReadError> { | ||
| let res = match m.interface { | ||
| ManagementInterface::Cmis => self.read_cmis_temperature(port), | ||
| ManagementInterface::Sff8636 => self.read_sff8636_temperature(port), | ||
| ManagementInterface::Unknown(..) => { | ||
| // We should never get here, because we only assign | ||
| // `self.thermal_models[i]` if the management interface is | ||
| // known. | ||
| return Err(TempReadError::UnknownInterface); | ||
| } | ||
| }?; | ||
|
|
||
| Ok(res) | ||
| } |
There was a problem hiding this comment.
i think the layering of get_temperature_resample -> get_temperature_once is conceptually reasonable, but it is a bit silly that we end up matching on m.interface twice every time we read temps from a thing. the performance hit from that is probably not actually important but it feels silly. probably not worth messing with.
| // avoid issues with the borrow checker. | ||
| let mut to_disable = LogicalPortMask(0); | ||
| for (i, m) in self.thermal_models.iter().enumerate() { | ||
| for (i, meta) in ports.iter_mut().enumerate() { |
There was a problem hiding this comment.
bit odd that we renamed this to PortData but the variable is still meta...
CC #2664 (though probably not a permanent solution).
This PR:
The hope for this PR is to get more trackable information and maybe catch a misbehaving I2C poll in action.
We may want to also add an ereport for this, I'd like to leave that for a follow-on PR though.
This is also currently based on #2667, we should merge that first.
This did require some refactoring of the transceiver server code, I tried to keep the diffs pretty stable and the commits pretty useful so far, if that makes things easier to review. I'll flag a couple of "wuts" I saw along the way.