Home | History | Annotate | Download | only in test
      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