Dump VMS HAL client stats obtained by reading a vendor property.
Bug: 139153868
Test: Added new unit tests to VmsHalServiceTest
Test: atest AndroidCarApiTest CarServiceTest CarServiceUnitTest
Change-Id: I97fcd730c7433ea6753719207d967e119bcc6a4f
diff --git a/service/res/values/config.xml b/service/res/values/config.xml
index 95383dc..370fe5d 100644
--- a/service/res/values/config.xml
+++ b/service/res/values/config.xml
@@ -78,6 +78,8 @@
</string-array>
<!-- Default home activity -->
<string name="defaultHomeActivity" translatable="false"><!--com.your.package/com.your.package.Activity--></string>
+ <!-- The vendor-defined HAL property used to collect VMS client metrics. Disabled by default.-->
+ <integer name="vmsHalClientMetricsProperty">0</integer>
<!-- The com.android.car.vms.VmsClientManager will bind to this list of clients running as system user -->
<string-array translatable="false" name="vmsPublisherSystemClients">
</string-array>
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index 5c170d9..8527208 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -118,7 +118,7 @@
CanBusErrorNotifier errorNotifier, String vehicleInterfaceName) {
mContext = serviceContext;
mSystemInterface = systemInterface;
- mHal = new VehicleHal(vehicle);
+ mHal = new VehicleHal(serviceContext, vehicle);
mVehicleInterfaceName = vehicleInterfaceName;
mUserManagerHelper = new CarUserManagerHelper(serviceContext);
final Resources res = mContext.getResources();
@@ -473,6 +473,8 @@
} else if ("--metrics".equals(args[0])) {
writer.println("*Dump car service metrics*");
dumpAllServices(writer, true);
+ } else if ("--vms-hal".equals(args[0])) {
+ mHal.getVmsHal().dumpMetrics(fd);
} else if (Build.IS_USERDEBUG || Build.IS_ENG) {
execShellCmd(args, writer);
} else {
diff --git a/service/src/com/android/car/hal/VehicleHal.java b/service/src/com/android/car/hal/VehicleHal.java
index 374ae7b..d85a357 100644
--- a/service/src/com/android/car/hal/VehicleHal.java
+++ b/service/src/com/android/car/hal/VehicleHal.java
@@ -23,6 +23,7 @@
import static java.lang.Integer.toHexString;
import android.annotation.CheckResult;
+import android.content.Context;
import android.hardware.automotive.vehicle.V2_0.IVehicle;
import android.hardware.automotive.vehicle.V2_0.IVehicleCallback;
import android.hardware.automotive.vehicle.V2_0.SubscribeFlags;
@@ -89,14 +90,14 @@
// Used by injectVHALEvent for testing purposes. Delimiter for an array of data
private static final String DATA_DELIMITER = ",";
- public VehicleHal(IVehicle vehicle) {
+ public VehicleHal(Context context, IVehicle vehicle) {
mHandlerThread = new HandlerThread("VEHICLE-HAL");
mHandlerThread.start();
// passing this should be safe as long as it is just kept and not used in constructor
mPowerHal = new PowerHalService(this);
mPropertyHal = new PropertyHalService(this);
mInputHal = new InputHalService(this);
- mVmsHal = new VmsHalService(this);
+ mVmsHal = new VmsHalService(context, this);
mDiagnosticHal = new DiagnosticHalService(this);
mAllServices.addAll(Arrays.asList(mPowerHal,
mInputHal,
diff --git a/service/src/com/android/car/hal/VmsHalService.java b/service/src/com/android/car/hal/VmsHalService.java
index fcf717f..40982c8 100644
--- a/service/src/com/android/car/hal/VmsHalService.java
+++ b/service/src/com/android/car/hal/VmsHalService.java
@@ -31,9 +31,11 @@
import android.car.vms.VmsLayersOffering;
import android.car.vms.VmsOperationRecorder;
import android.car.vms.VmsSubscriptionState;
+import android.content.Context;
import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropertyGroup;
import android.hardware.automotive.vehicle.V2_0.VmsBaseMessageIntegerValuesIndex;
import android.hardware.automotive.vehicle.V2_0.VmsMessageType;
import android.hardware.automotive.vehicle.V2_0.VmsMessageWithLayerAndPublisherIdIntegerValuesIndex;
@@ -55,6 +57,9 @@
import com.android.car.CarLog;
import com.android.car.vms.VmsClientManager;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
@@ -81,6 +86,7 @@
private final VehicleHal mVehicleHal;
private final int mCoreId;
private final MessageQueue mMessageQueue;
+ private final int mClientMetricsProperty;
private volatile boolean mIsSupported = false;
private VmsClientManager mClientManager;
@@ -192,15 +198,33 @@
/**
* Constructor used by {@link VehicleHal}
*/
- VmsHalService(VehicleHal vehicleHal) {
- this(vehicleHal, SystemClock::uptimeMillis);
+ VmsHalService(Context context, VehicleHal vehicleHal) {
+ this(context, vehicleHal, SystemClock::uptimeMillis);
}
@VisibleForTesting
- VmsHalService(VehicleHal vehicleHal, Supplier<Long> getCoreId) {
+ VmsHalService(Context context, VehicleHal vehicleHal, Supplier<Long> getCoreId) {
mVehicleHal = vehicleHal;
mCoreId = (int) (getCoreId.get() % Integer.MAX_VALUE);
mMessageQueue = new MessageQueue();
+ mClientMetricsProperty = getClientMetricsProperty(context);
+ }
+
+ private static int getClientMetricsProperty(Context context) {
+ int propId = context.getResources().getInteger(
+ com.android.car.R.integer.vmsHalClientMetricsProperty);
+ if (propId == 0) {
+ Log.i(TAG, "Metrics collection disabled");
+ return 0;
+ }
+ if ((propId & VehiclePropertyGroup.MASK) != VehiclePropertyGroup.VENDOR) {
+ Log.w(TAG, String.format("Metrics collection disabled, non-vendor property: 0x%x",
+ propId));
+ return 0;
+ }
+
+ Log.i(TAG, String.format("Metrics collection property: 0x%x", propId));
+ return propId;
}
/**
@@ -289,6 +313,37 @@
}
/**
+ * Dumps HAL client metrics obtained by reading the VMS HAL property.
+ *
+ * @param fd Dumpsys file descriptor to write client metrics to.
+ */
+ public void dumpMetrics(FileDescriptor fd) {
+ if (mClientMetricsProperty == 0) {
+ Log.w(TAG, "Metrics collection is disabled");
+ return;
+ }
+
+ VehiclePropValue vehicleProp = null;
+ try {
+ vehicleProp = mVehicleHal.get(mClientMetricsProperty);
+ } catch (PropertyTimeoutException e) {
+ Log.e(TAG, "Timeout while reading metrics from client");
+ }
+ if (vehicleProp == null) {
+ if (DBG) Log.d(TAG, "Metrics unavailable");
+ return;
+ }
+
+ FileOutputStream fout = new FileOutputStream(fd);
+ try {
+ fout.write(toByteArray(vehicleProp.value.bytes));
+ fout.flush();
+ } catch (IOException e) {
+ Log.e(TAG, "Error writing metrics to output stream");
+ }
+ }
+
+ /**
* Consumes/produces HAL messages.
*
* The format of these messages is defined in:
diff --git a/tests/carservice_unit_test/src/com/android/car/hal/VmsHalServiceTest.java b/tests/carservice_unit_test/src/com/android/car/hal/VmsHalServiceTest.java
index 2ef469c..7571867 100644
--- a/tests/carservice_unit_test/src/com/android/car/hal/VmsHalServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/hal/VmsHalServiceTest.java
@@ -15,10 +15,13 @@
*/
package com.android.car.hal;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.car.vms.IVmsPublisherClient;
@@ -31,15 +34,20 @@
import android.car.vms.VmsLayerDependency;
import android.car.vms.VmsLayersOffering;
import android.car.vms.VmsSubscriptionState;
+import android.content.Context;
+import android.content.res.Resources;
import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropertyGroup;
import android.hardware.automotive.vehicle.V2_0.VmsMessageType;
import android.os.Binder;
import android.os.IBinder;
import androidx.test.filters.RequiresDevice;
+import com.android.car.R;
+import com.android.car.test.utils.TemporaryFile;
import com.android.car.vms.VmsClientManager;
import org.junit.Before;
@@ -52,6 +60,9 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
@@ -73,6 +84,10 @@
@Rule
public MockitoRule mockito = MockitoJUnit.rule();
@Mock
+ private Context mContext;
+ @Mock
+ private Resources mResources;
+ @Mock
private VehicleHal mVehicleHal;
@Mock
private VmsClientManager mClientManager;
@@ -88,7 +103,8 @@
@Before
public void setUp() throws Exception {
- mHalService = new VmsHalService(mVehicleHal, () -> (long) CORE_ID);
+ when(mContext.getResources()).thenReturn(mResources);
+ mHalService = new VmsHalService(mContext, mVehicleHal, () -> (long) CORE_ID);
mHalService.setClientManager(mClientManager);
mHalService.setVmsSubscriberService(mSubscriberService);
@@ -148,7 +164,8 @@
@Test
public void testCoreId_IntegerOverflow() throws Exception {
- mHalService = new VmsHalService(mVehicleHal, () -> (long) Integer.MAX_VALUE + CORE_ID);
+ mHalService = new VmsHalService(mContext, mVehicleHal,
+ () -> (long) Integer.MAX_VALUE + CORE_ID);
VehiclePropConfig propConfig = new VehiclePropConfig();
propConfig.prop = VehicleProperty.VEHICLE_MAP_SERVICE;
@@ -944,6 +961,80 @@
verify(mVehicleHal).set(response);
}
+ @Test
+ public void testDumpMetrics_DefaultConfig() {
+ mHalService.dumpMetrics(new FileDescriptor());
+ verifyZeroInteractions(mVehicleHal);
+ }
+
+ @Test
+ public void testDumpMetrics_NonVendorProperty() throws Exception {
+ VehiclePropValue vehicleProp = new VehiclePropValue();
+ vehicleProp.value.bytes.addAll(PAYLOAD_AS_LIST);
+ when(mVehicleHal.get(anyInt())).thenReturn(vehicleProp);
+
+ when(mResources.getInteger(
+ R.integer.vmsHalClientMetricsProperty)).thenReturn(
+ VehicleProperty.VEHICLE_MAP_SERVICE);
+ setUp();
+
+ mHalService.dumpMetrics(new FileDescriptor());
+ verifyZeroInteractions(mVehicleHal);
+ }
+
+ @Test
+ public void testDumpMetrics_VendorProperty() throws Exception {
+ int metricsPropertyId = VehiclePropertyGroup.VENDOR | 1;
+ when(mResources.getInteger(
+ R.integer.vmsHalClientMetricsProperty)).thenReturn(
+ metricsPropertyId);
+ setUp();
+
+ VehiclePropValue metricsProperty = new VehiclePropValue();
+ metricsProperty.value.bytes.addAll(PAYLOAD_AS_LIST);
+ when(mVehicleHal.get(metricsPropertyId)).thenReturn(metricsProperty);
+
+ try (TemporaryFile dumpsysFile = new TemporaryFile("VmsHalServiceTest")) {
+ FileOutputStream outputStream = new FileOutputStream(dumpsysFile.getFile());
+ mHalService.dumpMetrics(outputStream.getFD());
+
+ verify(mVehicleHal).get(metricsPropertyId);
+ FileInputStream inputStream = new FileInputStream(dumpsysFile.getFile());
+ byte[] dumpsysOutput = new byte[PAYLOAD.length];
+ assertEquals(PAYLOAD.length, inputStream.read(dumpsysOutput));
+ assertArrayEquals(PAYLOAD, dumpsysOutput);
+ }
+ }
+
+ @Test
+ public void testDumpMetrics_VendorProperty_Timeout() throws Exception {
+ int metricsPropertyId = VehiclePropertyGroup.VENDOR | 1;
+ when(mResources.getInteger(
+ R.integer.vmsHalClientMetricsProperty)).thenReturn(
+ metricsPropertyId);
+ setUp();
+
+ when(mVehicleHal.get(metricsPropertyId))
+ .thenThrow(new PropertyTimeoutException(metricsPropertyId));
+
+ mHalService.dumpMetrics(new FileDescriptor());
+ verify(mVehicleHal).get(metricsPropertyId);
+ }
+
+ @Test
+ public void testDumpMetrics_VendorProperty_Unavailable() throws Exception {
+ int metricsPropertyId = VehiclePropertyGroup.VENDOR | 1;
+ when(mResources.getInteger(
+ R.integer.vmsHalClientMetricsProperty)).thenReturn(
+ metricsPropertyId);
+ setUp();
+
+ when(mVehicleHal.get(metricsPropertyId)).thenReturn(null);
+
+ mHalService.dumpMetrics(new FileDescriptor());
+ verify(mVehicleHal).get(metricsPropertyId);
+ }
+
private static VehiclePropValue createHalMessage(Integer... message) {
VehiclePropValue result = new VehiclePropValue();
result.prop = VehicleProperty.VEHICLE_MAP_SERVICE;