From f4006d9b0be123b2a4e874b89eb4a431d3d49c8b Mon Sep 17 00:00:00 2001 From: Felipe Leme Date: Fri, 7 Oct 2016 17:57:13 -0700 Subject: [PATCH] Implemented a PriorityDump helper. dumpsys / dumpstate will soon dump services in buckets (CRITICAL, HIGH, NORMAL) and services must check the --dump_priority argument to select which sections to dump. PriorityDump parses the args checking for that argument, making it easier to implement bucket dumping. BUG: 27429130 Test: com.android.server.utils.PriorityDumpTest passes Change-Id: Ia5ee08a3dc5bd0fde80b8a5616e1311e1b3bf7ba --- .../android/server/utils/PriorityDump.java | 179 +++++++++++++++ .../server/utils/PriorityDumpTest.java | 210 ++++++++++++++++++ 2 files changed, 389 insertions(+) create mode 100644 services/core/java/com/android/server/utils/PriorityDump.java create mode 100644 services/tests/servicestests/src/com/android/server/utils/PriorityDumpTest.java diff --git a/services/core/java/com/android/server/utils/PriorityDump.java b/services/core/java/com/android/server/utils/PriorityDump.java new file mode 100644 index 0000000000000..c05cc3ff88270 --- /dev/null +++ b/services/core/java/com/android/server/utils/PriorityDump.java @@ -0,0 +1,179 @@ +/** + * Copyright (c) 2016, 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.android.server.utils; + +import java.io.FileDescriptor; +import java.io.PrintWriter; + +/** + * Helper for {@link android.os.Binder#dump(java.io.FileDescriptor, String[])} that supports the + * {@link #PRIORITY_ARG} argument. + *

+ * Typical usage: + * + *


+public class SpringfieldNuclearPowerPlant extends Binder {
+
+ private final PriorityDump.PriorityDumper mPriorityDumper = new PriorityDump.PriorityDumper() {
+
+     @Override
+     public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args) {
+       pw.println("Donuts in the box: 1");
+     }
+
+     @Override
+     public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args) {
+       pw.println("Nuclear reactor status: DANGER - MELTDOWN IMMINENT");
+     }
+  };
+
+  @Override
+  protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+      PriorityDump.dump(mPriorityDumper, fd, pw, args);
+  }
+}
+
+ * 
+ * + * Disclaimer: a real-life service should prioritize core status over donuts :-) + * + *

Then to invoke it: + * + *


+ *
+    $ adb shell dumpsys snpp
+    Donuts in the box: 1
+    Nuclear reactor status: DANGER - MELTDOWN IMMINENT
+
+    $ adb shell dumpsys snpp --dump_priority CRITICAL
+    Donuts in the box: 1
+
+    $ adb shell dumpsys snpp --dump_priority NORMAL
+    Nuclear reactor status: DANGER - MELTDOWN IMMINENT
+
+ * 
+ * + * + * + *

To run the unit tests: + *


+ *
+ mmm -j32 frameworks/base/services/tests/servicestests/ && \
+ adb install -r -g ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk && \
+ adb shell am instrument -e class "com.android.server.utils.PriorityDumpTest" \
+ -w "com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner"
+
+ * 
+ * + * + * @hide + */ +public final class PriorityDump { + + public static final String PRIORITY_ARG = "--dump_priority"; + + private PriorityDump() { + throw new UnsupportedOperationException(); + } + + /** + * Parses {@code} and call the proper {@link PriorityDumper} method when the first argument is + * {@code --dump_priority}, stripping the priority and its type. + *

+ * For example, if called as {@code --dump_priority HIGH arg1 arg2 arg3}, it will call + * dumper.dumpHigh(fd, pw, {"arg1", "arg2", "arg3"}) + *

+ * If the {@code --dump_priority} is not set, it calls + * {@link PriorityDumper#dump(FileDescriptor, PrintWriter, String[])} passing the whole + * {@code args} instead. + */ + public static void dump(PriorityDumper dumper, FileDescriptor fd, PrintWriter pw, + String[] args) { + if (args != null && args.length >= 2 && args[0].equals(PRIORITY_ARG)) { + final String priority = args[1]; + switch (priority) { + case "CRITICAL": { + dumper.dumpCritical(fd, pw, getStrippedArgs(args)); + return; + } + case "HIGH": { + dumper.dumpHigh(fd, pw, getStrippedArgs(args)); + return; + } + case "NORMAL": { + dumper.dumpNormal(fd, pw, getStrippedArgs(args)); + return; + } + } + } + dumper.dump(fd, pw, args); + } + + /** + * Gets an array without the {@code --dump_priority PRIORITY} prefix. + */ + private static String[] getStrippedArgs(String[] args) { + final String[] stripped = new String[args.length - 2]; + System.arraycopy(args, 2, stripped, 0, stripped.length); + return stripped; + } + + /** + * Helper for {@link android.os.Binder#dump(java.io.FileDescriptor, String[])} that supports the + * {@link #PRIORITY_ARG} argument. + * + * @hide + */ + public static interface PriorityDumper { + + /** + * Dumps only the critical section. + */ + @SuppressWarnings("unused") + default void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args) { + } + + /** + * Dumps only the high-priority section. + */ + @SuppressWarnings("unused") + default void dumpHigh(FileDescriptor fd, PrintWriter pw, String[] args) { + } + + /** + * Dumps only the normal section. + */ + @SuppressWarnings("unused") + default void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args) { + } + + /** + * Dumps all sections. + *

+ * This method is called when + * {@link PriorityDump#dump(PriorityDumper, FileDescriptor, PrintWriter, String[])} is + * called without priority arguments. By default, it calls the 3 {@code dumpTYPE} methods, + * so sub-classes just need to implement the priority types they support. + */ + @SuppressWarnings("unused") + default void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + dumpCritical(fd, pw, args); + dumpHigh(fd, pw, args); + dumpNormal(fd, pw, args); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/utils/PriorityDumpTest.java b/services/tests/servicestests/src/com/android/server/utils/PriorityDumpTest.java new file mode 100644 index 0000000000000..d378b7c56acbd --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/utils/PriorityDumpTest.java @@ -0,0 +1,210 @@ +/** + * Copyright (c) 2016, 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.android.server.utils; + +import static com.android.server.utils.PriorityDump.dump; + +import static org.junit.Assert.assertSame; +import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.same; +import static org.mockito.Mockito.verify; + +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.SmallTest; +import com.android.server.utils.PriorityDump.PriorityDumper; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.FileDescriptor; +import java.io.PrintWriter; + +@SmallTest +@Presubmit +@RunWith(JUnit4.class) +public class PriorityDumpTest { + + private static final String[] EMPTY_ARGS = {}; + + @Mock + private PriorityDumper mDumper; + @Mock + private PrintWriter mPw; + + private final FileDescriptor mFd = FileDescriptor.err; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testNullArgs() { + dump(mDumper, mFd, mPw, null); + verify(mDumper).dump(same(mFd), same(mPw), eq(null)); + } + + @Test + public void testNoArgs() { + dump(mDumper, mFd, mPw, EMPTY_ARGS); + verify(mDumper).dump(same(mFd), same(mPw), same(EMPTY_ARGS)); + } + + @Test + public void testNonPriorityArgs() { + final String[] args = { + "--dumb_priority" + }; + dump(mDumper, mFd, mPw, args); + verify(mDumper).dump(same(mFd), same(mPw), same(args)); + } + + @Test + public void testMissingPriority() { + final String[] args = { + "--dump_priority" + }; + dump(mDumper, mFd, mPw, args); + verify(mDumper).dump(same(mFd), same(mPw), same(args)); + } + + @Test + public void testInvalidPriorityNoExtraArgs() { + final String[] args = { + "--dump_priority", "SUPER_HIGH" + }; + dump(mDumper, mFd, mPw, args); + verify(mDumper).dump(same(mFd), same(mPw), same(args)); + } + + @Test + public void testInvalidPriorityExtraArgs() { + final String[] args = { + "--dump_priority", "SUPER_HIGH", "--high", "--five" + }; + dump(mDumper, mFd, mPw, args); + verify(mDumper).dump(same(mFd), same(mPw), same(args)); + } + + @Test + public void testNoPriorityCallsAllMethods() { + final String[] args = { + "1", "2", "3" + }; + + // Cannot use mDumper here because it would mock the dump() call. + final FakeDumper fakeDumper = new FakeDumper(); + + dump(fakeDumper, mFd, mPw, args); + + assertSame(mFd, fakeDumper.criticalFd); + assertSame(mPw, fakeDumper.criticalPw); + assertSame(args, fakeDumper.criticalArgs); + assertSame(mFd, fakeDumper.highFd); + assertSame(mPw, fakeDumper.highPw); + assertSame(args, fakeDumper.highArgs); + assertSame(mFd, fakeDumper.normalFd); + assertSame(mPw, fakeDumper.normalPw); + assertSame(args, fakeDumper.normalArgs); + } + + @Test + public void testCriticalNoExtraArgs() { + dump(mDumper, mFd, mPw, new String[] { + "--dump_priority", "CRITICAL" + }); + verify(mDumper).dumpCritical(same(mFd), same(mPw), eq(EMPTY_ARGS)); + } + + @Test + public void testCriticalExtraArgs() { + dump(mDumper, mFd, mPw, new String[] { + "--dump_priority", "CRITICAL", "--high", "--five" + }); + verify(mDumper).dumpCritical(same(mFd), same(mPw), eq(new String[] { + "--high", "--five" + })); + } + + @Test + public void testHighNoExtraArgs() { + dump(mDumper, mFd, mPw, new String[] { + "--dump_priority", "HIGH" + }); + verify(mDumper).dumpHigh(same(mFd), same(mPw), eq(EMPTY_ARGS)); + } + + @Test + public void testHighExtraArgs() { + dump(mDumper, mFd, mPw, new String[] { + "--dump_priority", "HIGH", "--high", "--five" + }); + verify(mDumper).dumpHigh(same(mFd), same(mPw), eq(new String[] { + "--high", "--five" + })); + } + + @Test + public void testNormalNoExtraArgs() { + dump(mDumper, mFd, mPw, new String[] { + "--dump_priority", "NORMAL" + }); + verify(mDumper).dumpNormal(same(mFd), same(mPw), eq(EMPTY_ARGS)); + } + + @Test + public void testNormalExtraArgs() { + dump(mDumper, mFd, mPw, new String[] { + "--dump_priority", "NORMAL", "--high", "--five" + }); + verify(mDumper).dumpNormal(same(mFd), same(mPw), eq(new String[] { + "--high", "--five" + })); + } + + private final class FakeDumper implements PriorityDumper { + + String[] criticalArgs, highArgs, normalArgs; + FileDescriptor criticalFd, highFd, normalFd; + PrintWriter criticalPw, highPw, normalPw; + + @Override + public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args) { + criticalFd = fd; + criticalPw = pw; + criticalArgs = args; + } + + @Override + public void dumpHigh(FileDescriptor fd, PrintWriter pw, String[] args) { + highFd = fd; + highPw = pw; + highArgs = args; + } + + @Override + public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args) { + normalFd = fd; + normalPw = pw; + normalArgs = args; + } + } +}