Open cilki opened 5 years ago
Attribute needs not only the property, but the API "tree" to get there. For example, hardware.memory.available
.
Not sure I like the term "Component" for the POJO container. While the interfaces can be names of (physical) components, I am leaning toward (in OSHI 4) the POJO containers being called *Data.
The Interfaces will be the API and should be the simple component names, rather than the "Index".
Not sure I like the term "Component" [...] I'm sure we're talking terms only here, not how they'll be named in the implementation. Just how we'll reference them in the documentation, and in any further design conversation.
I stumbled over this issue myself while trying to give the explanation of how layers should work in #2 and #3
Just like in those issues, I have a more nuanced structure for the components in my head. I'll draw a quick diagram and share it later today. Until then, my contributions to this:
Term | Definition | Example | My ¢2 |
---|---|---|---|
Result | The content of any Attribute. Follows a pattern similar to Optional |
These should be able to contain the resulting variable from a valid Value flowing up all the way from the Driver, if available, or error details if something goes wrong in the process | |
Attribute | A specific property of some aspect of the system | guid , vendor , name |
I like this one, with @dbwiddis caveat of using full paths for their individual names |
Endpoint | An interface that defines a container for related attributes | Processor , ProcessorWithFrequencyRates , ... |
Instead of having indexes, I agree with @dbwiddis on using the API interfaces for this |
Entity | A POJO container that implements one or more Endpoints | PhysicalProcessor , LogicalProcessor , ... |
Any of these should correspond to a main Endpoint it implements, while maybe implementing extra minor Endpoints depending on their internals or references. Like I told @dbwiddis in #2, a LogicalProcessor would be one of the implementations of Processor , while also implementing ProcessorWithFrequencyRates and DependentOnProcessor (for having a parent PhysicalProcessor to reference) |
Component | Non-POJO implementation of an underlying system element. Maps between the underlying layers and the Entities/Endpoints | PhysicalProcessorIntel , NetworkInterfaceCommon , HardDriveWindows , ... |
These correspond, more or less, to the objects being output by current OSHI3 |
Cache | Any element, collection, or framework we'll use for caching values. No specific details, since we should use this transparently | Value refresh will be handled here | |
Strategy | A configurable object that knows how to effectively recover attribute values from the underlying system | MotherboardDetailsStrategyWindows , ProcessorStrategyMac , ... |
These will make sure to call the corresponding drivers, handle any errors they throw, etc |
Driver | A configurable low-level object that knows how to interact with a specific system service for data | WmicDriver , LinuxCommandDmiDecodeDriver , ... |
These know how to handle one single service well. They are charged with calling the service, and parsing the recovered values |
Value | Any value fetched and parsed by a Driver | We should strive for these to be only primitives, String, and enums |
This chart would correspond to the higher level API I was proposing. If we also want a lower level API closer to the drivers, we can have a second set of Components that may, or not, have a caching layer, and work in a straight coupling with the Strategies
The reason I intended users to interact with API classes (components) rather than interfaces (index) is because it would be beneficial for the user to do other things not defined in the interface.
Here's what I mean. A CPU interface would define String getModel()
, but it could also be desirable to have another method that returns the attribute itself: Attribute<String> model()
. This object would contain metadata like timestamps, but most importantly would be AutoClosable
so OSHI knows when the user is done polling the attribute. I'm also imagining an addListener()
defined for attributes which adds a callback that receives change events.
These methods shouldn't be added to the main interface because then the drivers would need to implement them as well.
Edit: Although now that we've agreed to generate the API, we could just make a separate interface for the drivers.
In that case, we're talking about building a whole second API.
And no, the drivers don't have to implement anything you don't need them to implement. Their job is only to fetch the values.
If you want timestamps, that would go wherever you're handling caching of the values. Same with callbacks and listeners, which should happen above the caching layer.
Those are all high level concerns that should never fall in the lower layers.
For example, change events require some kind of threading model that will keep a constant/periodical check on the values of the system to allow you to know if something's changed.
This means implementing at least a single thread that goes through a full list of every single value of interest, requesting the drivers for the latest value (irregardless of the cache state), and comparing it to the cached values to see if an event must be triggered.
Timestamps are a bit easier: the caching solution already implements timestamps for the cached values, so you just need to keep the full cache record when requesting values, instead of just the value itself
Finally, AutoClosable
is technically dependent on your underlying layers having an Open/Closed state of some type, that you will invoke within a single scope (no matter how big that scope is).
OSHI, in general, doesn't work that way. More so, this would go against the whole point of having unmodifiable POJOs at the public facing side of the library, since those are set once read.
The correct thing for such a use case would be for the user to write some AutoClosableOshiWrapper
that handles the auto-closable part of the request
Not saying the use cases aren't there, or interesting, just that we should focus on designing and implementing OSHI here, and leaving any high level functionalities to a separate module.
The problem with adding extra functionalities is that we start clashing with the try to be more performant part of the OSHI design.
What we can do, is build each layer of the library as a separate module (or set of modules). For example, a module with the Win Drivers, one for the Mac Drivers, one for Components, etc.
That way, adding a module with the functionality you want would be as easy as adding another module to the OSHI catalog, that depends on the Strategy/Driver level modules, and works as a standalone from the standard OSHI library
We discussed a polling engine to supply change listeners a while back and decided it was best for another artifact. I was just trying to setup the base design so it could easily support such extensions in the future.
One more tangent: OSHI doesn't currently have the notion of open/closed for anything, but I think it would benefit from having stateful drivers/strategies. What do you think about that?
The reason I intended users to interact with API classes (components) rather than interfaces (index) is because it would be beneficial for the user to do other things not defined in the interface.
Here's what I mean. A CPU interface would define
String getModel()
, but it could also be desirable to have another method that returns the attribute itself:Attribute<String> model()
. This object would contain metadata like timestamps, but most importantly would beAutoClosable
so OSHI knows when the user is done polling the attribute. I'm also imagining anaddListener()
defined for attributes which adds a callback that receives change events.These methods shouldn't be added to the main interface because then the drivers would need to implement them as well.
Edit: Although now that we've agreed to generate the API, we could just make a separate interface for the drivers.
One thing I've realized about your post, related to the Attributes, is that you're asking for a possible AutoClosable
on the Attributes themselves. My guess would be to keep updating them until you don't need to?
First of all, our current work (OSHI3) on the code that would go into the drivers doesn't contemplate keeping a resource open for variable lengths of time. We request the value from the system, we get it back, done. Any update means doing the full request once more. That's even the only approach available for most of the requests.
Second, what you're asking for the Attributes would be the reason why I split the response data into Response, Attribute, Entity and Component: The first three should be static. Once requested, they're fixed. That allows for the user to have stable and reliable data on hand. The Component is updateable, returning a new set of Entities on every update. With that, what you ask for in states is, technically, part of the Component; while metadata can easily be argued in favor of, or against, as part of the Result objects.
We discussed a polling engine to supply change listeners a while back and decided it was best for another artifact. I was just trying to setup the base design so it could easily support such extensions in the future.
I actually find the use case to be a very useful one. I just don't see this being a part of OSHI itself. For setting things up for the capability, I just raised a new issue, since I do find it to be a perfectly valid reason for some details in the design approach
One more tangent: OSHI doesn't currently have the notion of open/closed for anything, but I think it would benefit from having stateful drivers/strategies. What do you think about that?
This is one of the reasons why we'd have Drivers/Strategies in a separate layer from the Caching and API itself. If we need to, we can implement Drivers that are not stateless (I actually encourage that for some drivers). However, the way the driver works should not reflect on the way the API works, since the whole point of a Driver Pattern is to abstract such concerns from the API.
What we can do is add/leverage the configuration layer, to be able to set those up.
the way the driver works should not reflect on the way the API works, since the whole point of a Driver Pattern is to abstract such concerns from the API
The only problem is, how can you determine when to release a driver's resources without a cue from a higher layer? Making the lowest layer stateful introduces a session-like architecture to the higher layers.
Last response, and then let's move this to it's own issue, since this one is supposed to be about naming conventions ;)
The only problem is, how can you determine when to release a driver's resources without a cue from a higher layer? Making the lowest layer stateful introduces a session-like architecture to the higher layers.
Yup, that's an issue that we do need to address as part of the Driver's design/implementation. It should still not be part of the general API. By that, I mean that the state of the Drivers should not be something that impacts on the Entities/Attributes/Results.
If we need to, we'll add some management module that handles that. Or we'll add a way to set this up, or to open/close the resources, or whatever we might need. But it should never be delegated to an element five layers of abstraction away.
For the case you proposed back in oshi/oshi#548 , for example, with the ability to set only specific values to be updated on polling, and avoiding others, we'd just as easily have a configuration value that disables certain values from being fetched by a Driver. This is even something that can be done programmatically, just by replacing a value in the app properties map. If we have such a configuration value, we can just as well have some kind of ConfigHandler/ConfigManager that has methods to set all those without needing to blindly set values in the properties themselves.
Sorry for completely derailing this. I'll make an issue for the driver state thing and hide the tangential comments to get back on track since this is a very important issue.
I'm curious as to what @dbwiddis thinks about your terms. The only two that bother me are strategy and entity. Strategy is just a specialized driver, so that's why I've been saying driver and generic driver. Also, strategy doesn't really suit the concept in my opinion.
No problem. We're here to design all this, so any issue is welcome, I guess.
I used Entity and Strategy because they're the more common pattern names for what I was thinking. I'll welcome any name that might be better. Let's not forget that we're defining terms only here to be able to throw around designs and ideas without having (too many) communication failures.
In the case of Strategies, my idea would be that on OSHI3 we have multiple ways of recovering a value from the system, and we need to be able to use them.
For example, recovering some random system detail from Windows might trigger a request to WMIC, if that fails, we'll ask another Windows API, if that fails, we'll check the Register, and if that fails, we'll ask some file in the system folder. That kind of logic flow is pretty common in OSHI3.
For such a case, we'd actually have 4 different drivers involved there:
As such, the Strategy object would handle the flow between drivers depending on their priority, raise/throw the errors as needed, check the config for disabled drivers, etc
I'm curious as to what @dbwiddis thinks about your terms.
Don't give me any special consideration! You're the professionals!
That said...
Result -- Agreed, we need Value (or null/NaN/flag for failure), Error details, and also a Timestamp.
Entity/Endpoint -- don't understand the example ProcessorWithFrequencyRates
. This seems overly complicated.
Strategy -- ok. I prefer "Fallback Logic"
Driver -- ok. Should think about a generic "text column parsing" utility that we can use for a lot of the command line stuff that returns outputs in delimited columns, which is essentially like a "database query" equivalent.
Value -- not sure I like Enum, just return the string name for it. We expect Values to be eventually represented in JSON so generally need to follow that spec (numeric, string, arrays). Need a way to represent unsigned long (BigInteger).
WMIC Driver
Since we're being all official with definitions let me make sure this one's clear. Windows Management Instrumentation (WMI) is the infrastructure that gives us access to a lot of data on Windows. Component Object Model (COM) is the API available to us in C (or Java via JNA) to query WMI via WMI Query Language (SQL). WMI command-line (WMIC) is a command line utility with its own syntax similar to, but unique from WQL. Early versions of OSHI used the wmic.exe
command line, but that has since been replaced by WQL calls using the COM API.
We need to agree on some common definitions to make it easier to discuss design and write documentation. I propose the following initial set:
guid
,vendor
,name
Cpu
,Gpu
, ...CpuIndex
,GpuIndexWindows