On Thu, Sep 5, 2024 at 7:56 AM Juraj Linkeš <juraj.lin...@pantheon.tech> wrote: > > > > On 26. 8. 2024 19:11, Jeremy Spewock wrote: > > On Wed, Aug 21, 2024 at 10:53 AM Juraj Linkeš > > <juraj.lin...@pantheon.tech> wrote: > > <snip> > >> @dataclass > >> class TestPmdPort(TextParser): > >> """Dataclass representing the result of testpmd's ``show port info`` > >> command.""" > >> @@ -962,3 +1043,96 @@ def _close(self) -> None: > >> self.stop() > >> self.send_command("quit", "Bye...") > >> return super()._close() > >> + > >> + """ > >> + ====== Capability retrieval methods ====== > >> + """ > >> + > >> + def get_capabilities_rxq_info( > >> + self, > >> + supported_capabilities: MutableSet["NicCapability"], > >> + unsupported_capabilities: MutableSet["NicCapability"], > >> + ) -> None: > >> + """Get all rxq capabilities and divide them into supported and > >> unsupported. > >> + > >> + Args: > >> + supported_capabilities: Supported capabilities will be added > >> to this set. > >> + unsupported_capabilities: Unsupported capabilities will be > >> added to this set. > >> + """ > >> + self._logger.debug("Getting rxq capabilities.") > >> + command = f"show rxq info {self.ports[0].id} 0" > >> + rxq_info = TestPmdRxqInfo.parse(self.send_command(command)) > >> + if rxq_info.rx_scattered_packets: > >> + supported_capabilities.add(NicCapability.SCATTERED_RX_ENABLED) > >> + else: > >> + > >> unsupported_capabilities.add(NicCapability.SCATTERED_RX_ENABLED) > >> + > >> + """ > >> + ====== Decorator methods ====== > >> + """ > >> + > >> + @staticmethod > >> + def config_mtu_9000(testpmd_method: TestPmdShellSimpleMethod) -> > >> TestPmdShellDecoratedMethod: > > > > It might be more valuable for me to make a method for configuring the > > MTU of all ports so that you don't have to do the loops yourself, I > > can add this to the MTU patch once I update that and rebase it on > > main. > > > > Sure, if you add that, I'll use it here. :-) > What won't work with that is the per-port restoration of MTU. But if we > assume that MTU is always the same for all ports, then I don't think > that's going to be a problem. This assumption doesn't seem unreasonable, > I don't see a scenario where it would differ.
Good point, and something I didn't think about. I doubt they would be different either though and I think it would generally be fine to assume they are the same, but that could also be reason to do it on a per-port basis. Whatever you think is best. Setting the MTU on all ports isn't as efficient as I thought it would be when I first wrote this comment anyway since testpmd doesn't offer something like a `port config mtu all`, so I just do it one port at a time anyway. > > >> + """Configure MTU to 9000 on all ports, run `testpmd_method`, then > >> revert. > >> + > >> + Args: > >> + testpmd_method: The method to decorate. > >> + > >> + Returns: > >> + The method decorated with setting and reverting MTU. > >> + """ > >> + <snip> > >> + @classmethod > >> + def get_unique( > >> + cls, nic_capability: NicCapability, decorator_fn: > >> TestPmdShellDecorator | None > >> + ) -> "DecoratedNicCapability": > > > > This idea of get_unique really confused me at first. After reading > > different parts of the code to learn how it is being used, I think I > > understand now what it's for. My current understanding is basically > > that you're using an uninstantiated class as essentially a factory > > that stores a dictionary that you are using to hold singletons. > > Just a note, these are not singletons, just similar to them. A singleton > is just one instance of class can exist. This class allows more > instances, but it does limit the instances. It closer to an Enum, which > work exactly the same way, but only attribute names are taken into > consideration (with Enums). That's a good distinction to make. Singleton was the closest thing that I could make the connection too, but you're right that it isn't the same and the comparison to Enums makes a lot of sense. > > > It > > might be confusing to me in general because I haven't really seen this > > idea of dynamically modifying attributes of a class itself rather than > > an instance of the class used this way. Understanding it now, it makes > > sense what you are trying to do and how this is essentially a nice > > cache/factory for singleton values for each capability, but It might > > be helpful to document a little more somehow that _unique_capabilities > > is really just a container for the singleton capabilities, and that > > the top-level class is modified to keep a consistent state throughout > > the framework. > > > > Again, it could just be me having not really seen this idea used > > before, but it was strange to wrap my head around at first since I'm > > more used to class methods being used to read the state of attributes. > > > > I'm thinking of adding this to get_unique's docstring: > > This is a factory method that implements a quasi-enum pattern. > The instances of this class are stored in a class variable, > _unique_capabilities. > > If an instance with `nic_capability` and `decorator_fn` as inputs > doesn't exist, it is created and added to _unique_capabilities. > If it exists, it is returned so that a new identical instance is not > created. Sure, I think this reads pretty well, and I like specifically calling out the pattern so that if anyone was unfamiliar it gives them something to research. > > > >> + """Get the capability uniquely identified by `nic_capability` and > >> `decorator_fn`. > >> + > >> + Args: > >> + nic_capability: The NIC capability. > >> + decorator_fn: The function that will be passed the function > >> associated > >> + with `nic_capability` when discovering the support status > >> of the capability. > >> + > >> + Returns: > >> + The capability uniquely identified by `nic_capability` and > >> `decorator_fn`. > >> + """ <snip> > >> + @classmethod > >> + def _reduce_capabilities( > >> + cls, > >> + capabilities: set["DecoratedNicCapability"], > >> + supported_capabilities: MutableSet, > >> + unsupported_capabilities: MutableSet, > >> + ) -> TestPmdShellSimpleMethod: > >> + def reduced_fn(testpmd_shell: TestPmdShell) -> None: > >> + for capability in capabilities: > > This is where I'll add the fix: > if capability not in supported_capabilities | unsupported_capabilities: > Perfect, I think that would solve it, yes. > >> + capability.nic_capability( > >> + testpmd_shell, supported_capabilities, > >> unsupported_capabilities > >> + ) > >> + > >> + return reduced_fn > > > > Would it make sense to put these two methods above > > get_supported_capabilities since that is where they are used? I might > > be in favor of it just because it would save you from having to look > > further down in the diff to find what the method does and then go back > > up, but I also understand that it looks like you might have been > > sorting methods by private vs. public so if you think it makes more > > sense to leave them here that is also viable. > > > > I sorted it this what so that the code it's easier to read (in my > opinion). I read the method, what it does, then the method calls a > method I haven't seen so I go look beneath the method for the method > definition. To me, this is preferable that reading methods I haven't > seen before. Or, put in another way, the methods are sorted in the order > they're used in code (that's how the code is executed and that's why > this order feels natural to me). Right, that does also make sense and is more accurate to how the code runs. I think it is fine to leave this way then. > > >> + > >> + def __hash__(self) -> int: > >> + """Instances are identified by :attr:`nic_capability` and > >> :attr:`capability_decorator`.""" > >> + return hash((self.nic_capability, self.capability_decorator)) > > > > I guess my question above is asking if `hash(self.nic_capability) == > > hash(self.nic_capability.value())` because, if they aren't, then I > > think the map will contain multiple capabilities that use the same > > testpmd function since the capabilities themselves are unique, and > > then because the get_supported_capabilities() method above just calls > > whatever is in this map, it would call it twice. I think the whole > > point of the NoAliasEnum is making sure that they don't hash to the > > same thing. I could be missing something, but, if I am, maybe some > > kind of comment showing where this is handled would be helpful. > > > > I think the simple fix in _reduce_capabilities() addresses this, right? Yes it does, and it does so better than if the two hashes were equal anyway. > > >> + <snip> >