diff options
author | Yan Wang <yawanng@google.com> | 2019-11-20 10:52:33 -0800 |
---|---|---|
committer | Yan Wang <yawanng@google.com> | 2020-01-02 10:25:32 -0800 |
commit | faa7aa527ef79d754b5c144e4e1941d3cb6c2336 (patch) | |
tree | d3b511ec5e982956f76eb5ce2d7320a4109d8c0f /startop | |
parent | faf22d2c2ac4b5e6a4c4f8e23a19ebb4c6688b5b (diff) |
startop: Add a validator to check the correctness of event sequence.
Change-Id: Ic764cfe4f3f98e14e756897d6dcdc8c167d8a728
Diffstat (limited to 'startop')
-rw-r--r-- | startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java | 255 | ||||
-rw-r--r-- | startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java | 2 |
2 files changed, 257 insertions, 0 deletions
diff --git a/startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java b/startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java new file mode 100644 index 000000000000..b75510b576f6 --- /dev/null +++ b/startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2019 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.NonNull; +import android.annotation.Nullable; +import android.content.Intent; +import android.util.Log; + +import com.android.server.wm.ActivityMetricsLaunchObserver; + +/** + * A validator to check the correctness of event sequence during app startup. + * + * <p> A valid state transition of event sequence is shown as the following: + * + * <pre> + * + * +--------------------+ + * | | + * | INIT | + * | | + * +--------------------+ + * | + * | + * ↓ + * +--------------------+ + * | | + * +-------------------| INTENT_STARTED | ←--------------------------------+ + * | | | | + * | +--------------------+ | + * | | | + * | | | + * ↓ ↓ | + * +--------------------+ +--------------------+ | + * | | | | | + * | INTENT_FAILED | | ACTIVITY_LAUNCHED |------------------+ | + * | | | | | | + * +--------------------+ +--------------------+ | | + * | | | | + * | ↓ ↓ | + * | +--------------------+ +--------------------+ | + * | | | | | | + * +------------------ | ACTIVITY_FINISHED | | ACTIVITY_CANCELLED | | + * | | | | | | + * | +--------------------+ +--------------------+ | + * | | | | + * | | | | + * | ↓ | | + * | +--------------------+ | | + * | | | | | + * | | REPORT_FULLY_DRAWN | | | + * | | | | | + * | +--------------------+ | | + * | | | | + * | | | | + * | ↓ | | + * | +--------------------+ | | + * | | | | | + * +-----------------→ | END |←-----------------+ | + * | | | + * +--------------------+ | + * | | + * | | + * | | + * +--------------------------------------------- + * + * <p> END is not a real state in implementation. All states that points to END directly + * could transition to INTENT_STARTED. + * + * <p> If any bad transition happened, the state becomse UNKNOWN. The UNKNOWN state + * could be * accumulated, because during the UNKNOWN state more IntentStarted may + * be triggered. To recover from UNKNOWN to INIT, all the accumualted IntentStarted + * should termniate. + * + * <p> During UNKNOWN state, each IntentStarted increases the accumulation, and any of + * IntentFailed, ActivityLaunchCancelled and ActivityFinished decreases the accumulation. + * ReportFullyDrawn doesn't impact the accumulation. + */ +public class EventSequenceValidator implements ActivityMetricsLaunchObserver { + static final String TAG = "EventSequenceValidator"; + + private State state = State.INIT; + private long accIntentStartedEvents = 0; + + @Override + public void onIntentStarted(@NonNull Intent intent, long timestampNs) { + if (state == State.UNKNOWN) { + Log.e(TAG, "IntentStarted during UNKNOWN." + intent); + incAccIntentStartedEvents(); + return; + } + + if (state != State.INIT && + state != State.INTENT_FAILED && + state != State.ACTIVITY_CANCELLED && + state != State.ACTIVITY_FINISHED && + state != State.REPORT_FULLY_DRAWN) { + Log.e(TAG, + String.format("Cannot transition from %s to %s", state, State.INTENT_STARTED)); + incAccIntentStartedEvents(); + incAccIntentStartedEvents(); + return; + } + + Log.i(TAG, String.format("Tansition from %s to %s", state, State.INTENT_STARTED)); + state = State.INTENT_STARTED; + } + + @Override + public void onIntentFailed() { + if (state == State.UNKNOWN) { + Log.e(TAG, "IntentFailed during UNKNOWN."); + decAccIntentStartedEvents(); + return; + } + if (state != State.INTENT_STARTED) { + Log.e(TAG, + String.format("Cannot transition from %s to %s", state, State.INTENT_FAILED)); + incAccIntentStartedEvents(); + return; + } + + Log.i(TAG, String.format("Tansition from %s to %s", state, State.INTENT_FAILED)); + state = State.INTENT_FAILED; + } + + @Override + public void onActivityLaunched(@NonNull @ActivityRecordProto byte[] activity, + @Temperature int temperature) { + if (state == State.UNKNOWN) { + Log.e(TAG, "onActivityLaunched during UNKNOWN."); + return; + } + if (state != State.INTENT_STARTED) { + Log.e(TAG, + String.format("Cannot transition from %s to %s", state, State.ACTIVITY_LAUNCHED)); + incAccIntentStartedEvents(); + return; + } + + Log.i(TAG, String.format("Transition from %s to %s", state, State.ACTIVITY_LAUNCHED)); + state = State.ACTIVITY_LAUNCHED; + } + + @Override + public void onActivityLaunchCancelled(@Nullable @ActivityRecordProto byte[] activity) { + if (state == State.UNKNOWN) { + Log.e(TAG, "onActivityLaunchCancelled during UNKNOWN."); + decAccIntentStartedEvents(); + return; + } + if (state != State.ACTIVITY_LAUNCHED) { + Log.e(TAG, + String.format("Cannot transition from %s to %s", state, State.ACTIVITY_CANCELLED)); + incAccIntentStartedEvents(); + return; + } + + Log.i(TAG, String.format("Transition from %s to %s", state, State.ACTIVITY_CANCELLED)); + state = State.ACTIVITY_CANCELLED; + } + + @Override + public void onActivityLaunchFinished(@NonNull @ActivityRecordProto byte[] activity, + long timestampNs) { + if (state == State.UNKNOWN) { + Log.e(TAG, "onActivityLaunchFinished during UNKNOWN."); + decAccIntentStartedEvents(); + return; + } + + if (state != State.ACTIVITY_LAUNCHED) { + Log.e(TAG, + String.format("Cannot transition from %s to %s", state, State.ACTIVITY_FINISHED)); + incAccIntentStartedEvents(); + return; + } + + Log.i(TAG, String.format("Transition from %s to %s", state, State.ACTIVITY_FINISHED)); + state = State.ACTIVITY_FINISHED; + } + + @Override + public void onReportFullyDrawn(@NonNull @ActivityRecordProto byte[] activity, + long timestampNs) { + if (state == State.UNKNOWN) { + Log.e(TAG, "onReportFullyDrawn during UNKNOWN."); + return; + } + if (state == State.INIT) { + return; + } + + if (state != State.ACTIVITY_FINISHED) { + Log.e(TAG, + String.format("Cannot transition from %s to %s", state, State.REPORT_FULLY_DRAWN)); + return; + } + + Log.i(TAG, String.format("Transition from %s to %s", state, State.REPORT_FULLY_DRAWN)); + state = State.REPORT_FULLY_DRAWN; + } + + enum State { + INIT, + INTENT_STARTED, + INTENT_FAILED, + ACTIVITY_LAUNCHED, + ACTIVITY_CANCELLED, + ACTIVITY_FINISHED, + REPORT_FULLY_DRAWN, + UNKNOWN, + } + + private void incAccIntentStartedEvents() { + if (accIntentStartedEvents < 0) { + throw new AssertionError( + String.format("The number of unknows cannot be negative")); + } + if (accIntentStartedEvents == 0) { + state = State.UNKNOWN; + } + ++accIntentStartedEvents; + Log.i(TAG, + String.format("inc AccIntentStartedEvents to %d", accIntentStartedEvents)); + } + + private void decAccIntentStartedEvents() { + if (accIntentStartedEvents <= 0) { + throw new AssertionError( + String.format("The number of unknows cannot be negative")); + } + if(accIntentStartedEvents == 1) { + state = State.INIT; + } + --accIntentStartedEvents; + Log.i(TAG, + String.format("dec AccIntentStartedEvents to %d", accIntentStartedEvents)); + } +} diff --git a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java index f753548516ef..badff7bc2f8c 100644 --- a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java +++ b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java @@ -283,6 +283,7 @@ public class IorapForwardingService extends SystemService { } private final AppLaunchObserver mAppLaunchObserver = new AppLaunchObserver(); + private final EventSequenceValidator mEventSequenceValidator = new EventSequenceValidator(); private boolean mRegisteredListeners = false; private void registerInProcessListenersLocked() { @@ -303,6 +304,7 @@ public class IorapForwardingService extends SystemService { ActivityMetricsLaunchObserverRegistry launchObserverRegistry = provideLaunchObserverRegistry(); launchObserverRegistry.registerLaunchObserver(mAppLaunchObserver); + launchObserverRegistry.registerLaunchObserver(mEventSequenceValidator); mRegisteredListeners = true; } |