Conversation
Average since is calculated over persisted values AND the actual value. The description describes that it is calculated over persisted values OR the actual value (if no persisted values are found). The description is desribes the correct behaviour, while the implementation was wrong. This is fixed with this PR. Signed-off-by: Jan N. Klug <jan.n.klug@rub.de>
I updated all the JavaDoc headers in #1854 but it has since been refactored. As mentioned in that PR, there are/were inconsistencies in what is returned across the various "since" methods that needed attention and documentation. Could this PR be expanded to complete that? |
I agree.
I think that all these functions should fail with null if no persisted values are found. Only deltaSince and evolutionSince should use the current value as this is necessary for the calculation. All aggregation functions will fail on Counter RRD because RRD4J always returns differences and never full values. If you include e.g. the packetcounter of an interface this will break al the calculations. |
Thanks @J-N-K! In addition to what makes sense if we were to rethink it now, we should also consider whether breaking OH1 compatibility is worthwhile (and if so, document the change(s) in OH2). Before the 1.0 release of ESH is a good time to go forward with the best plan. |
There are three options:
|
I would prefer to return null if a calculation could not be done because of e.g. missing values instead of return zero (and the caller does not know that there has been no input value). |
This is an ESH bundle, the OH1 distribution will not be affected by any changes we make. It'll only break rules that are ported from an OH1 installation to ESH/OH2 without changes (in the case that no persisted values exist). Most probably this will lead to other problems anyway. |
@watou, @kaikreuzer (as original author): what is your opinion? |
I don't mind breaking OH1 compatibility if we can improve things here. But I have a few questions regarding the proposals:
Looking at the unit test that you changed in this PR, this behavior makes a lot of sense. If I KNOW the current value, why should it not be considered in the calculation? I do not expect 2007.5, when my current value is 3025.
You mean none at all (so we do not have any value for startDate) or no persisted values within the range? I do not see any issue with the current implementation with most the implementations. Maybe it would make sense to come up with concrete unit tests that show a problem where everyone agrees that this is an unexpected result and we then go and fix the code so that those tests are green? As it is a complex matter and I feel that I already have "knots in my brain" (German proverb), I'd think this might be the best approach to move forward, because everyone can see his use cases covered without the risk that they are broken again by future changes. |
Let's stick to averageSince for simplicity. Assume we have an item that get's persisted every five minutes. 10:00:00 value= 0.0 At 10:10:10 you ask for the averageSince(10:00:00). Your actual value is still 40. current implementation: (0+20+40+40)/4 = 100/4 = 25 Make a chart of the values and look at that. What do you think is the "better" average? IMO it's the second one. Of course both methods will fail if the persistence interval is not fixed.From the mathematical point of view (and probably also from the expectation of the user) it would probably be best if you would calculate the integral (or an approximation to that) and divide by the timespan (formula). Then including the actual value would not hurt. |
I agree, I think this is the only correct solution to the problem - especially as it will also work if persistence is done on value change and not with a fixed frequency. It shouldn't be too hard to calculate as we only need to additionally consider the time between two values as well. |
While I agree on the fact that it would not be so hard to implement and will perform well for non-aggregating persistence services as MySQL, it may (I have to think about that) impose problems for RRDs as they already average over the defined timespan (bin length, whatever you call it). Including the current value surely will fail for RRDs of COUNTER type, because there is no possible way to get the persisted (absolute/raw) values (the RRD4J API has no method for that). |
…ince(Item, since, includeCurrentValue, [serviceId]). By ommitting includeCurrentValue (or setting to true) the calculation is done including the current value (as was before) if set to false only already persisted values are used. The average function has been changed to the correct integration over the timespan, assuming a linear evolution between the data points. The test only is used without the current value because the value to test against depends on the time of the build. Signed-off-by: Jan N. Klug <jan.n.klug@rub.de>
@kaikreuzer: Maybe adding the "includeCurrentValue" option is a good compromise. I have changed the averaging function as discussed above. |
I do not get the point of this. If we are now calculating the integral, why would you ever want to exclude the current state? |
Because RRDs of counter type store differences, not actual values. Value 1: 3458 Assume that you want averageSince from Value 2 up to now. RRD4J will return 12 2 5 Average is something like 6.2 (assuming equal time differences) If you include 3580 you'll get something like 899 which is obviously wrong. There is no way to get the original values from the RRD4J API. |
Average imho simply does not make sense for counter type and more specifically, the ESH persistence API does not have such a concept of "counter data". |
Agreed. That is why RRD4J returns differences. Since data is stored over intervals you get something like consumption. I store the values of my power meter. Of course the absolute value which is something like 75k kWh makes no sense. It nice to see it displayed in the UI, nothing else. Stored in a counter RRD I get power consumption within 5 minutes (or daily consumption in another RRD with a timespan of 1d per bin). Averaging these values makes a lot of sense, adding the current value does not. The newly added switch is only 5-10 loc, that should not be a severe problem. And there is a use case for that. |
The problem I have with it is that this "feature" is based on the assumption of a "misbehaving" persistence service, i.e. one that does not return what you have put in. As I said, this isn't a concept supported nor expected in ESH. Looking at what you really want is an |
Honestly I think that either the PR introducing counter RRDs should be reverted or RRD4J patched that it can return the current value or (better) replaced with a RRD implementation that is compatible with standard RRD tools. |
Do you have a reference for that? I wasn't even aware that such a feature had been added. |
However, COUNTER RRDs are still the easiest way to graph usage of electrical power or data transfer. If this is removed a rule that calculates differences has to be created and these values need to be stored. |
Yeah, so I agree that it would be annoying to users to remove that feature again. |
I agree that this feature would be useful. The question is how this may solve the counter RRD problem. Imagine the following (date left out for simplicity): persisted values; 10:00 15 current time: 10:13 19 averageSince(10:00) (5*(15+20)/2 + 5*(20+17)/2 + 3*(17+19)/2)/13 How can I determine the value at 10:12 without using the current value? On the other hand, assuming a step from 15 to 20 at 10:05 and using the value from 10:00 for every time up to 10:04:59 as we already do for averageSince(10:01) will evaluate to: (4*(15+20)/2 + 5*(20+17)/2 + 3*(17+19)/2)/12 is not EXACT either. So maybe using the last persisted value: averageBetween(10:00, 10:12) (5*(15+20)/2 + 5*(20+17)/2 + 2*(17+17)/2)/12 is at least consistent. |
ok, you convinced me that doing averages simply does not make sense as we do not have a continuum on those databases. The persistence service is simply not able to return a sensible value for a timestamp between two persisted values. So my suggestion would be to forget about counters in this PR completely - changing the implementation to work with the integral for normal databases makes nonetheless a lot of sense! |
Well, the original intent of this PR was to fix the counter issue as this is what I need. |
Well, but the issue title is "Fix averageSince calculation" and fixing the implementation to correctly work with integrals perfectly fits here and solves a real bug. As you have implemented, it also makes sense to merge it, doesn't it? |
Signed-off-by: Jan N. Klug <jan.n.klug@rub.de>
There is a problem with the persistence tests if this if the current value is always included. The value of averageSince then depends on the time the test is run. Is it possible to set a time that shall be used for the tests? |
Signed-off-by: Jan N. Klug <jan.n.klug@rub.de>
Hm, I am afraid not. Maybe as a workaround you can check if the result is within some expected range instead of a concrete value? |
Signed-off-by: Jan N. Klug <jan.n.klug@rub.de>
I finally found a solution that should work. If this is ok for you, you can merge. Is it necessary that I squash or can you squash during merge? |
I don't get why Travis fails. |
Why don't you get it? Have you not seen the log or don't you think it is related to your changed?
|
I'll check tomorrow why it compiles for me. |
Signed-off-by: Jan N. Klug <jan.n.klug@rub.de>
@@ -376,6 +377,7 @@ public String getName() { | |||
* | |||
* @param item the item to get the average state value for | |||
* @param timestamp the point in time from which to search for the average state value | |||
* @param includeCurrentValue include the current value in the calculation |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this line should be removed again
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
MathContext.DECIMAL64); | ||
timeSpan = thisTimestamp.subtract(lastTimestamp); | ||
total = total.add(avgValue.multiply(timeSpan, MathContext.DECIMAL64)); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please remove this empty line
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
long recentInterval = DateTime.now().getMillis() - endStored.getMillis(); | ||
double expected = (2007.4994 * storedInterval + 2518.5 * recentInterval) / (storedInterval + recentInterval); | ||
DecimalType average = PersistenceExtensions.averageSince(item, startStored, "test"); | ||
assertEquals(expected, average.doubleValue(), 0.001); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this test stable enough? If the execution is slow, the average might be calculated with a much bigger value than recentInterval already and the results might differ, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have adjusted it to 0.01 which should be good enough even for every slow execution. In my tests the difference was always around 0.0001, so a factor of 100 better than the value I have set now.
Signed-off-by: Jan N. Klug <jan.n.klug@rub.de>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm, thanks a lot!
* Fix averageSince calculation Signed-off-by: Jan N. Klug <jan.n.klug@rub.de>
Average since is calculated over persisted values AND the actual value.
The description describes that it is calculated over persisted values OR
the actual value (if no persisted values are found). The description is
desribes the correct behaviour, while the implementation was wrong. This
is fixed with this PR.
Bug: #2404
Signed-off-by: Jan N. Klug jan.n.klug@rub.de