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 java.lang.Integer.toHexString; 19 import static org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertFalse; 21 import static org.junit.Assert.assertNotNull; 22 import static org.junit.Assert.assertTrue; 23 import static org.junit.Assume.assumeTrue; 24 25 import android.annotation.Nullable; 26 import android.car.Car; 27 import android.car.CarNotConnectedException; 28 import android.car.hardware.CarPropertyConfig; 29 import android.car.hardware.CarSensorManager; 30 import android.car.hardware.CarSensorManager.OnSensorChangedListener; 31 import android.car.hardware.hvac.CarHvacManager; 32 import android.content.ComponentName; 33 import android.content.Context; 34 import android.content.ServiceConnection; 35 import android.hardware.automotive.vehicle.V2_0.IVehicle; 36 import android.hardware.automotive.vehicle.V2_0.StatusCode; 37 import android.hardware.automotive.vehicle.V2_0.VehicleArea; 38 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue; 39 import android.hardware.automotive.vehicle.V2_0.VehicleProperty; 40 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyGroup; 41 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyType; 42 import android.os.Handler; 43 import android.os.HandlerThread; 44 import android.os.IBinder; 45 import android.os.RemoteException; 46 import android.os.SystemClock; 47 import android.support.test.InstrumentationRegistry; 48 import android.support.test.runner.AndroidJUnit4; 49 import android.test.suitebuilder.annotation.MediumTest; 50 import android.util.SparseArray; 51 import android.util.SparseIntArray; 52 53 import com.google.android.collect.Lists; 54 55 import com.android.car.vehiclehal.VehiclePropValueBuilder; 56 57 import org.junit.After; 58 import org.junit.Before; 59 import org.junit.BeforeClass; 60 import org.junit.Test; 61 import org.junit.runner.RunWith; 62 63 import java.util.ArrayList; 64 import java.util.List; 65 import java.util.concurrent.CountDownLatch; 66 import java.util.concurrent.Semaphore; 67 import java.util.concurrent.TimeUnit; 68 69 /** 70 * This test suite will make e2e test and measure some performance characteristics. The main idea 71 * is to send command to Vehicle HAL to generate some events with certain time interval and capture 72 * these events through car public API, e.g. CarSensorManager. 73 * 74 * TODO(pavelm): benchmark tests might be flaky, need a way to run them multiple times / use avg 75 * metrics. 76 */ 77 @MediumTest 78 @RunWith(AndroidJUnit4.class) 79 public class E2ePerformanceTest { 80 private static String TAG = Utils.concatTag(E2ePerformanceTest.class); 81 82 private IVehicle mVehicle; 83 private final CarConnectionListener mConnectionListener = new CarConnectionListener(); 84 private Context mContext; 85 private Car mCar; 86 87 private static Handler sEventHandler; 88 private static final HandlerThread sHandlerThread = new HandlerThread(TAG); 89 90 private static final int DEFAULT_WAIT_TIMEOUT_MS = 1000; 91 92 private static final int GENERATE_FAKE_DATA_CONTROLLING_PROPERTY = 0x0666 93 | VehiclePropertyGroup.VENDOR 94 | VehicleArea.GLOBAL 95 | VehiclePropertyType.COMPLEX; 96 97 private static final int CMD_START = 1; 98 private static final int CMD_STOP = 0; 99 100 private HalEventsGenerator mEventsGenerator; 101 102 @BeforeClass 103 public static void setupEventHandler() { 104 sHandlerThread.start(); 105 sEventHandler = new Handler(sHandlerThread.getLooper()); 106 } 107 108 @Before 109 public void connectToVehicleHal() throws Exception { 110 mVehicle = Utils.getVehicle(); 111 112 mVehicle.getPropConfigs(Lists.newArrayList(GENERATE_FAKE_DATA_CONTROLLING_PROPERTY), 113 (status, propConfigs) -> assumeTrue(status == StatusCode.OK)); 114 115 mEventsGenerator = new HalEventsGenerator(mVehicle); 116 } 117 118 @Before 119 public void connectToCarService() throws Exception { 120 mContext = InstrumentationRegistry.getContext(); 121 mCar = Car.createCar(mContext, mConnectionListener, sEventHandler); 122 assertNotNull(mCar); 123 mCar.connect(); 124 mConnectionListener.waitForConnection(DEFAULT_WAIT_TIMEOUT_MS); 125 } 126 127 @After 128 public void disconnect() throws Exception { 129 if (mVehicle != null) { 130 mEventsGenerator.stop(); 131 mVehicle = null; 132 mEventsGenerator = null; 133 } 134 if (mCar != null) { 135 mCar.disconnect(); 136 mCar = null; 137 } 138 } 139 140 @Test 141 public void singleOnChangeProperty() throws Exception { 142 verifyEventsFromSingleProperty( 143 CarSensorManager.SENSOR_TYPE_ODOMETER, VehicleProperty.PERF_ODOMETER); 144 } 145 146 @Test 147 public void singleContinuousProperty() throws Exception { 148 verifyEventsFromSingleProperty( 149 CarSensorManager.SENSOR_TYPE_RPM, VehicleProperty.ENGINE_RPM); 150 } 151 152 @Test 153 public void benchmarkEventBandwidthThroughCarService() throws Exception { 154 int[] mgrProperties = new int[] { 155 CarSensorManager.SENSOR_TYPE_ODOMETER, 156 CarSensorManager.SENSOR_TYPE_RPM, 157 CarSensorManager.SENSOR_TYPE_CAR_SPEED 158 }; 159 // Expecting to receive at least 10 events within 150ms. 160 final int EVENT_INTERVAL_MS = 1; // 161 final int EXPECTED_EVENTS_PER_PROPERTY = 1000; 162 final int EXPECTED_EVENTS = EXPECTED_EVENTS_PER_PROPERTY * mgrProperties.length; 163 164 CarSensorManager mgr = (CarSensorManager) mCar.getCarManager(Car.SENSOR_SERVICE); 165 assertNotNull(mgr); 166 for (int mgrPropId: mgrProperties) { 167 assertTrue("PropId: 0x" + toHexString(mgrPropId) + " is not supported", 168 mgr.isSensorSupported(mgrPropId)); 169 } 170 171 mEventsGenerator 172 .reset() 173 .setIntervalMs(EVENT_INTERVAL_MS) 174 .setInitialValue(1000) 175 .setIncrement(1.0f) 176 .setDispersion(100) 177 .start(VehicleProperty.PERF_ODOMETER); 178 179 mEventsGenerator 180 .start(VehicleProperty.ENGINE_RPM); 181 182 mEventsGenerator 183 .setIntervalMs(EVENT_INTERVAL_MS) 184 .setInitialValue(20.0f) 185 .setIncrement(0.1f) 186 .setDispersion(10) 187 .start(VehicleProperty.PERF_VEHICLE_SPEED); 188 189 SparseArray<CountDownLatch> eventsCounters = new SparseArray<>(); 190 for (int i = 0; i < mgrProperties.length; i++) { 191 eventsCounters.put(mgrProperties[i], new CountDownLatch(EXPECTED_EVENTS_PER_PROPERTY)); 192 } 193 OnSensorChangedListener listener = e -> eventsCounters.get(e.sensorType).countDown(); 194 for (int propId: mgrProperties) { 195 mgr.registerListener(listener, propId, CarSensorManager.SENSOR_RATE_FASTEST); 196 } 197 198 final long WAIT_TIME = (long) ((EVENT_INTERVAL_MS * EXPECTED_EVENTS_PER_PROPERTY) * 1.6); 199 CountDownLatch[] latches = new CountDownLatch[eventsCounters.size()]; 200 for (int i = 0; i < eventsCounters.size(); i++) { 201 latches[i] = eventsCounters.valueAt(i); 202 } 203 boolean allEventsReceived = awaitCountDownLatches(latches, WAIT_TIME); 204 mgr.unregisterListener(listener); 205 mEventsGenerator.stop(); 206 207 if (!allEventsReceived) { 208 SparseIntArray missingEventsPerProperty = new SparseIntArray(); 209 for (int i = 0; i < eventsCounters.size(); i++) { 210 int missingEvents = (int) eventsCounters.valueAt(i).getCount(); 211 if (missingEvents > 0) { 212 missingEventsPerProperty.put(eventsCounters.keyAt(i), missingEvents); 213 } 214 } 215 216 assertTrue("Too slow. Expected to receive: " + EXPECTED_EVENTS 217 + " within " + WAIT_TIME + " ms, " 218 + " missing events per property: " + missingEventsPerProperty, 219 missingEventsPerProperty.size() == 0); 220 } 221 } 222 223 @Test 224 public void benchmarkSetGetFromSingleClient() throws Exception { 225 final int PROP = CarHvacManager.ID_WINDOW_DEFROSTER_ON; 226 CarHvacManager mgr = (CarHvacManager) mCar.getCarManager(Car.HVAC_SERVICE); 227 assertNotNull(mgr); 228 long start = SystemClock.elapsedRealtimeNanos(); 229 230 final long TEST_DURATION_NANO = 1_000_000_000; // 1 second. 231 final long EXPECTED_ITERATIONS = 100; // We expect to have at least 100 get/set calls. 232 233 boolean value = false; 234 long actualIterations = 0; 235 while (SystemClock.elapsedRealtimeNanos() < start + TEST_DURATION_NANO) { 236 mgr.setBooleanProperty(PROP, 0, value); 237 boolean actualValue = mgr.getBooleanProperty(PROP, 0); 238 assertEquals(value, actualValue); 239 value = !value; 240 actualIterations++; 241 } 242 assertTrue("Too slow. Expected iterations: " + EXPECTED_ITERATIONS 243 + ", actual: " + actualIterations 244 + ", test duration: " + TEST_DURATION_NANO / 1000_1000 + " ms.", 245 actualIterations >= EXPECTED_ITERATIONS); 246 } 247 248 @Test 249 public void benchmarkSetGetFromSingleClientMultipleThreads() throws Exception { 250 final int PROP = CarHvacManager.ID_ZONED_TEMP_SETPOINT; 251 CarHvacManager mgr = (CarHvacManager) mCar.getCarManager(Car.HVAC_SERVICE); 252 assertNotNull(mgr); 253 254 CarPropertyConfig<Float> cfg = findHvacPropConfig(Float.class, PROP, mgr); 255 assertNotNull(cfg); 256 assertTrue("Expected at least 2 zones for 0x" + Integer.toHexString(PROP) 257 + ", got: " + cfg.getAreaCount(), cfg.getAreaCount() >= 2); 258 259 260 final int EXPECTED_INVOCATIONS = 1000; // How many time get/set will be called. 261 final int EXPECTED_DURATION_MS = 2500; 262 CountDownLatch counter = new CountDownLatch(EXPECTED_INVOCATIONS); 263 264 List<Thread> threads = new ArrayList<>(Lists.newArrayList( 265 new Thread(() -> invokeSetAndGetForHvacFloat(mgr, cfg, cfg.getAreaIds()[0], counter)), 266 new Thread(() -> invokeSetAndGetForHvacFloat(mgr, cfg, cfg.getAreaIds()[1], counter)))); 267 268 for (Thread t : threads) { 269 t.start(); 270 } 271 272 counter.await(EXPECTED_DURATION_MS, TimeUnit.MILLISECONDS); 273 long missingInvocations = counter.getCount(); 274 assertTrue("Failed to invoke get/set " + EXPECTED_INVOCATIONS 275 + " within " + EXPECTED_DURATION_MS + "ms" 276 + ", actually invoked: " + (EXPECTED_INVOCATIONS - missingInvocations), 277 missingInvocations == 0); 278 279 for (Thread t : threads) { 280 t.join(10000); // Let thread join to not interfere with other test. 281 assertFalse(t.isAlive()); 282 } 283 } 284 285 private void invokeSetAndGetForHvacFloat(CarHvacManager mgr, 286 CarPropertyConfig<Float> cfg, int areaId, CountDownLatch counter) { 287 float minValue = cfg.getMinValue(areaId); 288 float maxValue = cfg.getMaxValue(areaId); 289 float curValue = minValue; 290 291 while (counter.getCount() > 0) { 292 float actualValue; 293 try { 294 mgr.setFloatProperty(cfg.getPropertyId(), areaId, curValue); 295 actualValue = mgr.getFloatProperty(cfg.getPropertyId(), areaId); 296 } catch (CarNotConnectedException e) { 297 throw new RuntimeException(e); 298 } 299 assertEquals(curValue, actualValue, 0.001); 300 curValue += 0.5; 301 if (curValue > maxValue) { 302 curValue = minValue; 303 } 304 305 counter.countDown(); 306 } 307 } 308 309 @Nullable 310 private <T> CarPropertyConfig<T> findHvacPropConfig(Class<T> clazz, int hvacPropId, 311 CarHvacManager mgr) 312 throws CarNotConnectedException { 313 for (CarPropertyConfig<?> cfg : mgr.getPropertyList()) { 314 if (cfg.getPropertyId() == hvacPropId) { 315 return (CarPropertyConfig<T>) cfg; 316 } 317 } 318 return null; 319 } 320 321 private void verifyEventsFromSingleProperty(int mgrPropId, int halPropId) throws Exception { 322 // Expecting to receive at least 10 events within 150ms. 323 final int EXPECTED_EVENTS = 10; 324 final int EXPECTED_TIME_DURATION_MS = 150; 325 final float INITIAL_VALUE = 1000; 326 final float INCREMENT = 1.0f; 327 328 CarSensorManager mgr = (CarSensorManager) mCar.getCarManager(Car.SENSOR_SERVICE); 329 assertNotNull(mgr); 330 assertTrue(mgr.isSensorSupported(mgrPropId)); 331 332 mEventsGenerator 333 .setIntervalMs(10) 334 .setInitialValue(INITIAL_VALUE) 335 .setIncrement(INCREMENT) 336 .setDispersion(100) 337 .start(halPropId); 338 339 CountDownLatch latch = new CountDownLatch(EXPECTED_EVENTS); 340 OnSensorChangedListener listener = event -> latch.countDown(); 341 342 mgr.registerListener(listener, mgrPropId, CarSensorManager.SENSOR_RATE_FASTEST); 343 try { 344 assertTrue(latch.await(EXPECTED_TIME_DURATION_MS, TimeUnit.MILLISECONDS)); 345 } finally { 346 mgr.unregisterListener(listener); 347 } 348 } 349 350 private static class CarConnectionListener implements ServiceConnection { 351 private final Semaphore mConnectionWait = new Semaphore(0); 352 353 void waitForConnection(long timeoutMs) throws InterruptedException { 354 mConnectionWait.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS); 355 } 356 357 @Override 358 public void onServiceConnected(ComponentName name, IBinder service) { 359 mConnectionWait.release(); 360 } 361 362 @Override 363 public void onServiceDisconnected(ComponentName name) { } 364 } 365 366 private static boolean awaitCountDownLatches(CountDownLatch[] latches, long timeoutMs) 367 throws InterruptedException { 368 long start = SystemClock.elapsedRealtime(); 369 for (int i = 0; i < latches.length; i++) { 370 long timeLeft = timeoutMs - (SystemClock.elapsedRealtime() - start); 371 if (!latches[i].await(timeLeft, TimeUnit.MILLISECONDS)) { 372 return false; 373 } 374 } 375 376 return true; 377 } 378 379 static class HalEventsGenerator { 380 private final IVehicle mVehicle; 381 382 private long mIntervalMs; 383 private float mInitialValue; 384 private float mDispersion; 385 private float mIncrement; 386 387 HalEventsGenerator(IVehicle vehicle) { 388 mVehicle = vehicle; 389 reset(); 390 } 391 392 HalEventsGenerator reset() { 393 mIntervalMs = 1000; 394 mInitialValue = 1000; 395 mDispersion = 0; 396 mInitialValue = 0; 397 return this; 398 } 399 400 HalEventsGenerator setIntervalMs(long intervalMs) { 401 mIntervalMs = intervalMs; 402 return this; 403 } 404 405 HalEventsGenerator setInitialValue(float initialValue) { 406 mInitialValue = initialValue; 407 return this; 408 } 409 410 HalEventsGenerator setDispersion(float dispersion) { 411 mDispersion = dispersion; 412 return this; 413 } 414 415 HalEventsGenerator setIncrement(float increment) { 416 mIncrement = increment; 417 return this; 418 } 419 420 void start(int propId) throws RemoteException { 421 VehiclePropValue request = 422 VehiclePropValueBuilder.newBuilder(GENERATE_FAKE_DATA_CONTROLLING_PROPERTY) 423 .addIntValue(CMD_START, propId) 424 .setInt64Value(mIntervalMs * 1000_000) 425 .addFloatValue(mInitialValue, mDispersion, mIncrement) 426 .build(); 427 assertEquals(StatusCode.OK, mVehicle.set(request)); 428 } 429 430 void stop() throws RemoteException { 431 stop(0); 432 } 433 434 void stop(int propId) throws RemoteException { 435 VehiclePropValue request = 436 VehiclePropValueBuilder.newBuilder(GENERATE_FAKE_DATA_CONTROLLING_PROPERTY) 437 .addIntValue(CMD_STOP, propId) 438 .build(); 439 assertEquals(StatusCode.OK, mVehicle.set(request)); 440 } 441 } 442 } 443