Add random noise generator for Power Monitor API

Bug: 273310268
Test: atest FrameworksServicesTests:IntervalRandomNoiseGeneratorTest

Change-Id: Icf61ba54096d398e0d3722d05be7e2a196670936
This commit is contained in:
Dmitri Plotnikov
2023-03-21 11:15:08 -07:00
parent 3817543647
commit 1ea5f812f7
3 changed files with 192 additions and 0 deletions

View File

@@ -185,6 +185,7 @@ java_library_static {
"com.android.sysprop.watchdog",
"ImmutabilityAnnotation",
"securebox",
"apache-commons-math",
],
javac_shard_size: 50,
javacflags: [

View File

@@ -0,0 +1,83 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.powerstats;
import com.android.internal.annotations.VisibleForTesting;
import org.apache.commons.math.MathException;
import org.apache.commons.math.distribution.AbstractContinuousDistribution;
import org.apache.commons.math.distribution.BetaDistributionImpl;
import java.util.Arrays;
/**
* Adds random noise to provided value, keeping it within the limits of a specified range.
* @hide
*/
public class IntervalRandomNoiseGenerator {
private static final int DISTRIBUTION_SAMPLE_SIZE = 17;
private final AbstractContinuousDistribution mDistribution;
private final double[] mSamples = new double[DISTRIBUTION_SAMPLE_SIZE];
private static final double UNINITIALIZED = -1;
/**
* Higher alpha makes the distribution more asymmetrical, tightening it
* closer to the high bound. A value of alpha should be > 1 to ensure
* that the samples closer to 1 appear more frequently t those closer
* to 0.
*/
IntervalRandomNoiseGenerator(double alpha) {
if (alpha <= 1) {
throw new IllegalArgumentException("alpha should be > 1");
}
mDistribution = new BetaDistributionImpl(alpha, 1 /* beta */);
refresh();
}
@VisibleForTesting
void reseed(long seed) {
mDistribution.reseedRandomGenerator(seed);
}
/**
* Returns a random value between the specified bounds, statistically closer to the
* highProbabilityBound.
*
* The same value is returned for a given stickyKey until {@link #refresh()} is called.
*/
long addNoise(long lowProbabilityBound, long highProbabilityBound, int stickyKey) {
double sample = mSamples[stickyKey % DISTRIBUTION_SAMPLE_SIZE];
if (sample < 0) { // UNINITIALIZED
try {
sample = mDistribution.sample();
} catch (MathException e) {
throw new IllegalStateException(e);
}
mSamples[stickyKey % DISTRIBUTION_SAMPLE_SIZE] = sample;
}
return lowProbabilityBound + (long) ((highProbabilityBound - lowProbabilityBound) * sample);
}
/**
* Resets the cache of random samples.
*/
void refresh() {
Arrays.fill(mSamples, UNINITIALIZED);
}
}

View File

@@ -0,0 +1,108 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.powerstats;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import org.jetbrains.annotations.NotNull;
import org.junit.Test;
import java.util.function.Supplier;
public class IntervalRandomNoiseGeneratorTest {
@Test
public void parameterizedDistribution() {
// Assert closeness to theoretical distribution
assertDistribution(3.0,
0.0392,
0.2617,
0.6990);
assertDistribution(5.0,
0.0003,
0.0098,
0.0676,
0.2502,
0.6720);
assertDistribution(9.0,
0.0000,
0.0002,
0.0097,
0.1242,
0.8658);
}
private void assertDistribution(double alpha, Double... expectedBuckets) {
IntervalRandomNoiseGenerator generator = new IntervalRandomNoiseGenerator(alpha);
generator.reseed(42); // Make test repeatable
final int sampleCount = 1000;
final int bucketCount = expectedBuckets.length;
int[] histogram = buildHistogram(() -> {
generator.refresh();
return generator.addNoise(100, 200, 12345);
}, sampleCount, bucketCount, 100, 200);
for (int i = 0; i < expectedBuckets.length; i++) {
assertWithMessage("Bucket #" + i)
.that((double) histogram[i] / sampleCount)
.isWithin(0.05)
.of(expectedBuckets[i]);
}
}
@NotNull
private int[] buildHistogram(Supplier<Long> generator, int sampleCount,
int bucketCount, int lowBound, int highBound) {
int[] buckets = new int[bucketCount];
for (int i = 0; i < sampleCount; i++) {
long sample = generator.get();
assertThat(sample).isAtLeast(lowBound);
assertThat(sample).isAtMost(highBound);
buckets[(int) ((double) (sample - lowBound) / (highBound - lowBound) * bucketCount)]++;
}
return buckets;
}
@Test
public void stickiness() {
IntervalRandomNoiseGenerator generator = new IntervalRandomNoiseGenerator(9);
generator.reseed(42); // Make test repeatable
long value1a = generator.addNoise(1000, 5000, 123);
long value1b = generator.addNoise(1000, 5000, 123);
long value1c = generator.addNoise(1000, 5000, 123);
assertThat(value1b).isEqualTo(value1a);
assertThat(value1c).isEqualTo(value1a);
// Different stickyKey
long value2a = generator.addNoise(1000, 5000, 321);
long value2b = generator.addNoise(1000, 5000, 321);
assertThat(value2a).isNotEqualTo(value1a);
assertThat(value2b).isEqualTo(value2a);
generator.refresh();
// Same stickyKey after a refresh - different value
long value3 = generator.addNoise(1000, 5000, 123);
long value4 = generator.addNoise(1000, 5000, 321);
assertThat(value3).isNotEqualTo(value1a);
assertThat(value4).isNotEqualTo(value2a);
}
}