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 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 static java.lang.Integer.toHexString;
     25 
     26 import android.annotation.Nullable;
     27 import android.car.Car;
     28 import android.car.CarNotConnectedException;
     29 import android.car.hardware.CarPropertyConfig;
     30 import android.car.hardware.CarSensorManager;
     31 import android.car.hardware.CarSensorManager.OnSensorChangedListener;
     32 import android.car.hardware.hvac.CarHvacManager;
     33 import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
     34 import android.os.SystemClock;
     35 import android.support.test.filters.MediumTest;
     36 import android.support.test.runner.AndroidJUnit4;
     37 import android.util.Log;
     38 import android.util.SparseArray;
     39 import android.util.SparseIntArray;
     40 
     41 import com.google.android.collect.Lists;
     42 
     43 import org.junit.Test;
     44 import org.junit.runner.RunWith;
     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
     65     public void singleOnChangeProperty() throws Exception {
     66         verifyEventsFromSingleProperty(
     67                 CarSensorManager.SENSOR_TYPE_ODOMETER, VehicleProperty.PERF_ODOMETER);
     68     }
     69 
     70     @Test
     71     public void singleContinuousProperty() throws Exception {
     72         verifyEventsFromSingleProperty(
     73                 CarSensorManager.SENSOR_TYPE_CAR_SPEED, VehicleProperty.PERF_VEHICLE_SPEED);
     74     }
     75 
     76     @Test
     77     public void benchmarkEventBandwidthThroughCarService() throws Exception {
     78         int[] mgrProperties = new int[] {
     79                 CarSensorManager.SENSOR_TYPE_ODOMETER,
     80                 CarSensorManager.SENSOR_TYPE_CAR_SPEED
     81         };
     82         // Expecting to receive at least 10 events within 150ms.
     83         final int EVENT_INTERVAL_MS = 1; //
     84         final int EXPECTED_EVENTS_PER_PROPERTY = 1000;
     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
    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
    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 = 2500;
    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 = 3;
    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 
    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 
    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             try {
    237                 mgr.setFloatProperty(cfg.getPropertyId(), areaId, curValue);
    238                 actualValue = mgr.getFloatProperty(cfg.getPropertyId(), areaId);
    239             } catch (CarNotConnectedException e) {
    240                 throw new RuntimeException(e);
    241             }
    242             assertEquals(curValue, actualValue, 0.001);
    243             curValue += 0.5;
    244             if (curValue > maxValue) {
    245                 curValue = minValue;
    246             }
    247 
    248             counter.countDown();
    249         }
    250     }
    251 
    252     @Nullable
    253     private <T> CarPropertyConfig<T> findHvacPropConfig(Class<T> clazz, int hvacPropId,
    254                 CarHvacManager mgr)
    255             throws CarNotConnectedException {
    256         for (CarPropertyConfig<?> cfg : mgr.getPropertyList()) {
    257             if (cfg.getPropertyId() == hvacPropId) {
    258                 return (CarPropertyConfig<T>) cfg;
    259             }
    260         }
    261         return null;
    262     }
    263 
    264     private void verifyEventsFromSingleProperty(int mgrPropId, int halPropId) throws Exception {
    265         // Expecting to receive at least 10 events within 150ms.
    266         final int EXPECTED_EVENTS = 10;
    267         final int EXPECTED_TIME_DURATION_MS = 150;
    268         final float INITIAL_VALUE = 1000;
    269         final float INCREMENT = 1.0f;
    270 
    271         CarSensorManager mgr = (CarSensorManager) mCar.getCarManager(Car.SENSOR_SERVICE);
    272         assertNotNull(mgr);
    273         assertTrue(mgr.isSensorSupported(mgrPropId));
    274 
    275         VhalEventGenerator generator = new LinearVhalEventGenerator(mVehicle)
    276                 .setProp(halPropId)
    277                 .setIntervalMs(10)
    278                 .setInitialValue(INITIAL_VALUE)
    279                 .setIncrement(INCREMENT)
    280                 .setDispersion(100);
    281 
    282         generator.start();
    283 
    284         CountDownLatch latch = new CountDownLatch(EXPECTED_EVENTS);
    285         OnSensorChangedListener listener = event -> latch.countDown();
    286 
    287         mgr.registerListener(listener, mgrPropId, CarSensorManager.SENSOR_RATE_FASTEST);
    288         try {
    289             assertTrue(latch.await(EXPECTED_TIME_DURATION_MS, TimeUnit.MILLISECONDS));
    290         } finally {
    291             generator.stop();
    292             mgr.unregisterListener(listener);
    293         }
    294     }
    295 
    296 
    297     private static boolean awaitCountDownLatches(CountDownLatch[] latches, long timeoutMs)
    298             throws InterruptedException {
    299         long start = SystemClock.elapsedRealtime();
    300         for (int i = 0; i < latches.length; i++) {
    301             long timeLeft = timeoutMs - (SystemClock.elapsedRealtime() - start);
    302             if (!latches[i].await(timeLeft, TimeUnit.MILLISECONDS)) {
    303                 return false;
    304             }
    305         }
    306 
    307         return true;
    308     }
    309 }
    310