Home | History | Annotate | Download | only in dump
      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