1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.car.vehiclehal.test; 17 18 import static org.junit.Assert.assertEquals; 19 import static org.junit.Assert.assertFalse; 20 import static org.junit.Assert.assertNotNull; 21 import static org.junit.Assert.assertTrue; 22 import static org.junit.Assert.fail; 23 24 import android.annotation.Nullable; 25 import android.car.Car; 26 import android.car.hardware.CarPropertyConfig; 27 import android.car.hardware.CarSensorManager; 28 import android.car.hardware.CarSensorManager.OnSensorChangedListener; 29 import android.car.hardware.hvac.CarHvacManager; 30 import android.hardware.automotive.vehicle.V2_0.VehicleProperty; 31 import android.os.SystemClock; 32 import android.util.Log; 33 import android.util.SparseArray; 34 import android.util.SparseIntArray; 35 36 import androidx.test.filters.MediumTest; 37 import androidx.test.runner.AndroidJUnit4; 38 39 import com.google.android.collect.Lists; 40 41 import org.junit.Test; 42 import org.junit.runner.RunWith; 43 44 import static java.lang.Integer.toHexString; 45 46 import java.util.ArrayList; 47 import java.util.List; 48 import java.util.concurrent.CountDownLatch; 49 import java.util.concurrent.TimeUnit; 50 51 /** 52 * This test suite will make e2e test and measure some performance characteristics. The main idea 53 * is to send command to Vehicle HAL to generate some events with certain time interval and capture 54 * these events through car public API, e.g. CarSensorManager. 55 * 56 * TODO(pavelm): benchmark tests might be flaky, need a way to run them multiple times / use avg 57 * metrics. 58 */ 59 @MediumTest 60 @RunWith(AndroidJUnit4.class) 61 public class E2ePerformanceTest extends E2eCarTestBase { 62 private static String TAG = Utils.concatTag(E2ePerformanceTest.class); 63 64 @Test singleOnChangeProperty()65 public void singleOnChangeProperty() throws Exception { 66 verifyEventsFromSingleProperty( 67 CarSensorManager.SENSOR_TYPE_ODOMETER, VehicleProperty.PERF_ODOMETER); 68 } 69 70 @Test singleContinuousProperty()71 public void singleContinuousProperty() throws Exception { 72 verifyEventsFromSingleProperty( 73 CarSensorManager.SENSOR_TYPE_CAR_SPEED, VehicleProperty.PERF_VEHICLE_SPEED); 74 } 75 76 @Test benchmarkEventBandwidthThroughCarService()77 public void benchmarkEventBandwidthThroughCarService() throws Exception { 78 int[] mgrProperties = new int[] { 79 CarSensorManager.SENSOR_TYPE_ODOMETER, 80 CarSensorManager.SENSOR_TYPE_CAR_SPEED 81 }; 82 // CarPropertyManager supports highest rate is 100hz which means event interval is 10ms. 83 final int EVENT_INTERVAL_MS = 10; // 84 final int EXPECTED_EVENTS_PER_PROPERTY = 100; 85 final int EXPECTED_EVENTS = EXPECTED_EVENTS_PER_PROPERTY * mgrProperties.length; 86 87 CarSensorManager mgr = (CarSensorManager) mCar.getCarManager(Car.SENSOR_SERVICE); 88 assertNotNull(mgr); 89 for (int mgrPropId: mgrProperties) { 90 assertTrue("PropId: 0x" + toHexString(mgrPropId) + " is not supported", 91 mgr.isSensorSupported(mgrPropId)); 92 } 93 94 VhalEventGenerator odometerGenerator = new LinearVhalEventGenerator(mVehicle) 95 .setProp(VehicleProperty.PERF_ODOMETER) 96 .setIntervalMs(EVENT_INTERVAL_MS) 97 .setInitialValue(1000) 98 .setIncrement(1.0f) 99 .setDispersion(100); 100 101 102 VhalEventGenerator speedGenerator = new LinearVhalEventGenerator(mVehicle) 103 .setProp(VehicleProperty.PERF_VEHICLE_SPEED) 104 .setIntervalMs(EVENT_INTERVAL_MS) 105 .setInitialValue(20.0f) 106 .setIncrement(0.1f) 107 .setDispersion(10); 108 109 odometerGenerator.start(); 110 speedGenerator.start(); 111 112 SparseArray<CountDownLatch> eventsCounters = new SparseArray<>(); 113 for (int i = 0; i < mgrProperties.length; i++) { 114 eventsCounters.put(mgrProperties[i], new CountDownLatch(EXPECTED_EVENTS_PER_PROPERTY)); 115 } 116 OnSensorChangedListener listener = e -> eventsCounters.get(e.sensorType).countDown(); 117 for (int propId: mgrProperties) { 118 mgr.registerListener(listener, propId, CarSensorManager.SENSOR_RATE_FASTEST); 119 } 120 121 final long WAIT_TIME = (long) ((EVENT_INTERVAL_MS * EXPECTED_EVENTS_PER_PROPERTY) * 1.6); 122 CountDownLatch[] latches = new CountDownLatch[eventsCounters.size()]; 123 for (int i = 0; i < eventsCounters.size(); i++) { 124 latches[i] = eventsCounters.valueAt(i); 125 } 126 boolean allEventsReceived = awaitCountDownLatches(latches, WAIT_TIME); 127 mgr.unregisterListener(listener); 128 129 odometerGenerator.stop(); 130 speedGenerator.stop(); 131 132 if (!allEventsReceived) { 133 SparseIntArray missingEventsPerProperty = new SparseIntArray(); 134 for (int i = 0; i < eventsCounters.size(); i++) { 135 int missingEvents = (int) eventsCounters.valueAt(i).getCount(); 136 if (missingEvents > 0) { 137 missingEventsPerProperty.put(eventsCounters.keyAt(i), missingEvents); 138 } 139 } 140 141 assertTrue("Too slow. Expected to receive: " + EXPECTED_EVENTS 142 + " within " + WAIT_TIME + " ms, " 143 + " missing events per property: " + missingEventsPerProperty, 144 missingEventsPerProperty.size() == 0); 145 } 146 } 147 148 @Test benchmarkSetGetFromSingleClient()149 public void benchmarkSetGetFromSingleClient() throws Exception { 150 final int PROP = CarHvacManager.ID_WINDOW_DEFROSTER_ON; 151 CarHvacManager mgr = (CarHvacManager) mCar.getCarManager(Car.HVAC_SERVICE); 152 assertNotNull(mgr); 153 long start = SystemClock.elapsedRealtimeNanos(); 154 155 final long TEST_DURATION_NANO = 1_000_000_000; // 1 second. 156 final long EXPECTED_ITERATIONS = 100; // We expect to have at least 100 get/set calls. 157 158 boolean value = false; 159 long actualIterations = 0; 160 while (SystemClock.elapsedRealtimeNanos() < start + TEST_DURATION_NANO) { 161 mgr.setBooleanProperty(PROP, 1, value); 162 boolean actualValue = mgr.getBooleanProperty(PROP, 1); 163 assertEquals(value, actualValue); 164 value = !value; 165 actualIterations++; 166 } 167 assertTrue("Too slow. Expected iterations: " + EXPECTED_ITERATIONS 168 + ", actual: " + actualIterations 169 + ", test duration: " + TEST_DURATION_NANO / 1000_1000 + " ms.", 170 actualIterations >= EXPECTED_ITERATIONS); 171 } 172 173 @Test benchmarkSetGetFromSingleClientMultipleThreads()174 public void benchmarkSetGetFromSingleClientMultipleThreads() throws Exception { 175 final int PROP = CarHvacManager.ID_ZONED_TEMP_SETPOINT; 176 CarHvacManager mgr = (CarHvacManager) mCar.getCarManager(Car.HVAC_SERVICE); 177 assertNotNull(mgr); 178 179 CarPropertyConfig<Float> cfg = findHvacPropConfig(Float.class, PROP, mgr); 180 assertNotNull(cfg); 181 assertTrue("Expected at least 2 zones for 0x" + Integer.toHexString(PROP) 182 + ", got: " + cfg.getAreaCount(), cfg.getAreaCount() >= 2); 183 184 185 final int EXPECTED_INVOCATIONS = 1000; // How many time get/set will be called. 186 final int EXPECTED_DURATION_MS = 3000; 187 // This is a stress test and it can be flaky because it shares resources with all currently 188 // running process. Let's have this number of attempt before giving up. 189 final int ATTEMPTS = 5; 190 191 for (int curAttempt = 0; curAttempt < ATTEMPTS; curAttempt++) { 192 long missingInvocations = stressTestHvacProperties(mgr, cfg, 193 EXPECTED_INVOCATIONS, EXPECTED_DURATION_MS); 194 if (missingInvocations == 0) return; // All done. 195 196 Log.w(TAG, "Failed to invoke get/set " + EXPECTED_INVOCATIONS 197 + " within " + EXPECTED_DURATION_MS + "ms" 198 + ", actually invoked: " 199 + (EXPECTED_INVOCATIONS - missingInvocations)); 200 } 201 fail("Failed to invoke get/set " + EXPECTED_INVOCATIONS + " within " 202 + EXPECTED_DURATION_MS + "ms. Number of attempts: " + ATTEMPTS 203 + ". See logs for details."); 204 } 205 stressTestHvacProperties(CarHvacManager mgr, CarPropertyConfig<Float> cfg, int EXPECTED_INVOCATIONS, int EXPECTED_DURATION_MS)206 private long stressTestHvacProperties(CarHvacManager mgr, CarPropertyConfig<Float> cfg, 207 int EXPECTED_INVOCATIONS, int EXPECTED_DURATION_MS) throws InterruptedException { 208 CountDownLatch counter = new CountDownLatch(EXPECTED_INVOCATIONS); 209 210 List<Thread> threads = new ArrayList<>(Lists.newArrayList( 211 new Thread(() -> invokeSetAndGetForHvacFloat(mgr, cfg, cfg.getAreaIds()[0], counter)), 212 new Thread(() -> invokeSetAndGetForHvacFloat(mgr, cfg, cfg.getAreaIds()[1], counter)))); 213 214 for (Thread t : threads) { 215 t.start(); 216 } 217 218 counter.await(EXPECTED_DURATION_MS, TimeUnit.MILLISECONDS); 219 long missingInvocations = counter.getCount(); 220 221 for (Thread t : threads) { 222 t.join(10000); // Let thread join to not interfere with other test. 223 assertFalse(t.isAlive()); 224 } 225 return missingInvocations; 226 } 227 invokeSetAndGetForHvacFloat(CarHvacManager mgr, CarPropertyConfig<Float> cfg, int areaId, CountDownLatch counter)228 private void invokeSetAndGetForHvacFloat(CarHvacManager mgr, 229 CarPropertyConfig<Float> cfg, int areaId, CountDownLatch counter) { 230 float minValue = cfg.getMinValue(areaId); 231 float maxValue = cfg.getMaxValue(areaId); 232 float curValue = minValue; 233 234 while (counter.getCount() > 0) { 235 float actualValue; 236 mgr.setFloatProperty(cfg.getPropertyId(), areaId, curValue); 237 actualValue = mgr.getFloatProperty(cfg.getPropertyId(), areaId); 238 assertEquals(curValue, actualValue, 0.001); 239 curValue += 0.5; 240 if (curValue > maxValue) { 241 curValue = minValue; 242 } 243 244 counter.countDown(); 245 } 246 } 247 248 @Nullable findHvacPropConfig( Class<T> clazz, int hvacPropId, CarHvacManager mgr)249 private <T> CarPropertyConfig<T> findHvacPropConfig( 250 Class<T> clazz, int hvacPropId, CarHvacManager mgr) { 251 for (CarPropertyConfig<?> cfg : mgr.getPropertyList()) { 252 if (cfg.getPropertyId() == hvacPropId) { 253 return (CarPropertyConfig<T>) cfg; 254 } 255 } 256 return null; 257 } 258 verifyEventsFromSingleProperty(int mgrPropId, int halPropId)259 private void verifyEventsFromSingleProperty(int mgrPropId, int halPropId) throws Exception { 260 // Expecting to receive at least 10 events within 150ms. 261 final int EXPECTED_EVENTS = 10; 262 final int EXPECTED_TIME_DURATION_MS = 150; 263 final float INITIAL_VALUE = 1000; 264 final float INCREMENT = 1.0f; 265 266 CarSensorManager mgr = (CarSensorManager) mCar.getCarManager(Car.SENSOR_SERVICE); 267 assertNotNull(mgr); 268 assertTrue(mgr.isSensorSupported(mgrPropId)); 269 270 VhalEventGenerator generator = new LinearVhalEventGenerator(mVehicle) 271 .setProp(halPropId) 272 .setIntervalMs(10) 273 .setInitialValue(INITIAL_VALUE) 274 .setIncrement(INCREMENT) 275 .setDispersion(100); 276 277 generator.start(); 278 279 CountDownLatch latch = new CountDownLatch(EXPECTED_EVENTS); 280 OnSensorChangedListener listener = event -> latch.countDown(); 281 282 mgr.registerListener(listener, mgrPropId, CarSensorManager.SENSOR_RATE_FASTEST); 283 try { 284 assertTrue(latch.await(EXPECTED_TIME_DURATION_MS, TimeUnit.MILLISECONDS)); 285 } finally { 286 generator.stop(); 287 mgr.unregisterListener(listener); 288 } 289 } 290 291 awaitCountDownLatches(CountDownLatch[] latches, long timeoutMs)292 private static boolean awaitCountDownLatches(CountDownLatch[] latches, long timeoutMs) 293 throws InterruptedException { 294 long start = SystemClock.elapsedRealtime(); 295 for (int i = 0; i < latches.length; i++) { 296 long timeLeft = timeoutMs - (SystemClock.elapsedRealtime() - start); 297 if (!latches[i].await(timeLeft, TimeUnit.MILLISECONDS)) { 298 return false; 299 } 300 } 301 302 return true; 303 } 304 } 305