oshi / oshi6

Repository for planning/coordination of OSHI 6.0
MIT License
0 stars 2 forks source link

Configurability for OSHI #3

Open YoshiEnVerde opened 5 years ago

YoshiEnVerde commented 5 years ago

For this version, now that we'll start decoupling the fetching, the caching, and the API - plus the implementation of a more standard vs specialized set of APIs -, we should start looking into setting a mechanism (or two) for configuring OSHI.

YoshiEnVerde commented 5 years ago

As examples of details that we might want to be able to configure:

There's even possible hybrids of the above stated, like wanting API level issues to fail-fast, but OS level issues to just return some default unavailable value (kind of what we were doing before)

dbwiddis commented 5 years ago

Time before a value is stale -- this needs to be a per-value configuration. See, for example, how the openHAB SystemInfo Binding separates high (1s), medium (1m), and low (init only). When I was briefly playing around with Metrics (before realizing it couldn't handle arrays nicely) I actually rewrote the config file from the json artifact to specify millisecond-level refresh for each item. A Properties object could be passed to the update() method(s) or perhaps a dedicated class could store them somewhere in a map.

Fail fast -- Not sure what you mean here. I've implemented WMI timeouts. There are a few other places where we sometimes need some "retries" (I use 3 to 5 attempts with exponential fallbacks from 1ms up to 16ms) to get the values, but other than that we just wait for the results. A better solution would be multithreaded requests that all wait and update when they're done.

Logging -- I'd like trace and debug logging but we should replace warnings and errors with exceptions that the user can individually handle.

Caching -- how is this different from the first bullet regarding stale values?

YoshiEnVerde commented 5 years ago

Fail-fast means that as soon as something doesn't work, we throw an exception and execution is halted. For example, if we try to parse a serial number from the O.S., and it fails to do so, we throw some kind of MissingValueOshiException.

The counterpart to that would be the result objects, where a failure would be passed back as part of the result, and not as an exception.

Both situations are useful for different use cases.

YoshiEnVerde commented 5 years ago

Caching costs a lot of resources. The point for that is that you cache values when the overhead cost is lower than the costs of repeating the same operations over and over. That's great if we'll ask for values that don't change all the time, or we care more for performance than accuracy.

However, if you're going to refresh all the values every 200ms (heck, even every 5s), caching becomes not only worthless, it actually increments resource cost unnecessarily.

The same goes for manual updates.

As a general example: A simple pseudocode for fetching values with caching involved goes:

API.GetValue()
{
    return Cache.GetValue("FieldName");
}

Cache.GetValue(string name)
{
    SynchroLock.Lock();
    var entry = CacheMap.Get(name);
    if(entry not exists || entry is stale)
    {
        var field = Driver.FetchField(name);  // <-- (*)
        entry = CacheMap.Put(name, field); // <-- (*)
    }
    SynchroLock.Unlock();
    return entry.Value();
}

This would always be followed, even if we were to execute an update every single time before we ask for a value. The only thing that would be avoided is (*), which would still be run every time we update, so we actually avoid nothing at all.

Now, if the user could, for example, disable caching, everything within the lock (except for item 6) would be out of the picture, replaced by a check for caching status:

API.GetValue()
{
    if(is cache enabled)
    {
        return Cache.GetValue("FieldName");
    }
    else
    {
        return Driver.FetchField(name);
    }
}

Cache.GetValue(string name) {...} //Same as before

And here is where some of that inheritance we're talking about in #2 starts playing a factor too, because depending on how we're working the different modules/layers, we could even just check things like caching status at init time, and then use different API providers for cached and uncached.

YoshiEnVerde commented 5 years ago

Touching on your point about staleness, but expanded to them all: It's not about us setting details, it's about the users being able to set them.

For example, the OpenHAB model is not a bad idea, but the point would be to let the user decide if they want all fields to be high/medium/init, or which are of what type. Also, allowing the users to decide how long a high refresh ratio is: maybe they want 15s for mediums?, or maybe less than a second for highs? Maybe they want to refresh once every 24hs?

dbwiddis commented 5 years ago

Right, I was pointing to OpenHAB as a user, not how we should model it. I mentioned that I had each value have its own refresh value (in ms) specified. Basically a request for information would be:

getFoo() {
    long now = System.currentTimeMillis();
    if (now - fooTimestamp() > fooRefreshConfigValue()) {
        updateFooValue(); // updates value and timestamp
    }
    return fooValue;
}
YoshiEnVerde commented 5 years ago

Ahh, good. Yes, that's exactly what I meant too then ;)

I'd also like to have the ability to configure default values for anything that will be that granular. For example, define a general 5m staleness for all values, then specify that some 5 values I'll need to check with very high frequency should become stale at 5s instead.

This way, the user can define a single configuration value, plus a handful for special cases, instead of having to trawl through a reference manual to know the name of every single configuration variable.

We'd also need to define default values for every single conceivable configuration value.

For an internal delivery mechanism, the easiest way is to just use the Properties already given to us by Java. This would allow us both to load the configuration for an external file (be it a prop file, or a more lateral method like XML config files) or programmatically (straight out loading the value, parsing incoming jar arguments, reading a DB, etc)

YoshiEnVerde commented 5 years ago

We will, however, need to define some procedure for naming the configuration values. Once you pass the dozen names (and we will most probably reach an order of magnitude, or two, more than that) things get messy pretty quick

dbwiddis commented 5 years ago
  hardware.memory.refresh                               = 1000 # default for this branch
    hardware.memory.available.refresh                   = 1000
    hardware.memory.total.refresh                       = -1 # never changes
    hardware.memory.swapTotal.refresh                   = 300000
    hardware.memory.swapUsed.refresh                    = 5000

It may be that "seconds" resolution is better than milliseconds.

dbwiddis commented 5 years ago

Also, concur with Properties that's how the json file currently uses the above config file that I'm suggesting we adapt.

cilki commented 5 years ago

XML config files

That gave me flashbacks of using Hibernate. XML is probably overkill for this use case.

I think Properties is a really good choice because they can merged which makes overriding defaults extremely easy. If you want system properties to take precedence over an external configuration file, then you can just merge each one over the default Properties.

Although I don't think many users will go for the external config file.

dbwiddis commented 5 years ago

Check out how I used Properties in the JSON output. Imagine doing the same thing for the update() methods.