From 4de1e16570bba8b78d3619ff4d37b79d504a1679 Mon Sep 17 00:00:00 2001 From: Igor Murashkin Date: Mon, 26 Nov 2018 10:33:17 -0800 Subject: [PATCH] services: Add iorap forwarding service Add a new service to system_server. It purely forwards data from internal APIs to iorapd over binder, it is not a binder service itself. Currently forwards ActivityMetricsLaunchObserver APIs from ActivityTaskManagerInternal. Bug: 72170747 Change-Id: Ic4fa283df1c16660099030c74a0039ef24866819 --- services/Android.bp | 1 + .../java/com/android/server/SystemServer.java | 6 +- services/startop/Android.bp | 24 ++ startop/iorap/Android.bp | 4 +- .../android/startop/iorap/AppLaunchEvent.java | 368 ++++++++++++++++++ .../startop/iorap/IorapForwardingService.java | 216 ++++++++++ .../android/startop/iorap/RequestId.java | 2 +- startop/iorap/tests/Android.bp | 11 +- 8 files changed, 627 insertions(+), 5 deletions(-) create mode 100644 services/startop/Android.bp create mode 100644 startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java create mode 100644 startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java diff --git a/services/Android.bp b/services/Android.bp index 58a09977596f3..01734f4051d33 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -28,6 +28,7 @@ java_library { "services.net", "services.print", "services.restrictions", + "services.startop", "services.usage", "services.usb", "services.voiceinteraction", diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index be09aea650737..4326c39c43a29 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -145,6 +145,7 @@ import com.android.server.wm.ActivityTaskManagerService; import com.android.server.wm.WindowManagerGlobalLock; import com.android.server.wm.WindowManagerService; +import com.google.android.startop.iorap.IorapForwardingService; import dalvik.system.VMRuntime; @@ -1007,10 +1008,13 @@ private void startOtherServices() { mSystemServiceManager.startService(PinnerService.class); traceEnd(); + traceBeginAndSlog("IorapForwardingService"); + mSystemServiceManager.startService(IorapForwardingService.class); + traceEnd(); + traceBeginAndSlog("SignedConfigService"); SignedConfigService.registerUpdateReceiver(mSystemContext); traceEnd(); - } catch (RuntimeException e) { Slog.e("System", "******************************************"); Slog.e("System", "************ Failure starting core service", e); diff --git a/services/startop/Android.bp b/services/startop/Android.bp new file mode 100644 index 0000000000000..093b4ec66ddfe --- /dev/null +++ b/services/startop/Android.bp @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +java_library_static { + name: "services.startop", + + static_libs: [ + // frameworks/base/startop/iorap + "services.startop.iorap", + ], +} diff --git a/startop/iorap/Android.bp b/startop/iorap/Android.bp index b3b09001e7eec..59a80fbae792d 100644 --- a/startop/iorap/Android.bp +++ b/startop/iorap/Android.bp @@ -13,7 +13,7 @@ // limitations under the License. java_library_static { - name: "libiorap-java", + name: "services.startop.iorap", aidl: { include_dirs: [ @@ -21,6 +21,8 @@ java_library_static { ], }, + libs: ["services.core"], + srcs: [ ":iorap-aidl", "**/*.java", diff --git a/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java b/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java new file mode 100644 index 0000000000000..c2e4581285fd3 --- /dev/null +++ b/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java @@ -0,0 +1,368 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.startop.iorap; + +import android.annotation.LongDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ComponentName; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.os.Parcel; +import android.os.Parcelable; + +// TODO: fix this. either move this class into system server or add a dependency on +// these wm classes to libiorap-java and libiorap-java-tests (somehow). +import com.android.server.wm.ActivityMetricsLaunchObserver; +import com.android.server.wm.ActivityMetricsLaunchObserver.ActivityRecordProto; +import com.android.server.wm.ActivityMetricsLaunchObserver.Temperature; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.InvocationTargetException; +import java.util.Objects; + +/** + * Provide a hint to iorapd that an app launch sequence has transitioned state.

+ * + * Knowledge of when an activity starts/stops can be used by iorapd to increase system + * performance (e.g. by launching perfetto tracing to record an io profile, or by + * playing back an ioprofile via readahead) over the long run.

+ * + * /@see com.google.android.startop.iorap.IIorap#onAppLaunchEvent

+ * @see com.android.server.wm.ActivityMetricsLaunchObserver + * ActivityMetricsLaunchObserver for the possible event states. + * @hide + */ +public abstract class AppLaunchEvent implements Parcelable { + @LongDef + @Retention(RetentionPolicy.SOURCE) + public @interface SequenceId {} + + public final @SequenceId + long sequenceId; + + protected AppLaunchEvent(@SequenceId long sequenceId) { + this.sequenceId = sequenceId; + } + + @Override + public boolean equals(Object other) { + if (other instanceof AppLaunchEvent) { + return equals((AppLaunchEvent) other); + } + return false; + } + + protected boolean equals(AppLaunchEvent other) { + return sequenceId == other.sequenceId; + } + + + @Override + public String toString() { + return getClass().getSimpleName() + + "{" + "sequenceId=" + Long.toString(sequenceId) + + toStringBody() + "}"; + } + + protected String toStringBody() { return ""; }; + + // List of possible variants: + + public static final class IntentStarted extends AppLaunchEvent { + @NonNull + public final Intent intent; + + public IntentStarted(@SequenceId long sequenceId, Intent intent) { + super(sequenceId); + this.intent = intent; + + Objects.requireNonNull(intent, "intent"); + } + + @Override + public boolean equals(Object other) { + if (other instanceof IntentStarted) { + return intent.equals(((IntentStarted)other).intent) && + super.equals(other); + } + return false; + } + + @Override + protected String toStringBody() { + return ", intent=" + intent.toString(); + } + + + @Override + protected void writeToParcelImpl(Parcel p, int flags) { + super.writeToParcelImpl(p, flags); + intent.writeToParcel(p, flags); + } + + IntentStarted(Parcel p) { + super(p); + intent = Intent.CREATOR.createFromParcel(p); + } + } + + public static final class IntentFailed extends AppLaunchEvent { + public IntentFailed(@SequenceId long sequenceId) { + super(sequenceId); + } + + @Override + public boolean equals(Object other) { + if (other instanceof IntentFailed) { + return super.equals(other); + } + return false; + } + + IntentFailed(Parcel p) { + super(p); + } + } + + public static abstract class BaseWithActivityRecordData extends AppLaunchEvent { + public final @NonNull + @ActivityRecordProto byte[] activityRecordSnapshot; + + protected BaseWithActivityRecordData(@SequenceId long sequenceId, + @NonNull @ActivityRecordProto byte[] snapshot) { + super(sequenceId); + activityRecordSnapshot = snapshot; + + Objects.requireNonNull(snapshot, "snapshot"); + } + + @Override + public boolean equals(Object other) { + if (other instanceof BaseWithActivityRecordData) { + return activityRecordSnapshot.equals( + ((BaseWithActivityRecordData)other).activityRecordSnapshot) && + super.equals(other); + } + return false; + } + + @Override + protected String toStringBody() { + return ", " + activityRecordSnapshot.toString(); + } + + @Override + protected void writeToParcelImpl(Parcel p, int flags) { + super.writeToParcelImpl(p, flags); + ActivityRecordProtoParcelable.write(p, activityRecordSnapshot, flags); + } + + BaseWithActivityRecordData(Parcel p) { + super(p); + activityRecordSnapshot = ActivityRecordProtoParcelable.create(p); + } + } + + public static final class ActivityLaunched extends BaseWithActivityRecordData { + public final @ActivityMetricsLaunchObserver.Temperature + int temperature; + + public ActivityLaunched(@SequenceId long sequenceId, + @NonNull @ActivityRecordProto byte[] snapshot, + @ActivityMetricsLaunchObserver.Temperature int temperature) { + super(sequenceId, snapshot); + this.temperature = temperature; + } + + @Override + public boolean equals(Object other) { + if (other instanceof ActivityLaunched) { + return temperature == ((ActivityLaunched)other).temperature && + super.equals(other); + } + return false; + } + + @Override + protected String toStringBody() { + return ", temperature=" + Integer.toString(temperature); + } + + @Override + protected void writeToParcelImpl(Parcel p, int flags) { + super.writeToParcelImpl(p, flags); + p.writeInt(temperature); + } + + ActivityLaunched(Parcel p) { + super(p); + temperature = p.readInt(); + } + } + + public static final class ActivityLaunchFinished extends BaseWithActivityRecordData { + public ActivityLaunchFinished(@SequenceId long sequenceId, + @NonNull @ActivityRecordProto byte[] snapshot) { + super(sequenceId, snapshot); + } + + @Override + public boolean equals(Object other) { + if (other instanceof ActivityLaunched) { + return super.equals(other); + } + return false; + } + } + + public static class ActivityLaunchCancelled extends AppLaunchEvent { + public final @Nullable + @ActivityRecordProto byte[] activityRecordSnapshot; + + public ActivityLaunchCancelled(@SequenceId long sequenceId, + @Nullable @ActivityRecordProto byte[] snapshot) { + super(sequenceId); + activityRecordSnapshot = snapshot; + } + + @Override + public boolean equals(Object other) { + if (other instanceof ActivityLaunchCancelled) { + return Objects.equals(activityRecordSnapshot, + ((ActivityLaunchCancelled)other).activityRecordSnapshot) && + super.equals(other); + } + return false; + } + + @Override + protected String toStringBody() { + return ", " + activityRecordSnapshot.toString(); + } + + @Override + protected void writeToParcelImpl(Parcel p, int flags) { + super.writeToParcelImpl(p, flags); + if (activityRecordSnapshot != null) { + p.writeBoolean(true); + ActivityRecordProtoParcelable.write(p, activityRecordSnapshot, flags); + } else { + p.writeBoolean(false); + } + } + + ActivityLaunchCancelled(Parcel p) { + super(p); + if (p.readBoolean()) { + activityRecordSnapshot = ActivityRecordProtoParcelable.create(p); + } else { + activityRecordSnapshot = null; + } + } + } + + @Override + public @ContentsFlags int describeContents() { return 0; } + + @Override + public void writeToParcel(Parcel p, @WriteFlags int flags) { + p.writeInt(getTypeIndex()); + + writeToParcelImpl(p, flags); + } + + + public static Creator CREATOR = + new Creator() { + @Override + public AppLaunchEvent createFromParcel(Parcel source) { + int typeIndex = source.readInt(); + + Class kls = getClassFromTypeIndex(typeIndex); + if (kls == null) { + throw new IllegalArgumentException("Invalid type index: " + typeIndex); + } + + try { + return (AppLaunchEvent) kls.getConstructor(Parcel.class).newInstance(source); + } catch (InstantiationException e) { + throw new AssertionError(e); + } catch (IllegalAccessException e) { + throw new AssertionError(e); + } catch (InvocationTargetException e) { + throw new AssertionError(e); + } catch (NoSuchMethodException e) { + throw new AssertionError(e); + } + } + + @Override + public AppLaunchEvent[] newArray(int size) { + return new AppLaunchEvent[0]; + } + }; + + protected void writeToParcelImpl(Parcel p, int flags) { + p.writeLong(sequenceId); + } + + protected AppLaunchEvent(Parcel p) { + sequenceId = p.readLong(); + } + + private int getTypeIndex() { + for (int i = 0; i < sTypes.length; ++i) { + if (sTypes[i].equals(this.getClass())) { + return i; + } + } + throw new AssertionError("sTypes did not include this type: " + this.getClass()); + } + + private static @Nullable Class getClassFromTypeIndex(int typeIndex) { + if (typeIndex >= 0 && typeIndex < sTypes.length) { + return sTypes[typeIndex]; + } + return null; + } + + // Index position matters: It is used to encode the specific type in parceling. + // Keep up-to-date with C++ side. + private static Class[] sTypes = new Class[] { + IntentStarted.class, + IntentFailed.class, + ActivityLaunched.class, + ActivityLaunchFinished.class, + ActivityLaunchCancelled.class, + }; + + // TODO: move to @ActivityRecordProto byte[] once we have unit tests. + public static class ActivityRecordProtoParcelable { + public static void write(Parcel p, @ActivityRecordProto byte[] activityRecordSnapshot, + int flags) { + p.writeByteArray(activityRecordSnapshot); + } + + public static @ActivityRecordProto byte[] create(Parcel p) { + byte[] data = p.createByteArray(); + + return data; + } + } +} diff --git a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java new file mode 100644 index 0000000000000..7fcad360b8fe1 --- /dev/null +++ b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.startop.iorap; +// TODO: rename to com.android.server.startop.iorap + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.os.IBinder; +import android.os.Parcel; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.LocalServices; +import com.android.server.SystemService; +import com.android.server.wm.ActivityMetricsLaunchObserver; +import com.android.server.wm.ActivityMetricsLaunchObserver.ActivityRecordProto; +import com.android.server.wm.ActivityMetricsLaunchObserver.Temperature; +import com.android.server.wm.ActivityMetricsLaunchObserverRegistry; +import com.android.server.wm.ActivityTaskManagerInternal; + +/** + * System-server-local proxy into the {@code IIorap} native service. + */ +public class IorapForwardingService extends SystemService { + + public static final boolean DEBUG = true; // TODO: read from a getprop? + public static final String TAG = "IorapForwardingService"; + + private IIorap mIorapRemote; + + /** + * Initializes the system service. + *

+ * Subclasses must define a single argument constructor that accepts the context + * and passes it to super. + *

+ * + * @param context The system server context. + */ + public IorapForwardingService(Context context) { + super(context); + } + + // + /* + * Providers for external dependencies: + * - These are marked as protected to allow tests to inject different values via mocks. + */ + + @VisibleForTesting + protected ActivityMetricsLaunchObserverRegistry provideLaunchObserverRegistry() { + ActivityTaskManagerInternal amtInternal = + LocalServices.getService(ActivityTaskManagerInternal.class); + ActivityMetricsLaunchObserverRegistry launchObserverRegistry = + amtInternal.getLaunchObserverRegistry(); + return launchObserverRegistry; + } + + @VisibleForTesting + protected IIorap provideIorapRemote() { + try { + return IIorap.Stub.asInterface(ServiceManager.getServiceOrThrow("iorapd")); + } catch (ServiceManager.ServiceNotFoundException e) { + // TODO: how do we handle service being missing? + throw new AssertionError(e); + } + } + + // + + @Override + public void onStart() { + if (DEBUG) { + Log.v(TAG, "onStart"); + } + + // Connect to the native binder service. + mIorapRemote = provideIorapRemote(); + invokeRemote( () -> mIorapRemote.setTaskListener(new RemoteTaskListener()) ); + + // Listen to App Launch Sequence events from ActivityTaskManager, + // and forward them to the native binder service. + ActivityMetricsLaunchObserverRegistry launchObserverRegistry = + provideLaunchObserverRegistry(); + launchObserverRegistry.registerLaunchObserver(new AppLaunchObserver()); + } + + private class AppLaunchObserver implements ActivityMetricsLaunchObserver { + // We add a synthetic sequence ID here to make it easier to differentiate new + // launch sequences on the native side. + private @AppLaunchEvent.SequenceId long mSequenceId = -1; + + @Override + public void onIntentStarted(@NonNull Intent intent) { + // #onIntentStarted [is the only transition that] initiates a new launch sequence. + ++mSequenceId; + + if (DEBUG) { + Log.v(TAG, String.format("AppLaunchObserver#onIntentStarted(%d, %s)", + mSequenceId, intent)); + } + + invokeRemote(() -> + mIorapRemote.onAppLaunchEvent(RequestId.nextValueForSequence(), + new AppLaunchEvent.IntentStarted(mSequenceId, intent)) + ); + } + + @Override + public void onIntentFailed() { + if (DEBUG) { + Log.v(TAG, String.format("AppLaunchObserver#onIntentFailed(%d)", mSequenceId)); + } + + invokeRemote(() -> + mIorapRemote.onAppLaunchEvent(RequestId.nextValueForSequence(), + new AppLaunchEvent.IntentFailed(mSequenceId)) + ); + } + + @Override + public void onActivityLaunched(@NonNull @ActivityRecordProto byte[] activity, + @Temperature int temperature) { + if (DEBUG) { + Log.v(TAG, String.format("AppLaunchObserver#onActivityLaunched(%d, %s, %d)", + mSequenceId, activity, temperature)); + } + + invokeRemote(() -> + mIorapRemote.onAppLaunchEvent(RequestId.nextValueForSequence(), + new AppLaunchEvent.ActivityLaunched(mSequenceId, activity, temperature)) + ); + } + + @Override + public void onActivityLaunchCancelled(@Nullable @ActivityRecordProto byte[] activity) { + if (DEBUG) { + Log.v(TAG, String.format("AppLaunchObserver#onActivityLaunchCancelled(%d, %s)", + mSequenceId, activity)); + } + + invokeRemote(() -> + mIorapRemote.onAppLaunchEvent(RequestId.nextValueForSequence(), + new AppLaunchEvent.ActivityLaunchCancelled(mSequenceId, + activity))); + } + + @Override + public void onActivityLaunchFinished(@NonNull @ActivityRecordProto byte[] activity) { + if (DEBUG) { + Log.v(TAG, String.format("AppLaunchObserver#onActivityLaunchFinished(%d, %s)", + mSequenceId, activity)); + } + + invokeRemote(() -> + mIorapRemote.onAppLaunchEvent(RequestId.nextValueForSequence(), + new AppLaunchEvent.ActivityLaunchCancelled(mSequenceId, activity)) + ); + } + } + + private class RemoteTaskListener extends ITaskListener.Stub { + @Override + public void onProgress(RequestId requestId, TaskResult result) throws RemoteException { + if (DEBUG) { + Log.v(TAG, + String.format("RemoteTaskListener#onProgress(%s, %s)", requestId, result)); + } + + // TODO: implement rest. + } + + @Override + public void onComplete(RequestId requestId, TaskResult result) throws RemoteException { + if (DEBUG) { + Log.v(TAG, + String.format("RemoteTaskListener#onComplete(%s, %s)", requestId, result)); + } + + // TODO: implement rest. + } + } + + private interface RemoteRunnable { + void run() throws RemoteException; + } + + private static void invokeRemote(RemoteRunnable r) { + try { + r.run(); + } catch (RemoteException e) { + // TODO: what do we do with exceptions? + throw new AssertionError("not implemented", e); + } + } +} diff --git a/startop/iorap/src/com/google/android/startop/iorap/RequestId.java b/startop/iorap/src/com/google/android/startop/iorap/RequestId.java index 2c79319a1459a..adb3a910f7fe1 100644 --- a/startop/iorap/src/com/google/android/startop/iorap/RequestId.java +++ b/startop/iorap/src/com/google/android/startop/iorap/RequestId.java @@ -71,7 +71,7 @@ private void checkConstructorArguments() { @Override public String toString() { - return String.format("{requestId: %ld}", requestId); + return String.format("{requestId: %d}", requestId); } @Override diff --git a/startop/iorap/tests/Android.bp b/startop/iorap/tests/Android.bp index 76057846e8962..5ac4a46b81f15 100644 --- a/startop/iorap/tests/Android.bp +++ b/startop/iorap/tests/Android.bp @@ -18,8 +18,15 @@ java_library { srcs: ["src/**/*.kt"], static_libs: [ - // non-test dependencies - "libiorap-java", + // Non-test dependencies + + // library under test + "services.startop.iorap", + // need the system_server code to be on the classpath, + "services.core", + + // Test Dependencies + // test android dependencies "platform-test-annotations", "android-support-test",