MIDI Metrics: Add MIDI Metrics in Java

This CL adds new properties to MediaMetrics.java and sets them in
MidiService.java whenever a MIDI connection is disconnected or a
MIDI device is closed.

Bug: 248607712
Test: adb shell dumpsys media.metrics
Test: statsd_testdrive
Change-Id: I6204ce2f7d55065a729aad34d44604af01d50b31
This commit is contained in:
Robert Wu
2022-11-02 22:54:35 +00:00
parent 226fc4135e
commit 246e2a2fcd
6 changed files with 172 additions and 4 deletions

View File

@@ -49,10 +49,11 @@ public class MediaMetrics {
public static final String AUDIO_FOCUS = AUDIO + SEPARATOR + "focus";
public static final String AUDIO_FORCE_USE = AUDIO + SEPARATOR + "forceUse";
public static final String AUDIO_MIC = AUDIO + SEPARATOR + "mic";
public static final String AUDIO_MIDI = AUDIO + SEPARATOR + "midi";
public static final String AUDIO_MODE = AUDIO + SEPARATOR + "mode";
public static final String AUDIO_SERVICE = AUDIO + SEPARATOR + "service";
public static final String AUDIO_VOLUME = AUDIO + SEPARATOR + "volume";
public static final String AUDIO_VOLUME_EVENT = AUDIO_VOLUME + SEPARATOR + "event";
public static final String AUDIO_MODE = AUDIO + SEPARATOR + "mode";
public static final String METRICS_MANAGER = "metrics" + SEPARATOR + "manager";
}
@@ -90,15 +91,27 @@ public class MediaMetrics {
// The client name
public static final Key<String> CLIENT_NAME = createKey("clientName", String.class);
public static final Key<Integer> CLOSED_COUNT =
createKey("closedCount", Integer.class); // MIDI
// The device type
public static final Key<Integer> DELAY_MS = createKey("delayMs", Integer.class);
// The device type
public static final Key<String> DEVICE = createKey("device", String.class);
// Whether the device is disconnected. This is either "true" or "false"
public static final Key<String> DEVICE_DISCONNECTED =
createKey("deviceDisconnected", String.class); // MIDI
// The ID of the device
public static final Key<Integer> DEVICE_ID =
createKey("deviceId", Integer.class); // MIDI
// For volume changes, up or down
public static final Key<String> DIRECTION = createKey("direction", String.class);
public static final Key<Long> DURATION_NS =
createKey("durationNs", Long.class); // MIDI
// A reason for early return or error
public static final Key<String> EARLY_RETURN =
createKey("earlyReturn", String.class);
@@ -128,11 +141,17 @@ public class MediaMetrics {
// Generally string "true" or "false"
public static final Key<String> HAS_HEAD_TRACKER =
createKey("hasHeadTracker", String.class); // spatializer
public static final Key<Integer> HARDWARE_TYPE =
createKey("hardwareType", Integer.class); // MIDI
// Generally string "true" or "false"
public static final Key<String> HEAD_TRACKER_ENABLED =
createKey("headTrackerEnabled", String.class); // spatializer
public static final Key<Integer> INDEX = createKey("index", Integer.class); // volume
public static final Key<Integer> INPUT_PORT_COUNT =
createKey("inputPortCount", Integer.class); // MIDI
// Either "true" or "false"
public static final Key<String> IS_SHARED = createKey("isShared", String.class); // MIDI
public static final Key<String> LOG_SESSION_ID = createKey("logSessionId", String.class);
public static final Key<Integer> MAX_INDEX = createKey("maxIndex", Integer.class); // vol
public static final Key<Integer> MIN_INDEX = createKey("minIndex", Integer.class); // vol
@@ -149,6 +168,11 @@ public class MediaMetrics {
public static final Key<Integer> OBSERVERS =
createKey("observers", Integer.class);
public static final Key<Integer> OPENED_COUNT =
createKey("openedCount", Integer.class); // MIDI
public static final Key<Integer> OUTPUT_PORT_COUNT =
createKey("outputPortCount", Integer.class); // MIDI
public static final Key<String> REQUEST =
createKey("request", String.class);
@@ -163,6 +187,18 @@ public class MediaMetrics {
public static final Key<String> STATE = createKey("state", String.class);
public static final Key<Integer> STATUS = createKey("status", Integer.class);
public static final Key<String> STREAM_TYPE = createKey("streamType", String.class);
// The following MIDI string is generally either "true" or "false"
public static final Key<String> SUPPORTS_MIDI_UMP =
createKey("supportsMidiUmp", String.class); // Universal MIDI Packets
public static final Key<Integer> TOTAL_INPUT_BYTES =
createKey("totalInputBytes", Integer.class); // MIDI
public static final Key<Integer> TOTAL_OUTPUT_BYTES =
createKey("totalOutputBytes", Integer.class); // MIDI
// The following MIDI string is generally either "true" or "false"
public static final Key<String> USING_ALSA = createKey("usingAlsa", String.class);
}
/**

View File

@@ -60,4 +60,7 @@ interface IMidiManager
// used by MIDI devices to report their status
// the token is used by MidiService for death notification
void setDeviceStatus(in IMidiDeviceServer server, in MidiDeviceStatus status);
// Updates the number of bytes sent and received
void updateTotalBytes(in IMidiDeviceServer server, int inputBytes, int outputBytes);
}

View File

@@ -36,6 +36,7 @@ import java.io.FileDescriptor;
import java.io.IOException;
import java.util.HashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Internal class used for providing an implementation for a MIDI device.
@@ -79,6 +80,9 @@ public final class MidiDeviceServer implements Closeable {
private final HashMap<MidiInputPort, PortClient> mInputPortClients =
new HashMap<MidiInputPort, PortClient>();
private AtomicInteger mTotalInputBytes = new AtomicInteger();
private AtomicInteger mTotalOutputBytes = new AtomicInteger();
public interface Callback {
/**
* Called to notify when an our device status has changed
@@ -133,6 +137,8 @@ public final class MidiDeviceServer implements Closeable {
int portNumber = mOutputPort.getPortNumber();
mInputPortOutputPorts[portNumber] = null;
mInputPortOpen[portNumber] = false;
mTotalOutputBytes.addAndGet(mOutputPort.pullTotalBytesCount());
updateTotalBytes();
updateDeviceStatus();
}
IoUtils.closeQuietly(mOutputPort);
@@ -156,6 +162,8 @@ public final class MidiDeviceServer implements Closeable {
dispatcher.getSender().disconnect(mInputPort);
int openCount = dispatcher.getReceiverCount();
mOutputPortOpenCount[portNumber] = openCount;
mTotalInputBytes.addAndGet(mInputPort.pullTotalBytesCount());
updateTotalBytes();
updateDeviceStatus();
}
@@ -405,18 +413,20 @@ public final class MidiDeviceServer implements Closeable {
synchronized (mGuard) {
if (mIsClosed) return;
mGuard.close();
for (int i = 0; i < mInputPortCount; i++) {
MidiOutputPort outputPort = mInputPortOutputPorts[i];
if (outputPort != null) {
mTotalOutputBytes.addAndGet(outputPort.pullTotalBytesCount());
IoUtils.closeQuietly(outputPort);
mInputPortOutputPorts[i] = null;
}
}
for (MidiInputPort inputPort : mInputPorts) {
mTotalInputBytes.addAndGet(inputPort.pullTotalBytesCount());
IoUtils.closeQuietly(inputPort);
}
mInputPorts.clear();
updateTotalBytes();
try {
mMidiManager.unregisterDeviceServer(mServer);
} catch (RemoteException e) {
@@ -449,4 +459,12 @@ public final class MidiDeviceServer implements Closeable {
System.arraycopy(mOutputPortDispatchers, 0, receivers, 0, mOutputPortCount);
return receivers;
}
private void updateTotalBytes() {
try {
mMidiManager.updateTotalBytes(mServer, mTotalInputBytes.get(), mTotalOutputBytes.get());
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in updateTotalBytes");
}
}
}

View File

@@ -28,6 +28,7 @@ import java.io.Closeable;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger;
/**
* This class is used for sending data to a port on a MIDI device
@@ -43,6 +44,7 @@ public final class MidiInputPort extends MidiReceiver implements Closeable {
private final CloseGuard mGuard = CloseGuard.get();
private boolean mIsClosed;
private AtomicInteger mTotalBytes = new AtomicInteger();
// buffer to use for sending data out our output stream
private final byte[] mBuffer = new byte[MidiPortImpl.MAX_PACKET_SIZE];
@@ -87,6 +89,7 @@ public final class MidiInputPort extends MidiReceiver implements Closeable {
}
int length = MidiPortImpl.packData(msg, offset, count, timestamp, mBuffer);
mOutputStream.write(mBuffer, 0, length);
mTotalBytes.addAndGet(length);
}
}
@@ -170,4 +173,12 @@ public final class MidiInputPort extends MidiReceiver implements Closeable {
super.finalize();
}
}
/**
* Pulls total number of bytes and sets to zero. This allows multiple callers.
* @hide
*/
public int pullTotalBytesCount() {
return mTotalBytes.getAndSet(0);
}
}

View File

@@ -31,6 +31,7 @@ import java.io.Closeable;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger;
/**
* This class is used for receiving data from a port on a MIDI device
@@ -46,6 +47,7 @@ public final class MidiOutputPort extends MidiSender implements Closeable {
private final CloseGuard mGuard = CloseGuard.get();
private boolean mIsClosed;
private AtomicInteger mTotalBytes = new AtomicInteger();
// This thread reads MIDI events from a socket and distributes them to the list of
// MidiReceivers attached to this device.
@@ -83,6 +85,7 @@ public final class MidiOutputPort extends MidiSender implements Closeable {
Log.e(TAG, "Unknown packet type " + packetType);
break;
}
mTotalBytes.addAndGet(count);
} // while (true)
} catch (IOException e) {
// FIXME report I/O failure?
@@ -163,4 +166,12 @@ public final class MidiOutputPort extends MidiSender implements Closeable {
super.finalize();
}
}
/**
* Pulls total number of bytes and sets to zero. This allows multiple callers.
* @hide
*/
public int pullTotalBytesCount() {
return mTotalBytes.getAndSet(0);
}
}

View File

@@ -23,7 +23,6 @@ import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
// import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
@@ -31,6 +30,7 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.XmlResourceParser;
import android.media.MediaMetrics;
import android.media.midi.IBluetoothMidiService;
import android.media.midi.IMidiDeviceListener;
import android.media.midi.IMidiDeviceOpenCallback;
@@ -63,12 +63,16 @@ import org.xmlpull.v1.XmlPullParser;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
// NOTE about locking order:
// if there is a path that syncs on BOTH mDevicesByInfo AND mDeviceConnections,
@@ -359,6 +363,17 @@ public class MidiService extends IMidiManager.Stub {
private final ArrayList<DeviceConnection> mDeviceConnections
= new ArrayList<DeviceConnection>();
// Keep track of number of added and removed collections for logging
private AtomicInteger mDeviceConnectionsAdded = new AtomicInteger();
private AtomicInteger mDeviceConnectionsRemoved = new AtomicInteger();
// Keep track of total time with at least one active connection
private AtomicLong mTotalTimeConnectedNs = new AtomicLong();
private Instant mPreviousCounterInstant = null;
private AtomicInteger mTotalInputBytes = new AtomicInteger();
private AtomicInteger mTotalOutputBytes = new AtomicInteger();
public Device(IMidiDeviceServer server, MidiDeviceInfo deviceInfo,
ServiceInfo serviceInfo, int uid) {
mDeviceInfo = deviceInfo;
@@ -460,6 +475,11 @@ public class MidiService extends IMidiManager.Stub {
public void addDeviceConnection(DeviceConnection connection) {
Log.d(TAG, "addDeviceConnection() [A] connection:" + connection);
synchronized (mDeviceConnections) {
mDeviceConnectionsAdded.incrementAndGet();
if (mPreviousCounterInstant == null) {
mPreviousCounterInstant = Instant.now();
}
Log.d(TAG, " mServer:" + mServer);
if (mServer != null) {
Log.i(TAG, "++++ A");
@@ -533,6 +553,20 @@ public class MidiService extends IMidiManager.Stub {
public void removeDeviceConnection(DeviceConnection connection) {
synchronized (mDevicesByInfo) {
synchronized (mDeviceConnections) {
int numRemovedConnections = mDeviceConnectionsRemoved.incrementAndGet();
if (mPreviousCounterInstant != null) {
mTotalTimeConnectedNs.addAndGet(Duration.between(
mPreviousCounterInstant, Instant.now()).toNanos());
}
// Stop the clock if all devices have been removed.
// Otherwise, start the clock from the current instant.
if (numRemovedConnections >= mDeviceConnectionsAdded.get()) {
mPreviousCounterInstant = null;
} else {
mPreviousCounterInstant = Instant.now();
}
logMetrics(false /* isDeviceDisconnected */);
mDeviceConnections.remove(connection);
if (connection.getDevice().getDeviceInfo().getType()
@@ -569,6 +603,16 @@ public class MidiService extends IMidiManager.Stub {
connection.getClient().removeDeviceConnection(connection);
}
mDeviceConnections.clear();
// If the timer is still going, some clients have not closed the connection yet.
if (mPreviousCounterInstant != null) {
Instant currentInstant = Instant.now();
mTotalTimeConnectedNs.addAndGet(Duration.between(
mPreviousCounterInstant, currentInstant).toNanos());
mPreviousCounterInstant = currentInstant;
}
logMetrics(true /* isDeviceDisconnected */);
}
setDeviceServer(null);
@@ -585,6 +629,35 @@ public class MidiService extends IMidiManager.Stub {
}
}
private void logMetrics(boolean isDeviceDisconnected) {
// Only log metrics if the device was used in a connection
int numDeviceConnectionAdded = mDeviceConnectionsAdded.get();
if (mDeviceInfo != null && numDeviceConnectionAdded > 0) {
new MediaMetrics.Item(MediaMetrics.Name.AUDIO_MIDI)
.setUid(mUid)
.set(MediaMetrics.Property.DEVICE_ID, mDeviceInfo.getId())
.set(MediaMetrics.Property.INPUT_PORT_COUNT, mDeviceInfo.getInputPortCount())
.set(MediaMetrics.Property.OUTPUT_PORT_COUNT,
mDeviceInfo.getOutputPortCount())
.set(MediaMetrics.Property.HARDWARE_TYPE, mDeviceInfo.getType())
.set(MediaMetrics.Property.DURATION_NS, mTotalTimeConnectedNs.get())
.set(MediaMetrics.Property.OPENED_COUNT, numDeviceConnectionAdded)
.set(MediaMetrics.Property.CLOSED_COUNT, mDeviceConnectionsRemoved.get())
.set(MediaMetrics.Property.DEVICE_DISCONNECTED,
isDeviceDisconnected ? "true" : "false")
.set(MediaMetrics.Property.IS_SHARED,
!mDeviceInfo.isPrivate() ? "true" : "false")
.set(MediaMetrics.Property.SUPPORTS_MIDI_UMP, mDeviceInfo.getDefaultProtocol()
!= MidiDeviceInfo.PROTOCOL_UNKNOWN ? "true" : "false")
.set(MediaMetrics.Property.USING_ALSA, mDeviceInfo.getProperties().get(
MidiDeviceInfo.PROPERTY_ALSA_CARD) != null ? "true" : "false")
.set(MediaMetrics.Property.EVENT, "deviceClosed")
.set(MediaMetrics.Property.TOTAL_INPUT_BYTES, mTotalInputBytes.get())
.set(MediaMetrics.Property.TOTAL_OUTPUT_BYTES, mTotalOutputBytes.get())
.record();
}
}
@Override
public void binderDied() {
Log.d(TAG, "Device died: " + this);
@@ -593,6 +666,11 @@ public class MidiService extends IMidiManager.Stub {
}
}
public void updateTotalBytes(int totalInputBytes, int totalOutputBytes) {
mTotalInputBytes.set(totalInputBytes);
mTotalOutputBytes.set(totalOutputBytes);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("Device Info: ");
@@ -1372,6 +1450,17 @@ public class MidiService extends IMidiManager.Stub {
}
}
@Override
public void updateTotalBytes(IMidiDeviceServer server, int totalInputBytes,
int totalOutputBytes) {
synchronized (mDevicesByInfo) {
Device device = mDevicesByServer.get(server.asBinder());
if (device != null) {
device.updateTotalBytes(totalInputBytes, totalOutputBytes);
}
}
}
@Override
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, writer)) return;