Skip to content
This repository has been archived by the owner on Dec 24, 2023. It is now read-only.

Configurability for OSHI #3

Open
YoshiEnVerde opened this issue Jan 3, 2019 · 12 comments
Open

Configurability for OSHI #3

YoshiEnVerde opened this issue Jan 3, 2019 · 12 comments

Comments

@YoshiEnVerde
Copy link
Collaborator

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
Copy link
Collaborator Author

YoshiEnVerde commented Jan 3, 2019

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

  • Time before a cached value is considered stale: Maybe we want the values to stay as fresh as possible, maybe we never need to update values, so keeping values cached forever is the intended use?
  • Fail fast vs fail slow (related to Logging vs. Exceptions vs. Result Objects #2): would we want OSHI to fetch what it can, and fail only when requesting an unavailable value? Or do we want any problem to fail-fast and throw an exception?
  • Logging: No matter how we handle our internal logging (assuming we will have logging), we'll need to be able to set the logging levels, the appenders (or the corresponding mechanism), maybe have the ability to selectively choose some logging and not the rest (like only log at the API level and don't bother with OS level logging)
  • Caching: If the OSHI user is implementing some code that requires incredibly low latencies (like some network scanner, or some metrics providing library), having a cache between the fetching and API when we'll manually update all values tens of times per seconds becomes a huge resource hog. We could have a way to disable caching altogether
  • Manual Updates: In the same vein as the previous bullet, if we expect a very low latency performance from OSHI, we could just as well set automatic data updating for every fetch

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
Copy link
Member

dbwiddis commented Jan 3, 2019

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
Copy link
Collaborator Author

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
Copy link
Collaborator Author

YoshiEnVerde commented Jan 3, 2019

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
Copy link
Collaborator Author

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
Copy link
Member

dbwiddis commented Jan 3, 2019

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
Copy link
Collaborator Author

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
Copy link
Collaborator Author

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
Copy link
Member

dbwiddis commented Jan 4, 2019

  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
Copy link
Member

dbwiddis commented Jan 4, 2019

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

@cilki
Copy link
Collaborator

cilki commented Jan 4, 2019

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
Copy link
Member

dbwiddis commented Jan 4, 2019

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

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants