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