1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.internal.util.dump; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.util.Log; 22 import android.util.proto.ProtoOutputStream; 23 24 import com.android.internal.util.IndentingPrintWriter; 25 26 import java.nio.charset.StandardCharsets; 27 import java.util.ArrayList; 28 import java.util.Arrays; 29 import java.util.LinkedHashMap; 30 import java.util.LinkedList; 31 32 /** 33 * Dump either to a proto or a print writer using the same interface. 34 * 35 * <p>This mirrors the interface of {@link ProtoOutputStream}. 36 */ 37 public class DualDumpOutputStream { 38 private static final String LOG_TAG = DualDumpOutputStream.class.getSimpleName(); 39 40 // When writing to a proto, the proto 41 private final @Nullable ProtoOutputStream mProtoStream; 42 43 // When printing in clear text, the writer 44 private final @Nullable IndentingPrintWriter mIpw; 45 // Temporary storage of data when printing to mIpw 46 private final LinkedList<DumpObject> mDumpObjects = new LinkedList<>(); 47 48 private static abstract class Dumpable { 49 final String name; 50 51 private Dumpable(String name) { 52 this.name = name; 53 } 54 55 abstract void print(IndentingPrintWriter ipw, boolean printName); 56 } 57 58 private static class DumpObject extends Dumpable { 59 private final LinkedHashMap<String, ArrayList<Dumpable>> mSubObjects = new LinkedHashMap<>(); 60 61 private DumpObject(String name) { 62 super(name); 63 } 64 65 @Override 66 void print(IndentingPrintWriter ipw, boolean printName) { 67 if (printName) { 68 ipw.println(name + "={"); 69 } else { 70 ipw.println("{"); 71 } 72 ipw.increaseIndent(); 73 74 for (ArrayList<Dumpable> subObject: mSubObjects.values()) { 75 int numDumpables = subObject.size(); 76 77 if (numDumpables == 1) { 78 subObject.get(0).print(ipw, true); 79 } else { 80 ipw.println(subObject.get(0).name + "=["); 81 ipw.increaseIndent(); 82 83 for (int i = 0; i < numDumpables; i++) { 84 subObject.get(i).print(ipw, false); 85 } 86 87 ipw.decreaseIndent(); 88 ipw.println("]"); 89 } 90 } 91 92 ipw.decreaseIndent(); 93 ipw.println("}"); 94 } 95 96 /** 97 * Add new field / subobject to this object. 98 * 99 * <p>If a name is added twice, they will be printed as a array 100 * 101 * @param fieldName name of the field added 102 * @param d The dumpable to add 103 */ 104 public void add(String fieldName, Dumpable d) { 105 ArrayList<Dumpable> l = mSubObjects.get(fieldName); 106 107 if (l == null) { 108 l = new ArrayList<>(1); 109 mSubObjects.put(fieldName, l); 110 } 111 112 l.add(d); 113 } 114 } 115 116 private static class DumpField extends Dumpable { 117 private final String mValue; 118 119 private DumpField(String name, String value) { 120 super(name); 121 this.mValue = value; 122 } 123 124 @Override 125 void print(IndentingPrintWriter ipw, boolean printName) { 126 if (printName) { 127 ipw.println(name + "=" + mValue); 128 } else { 129 ipw.println(mValue); 130 } 131 } 132 } 133 134 /** 135 * Create a new DualDumpOutputStream. 136 * 137 * @param proto the {@link ProtoOutputStream} 138 */ 139 public DualDumpOutputStream(@NonNull ProtoOutputStream proto) { 140 mProtoStream = proto; 141 mIpw = null; 142 } 143 144 /** 145 * Create a new DualDumpOutputStream. 146 * 147 * @param ipw the {@link IndentingPrintWriter} 148 */ 149 public DualDumpOutputStream(@NonNull IndentingPrintWriter ipw) { 150 mProtoStream = null; 151 mIpw = ipw; 152 153 // Add root object 154 mDumpObjects.add(new DumpObject(null)); 155 } 156 157 public void write(@NonNull String fieldName, long fieldId, double val) { 158 if (mProtoStream != null) { 159 mProtoStream.write(fieldId, val); 160 } else { 161 mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, String.valueOf(val))); 162 } 163 } 164 165 public void write(@NonNull String fieldName, long fieldId, boolean val) { 166 if (mProtoStream != null) { 167 mProtoStream.write(fieldId, val); 168 } else { 169 mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, String.valueOf(val))); 170 } 171 } 172 173 public void write(@NonNull String fieldName, long fieldId, int val) { 174 if (mProtoStream != null) { 175 mProtoStream.write(fieldId, val); 176 } else { 177 mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, String.valueOf(val))); 178 } 179 } 180 181 public void write(@NonNull String fieldName, long fieldId, float val) { 182 if (mProtoStream != null) { 183 mProtoStream.write(fieldId, val); 184 } else { 185 mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, String.valueOf(val))); 186 } 187 } 188 189 public void write(@NonNull String fieldName, long fieldId, byte[] val) { 190 if (mProtoStream != null) { 191 mProtoStream.write(fieldId, val); 192 } else { 193 mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, Arrays.toString(val))); 194 } 195 } 196 197 public void write(@NonNull String fieldName, long fieldId, long val) { 198 if (mProtoStream != null) { 199 mProtoStream.write(fieldId, val); 200 } else { 201 mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, String.valueOf(val))); 202 } 203 } 204 205 public void write(@NonNull String fieldName, long fieldId, @Nullable String val) { 206 if (mProtoStream != null) { 207 mProtoStream.write(fieldId, val); 208 } else { 209 mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, String.valueOf(val))); 210 } 211 } 212 213 public long start(@NonNull String fieldName, long fieldId) { 214 if (mProtoStream != null) { 215 return mProtoStream.start(fieldId); 216 } else { 217 DumpObject d = new DumpObject(fieldName); 218 mDumpObjects.getLast().add(fieldName, d); 219 mDumpObjects.addLast(d); 220 return System.identityHashCode(d); 221 } 222 } 223 224 public void end(long token) { 225 if (mProtoStream != null) { 226 mProtoStream.end(token); 227 } else { 228 if (System.identityHashCode(mDumpObjects.getLast()) != token) { 229 Log.w(LOG_TAG, "Unexpected token for ending " + mDumpObjects.getLast().name 230 + " at " + Arrays.toString(Thread.currentThread().getStackTrace())); 231 } 232 mDumpObjects.removeLast(); 233 } 234 } 235 236 public void flush() { 237 if (mProtoStream != null) { 238 mProtoStream.flush(); 239 } else { 240 if (mDumpObjects.size() == 1) { 241 mDumpObjects.getFirst().print(mIpw, false); 242 243 // Reset root object 244 mDumpObjects.clear(); 245 mDumpObjects.add(new DumpObject(null)); 246 } 247 248 mIpw.flush(); 249 } 250 } 251 252 /** 253 * Add a dump from a different service into this dump. 254 * 255 * <p>Only for clear text dump. For proto dump use {@link #write(String, long, byte[])}. 256 * 257 * @param fieldName The name of the field 258 * @param nestedState The state of the dump 259 */ 260 public void writeNested(@NonNull String fieldName, byte[] nestedState) { 261 if (mIpw == null) { 262 Log.w(LOG_TAG, "writeNested does not work for proto logging"); 263 return; 264 } 265 266 mDumpObjects.getLast().add(fieldName, 267 new DumpField(fieldName, (new String(nestedState, StandardCharsets.UTF_8)).trim())); 268 } 269 270 /** 271 * @return {@code true} iff we are dumping to a proto 272 */ 273 public boolean isProto() { 274 return mProtoStream != null; 275 } 276 } 277