• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.android.internal.protolog;
2 
3 import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.GROUPS;
4 import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.ID;
5 import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.NAME;
6 import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MESSAGES;
7 import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE;
8 import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE_ID;
9 import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.GROUP_ID;
10 
11 import android.annotation.NonNull;
12 import android.annotation.Nullable;
13 import android.util.LongSparseArray;
14 import android.util.proto.ProtoInputStream;
15 
16 import com.android.internal.protolog.common.ILogger;
17 
18 import java.io.IOException;
19 import java.util.Map;
20 import java.util.Objects;
21 import java.util.Set;
22 import java.util.TreeMap;
23 
24 public class ProtoLogViewerConfigReader {
25     @NonNull
26     private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider;
27     @NonNull
28     private final Map<String, Set<Long>> mGroupHashes = new TreeMap<>();
29     @NonNull
30     private final LongSparseArray<String> mLogMessageMap = new LongSparseArray<>();
31 
ProtoLogViewerConfigReader( @onNull ViewerConfigInputStreamProvider viewerConfigInputStreamProvider)32     public ProtoLogViewerConfigReader(
33             @NonNull ViewerConfigInputStreamProvider viewerConfigInputStreamProvider) {
34         this.mViewerConfigInputStreamProvider = viewerConfigInputStreamProvider;
35     }
36 
37     /**
38      * Returns message format string for its hash or null if unavailable
39      * or the viewer config is not loaded into memory.
40      */
41     @Nullable
getViewerString(long messageHash)42     public String getViewerString(long messageHash) {
43         return mLogMessageMap.get(messageHash);
44     }
45 
46     /**
47      * Load the viewer configs for the target groups into memory.
48      * Only viewer configs loaded into memory can be required. So this must be called for all groups
49      * we want to query before we query their viewer config.
50      *
51      * @param groups Groups to load the viewer configs from file into memory.
52      */
loadViewerConfig(@onNull String[] groups)53     public synchronized void loadViewerConfig(@NonNull String[] groups) {
54         loadViewerConfig(groups, (message) -> {});
55     }
56 
57     /**
58      * Loads the viewer config into memory. No-op if already loaded in memory.
59      */
loadViewerConfig(@onNull String[] groups, @NonNull ILogger logger)60     public synchronized void loadViewerConfig(@NonNull String[] groups, @NonNull ILogger logger) {
61         for (String group : groups) {
62             if (mGroupHashes.containsKey(group)) {
63                 continue;
64             }
65 
66             try {
67                 Map<Long, String> mappings = loadViewerConfigMappingForGroup(group);
68                 mGroupHashes.put(group, mappings.keySet());
69                 for (Long key : mappings.keySet()) {
70                     mLogMessageMap.put(key, mappings.get(key));
71                 }
72 
73                 logger.log("Loaded " + mLogMessageMap.size() + " log definitions");
74             } catch (IOException e) {
75                 logger.log("Unable to load log definitions: "
76                         + "IOException while processing viewer config" + e);
77             }
78         }
79     }
80 
unloadViewerConfig(@onNull String[] groups)81     public synchronized void unloadViewerConfig(@NonNull String[] groups) {
82         unloadViewerConfig(groups, (message) -> {});
83     }
84 
85     /**
86      * Unload the viewer config from memory.
87      */
unloadViewerConfig(@onNull String[] groups, @NonNull ILogger logger)88     public synchronized void unloadViewerConfig(@NonNull String[] groups, @NonNull ILogger logger) {
89         for (String group : groups) {
90             if (!mGroupHashes.containsKey(group)) {
91                 continue;
92             }
93 
94             final Set<Long> hashes = mGroupHashes.get(group);
95             for (Long hash : hashes) {
96                 logger.log("Unloading viewer config hash " + hash);
97                 mLogMessageMap.remove(hash);
98             }
99             mGroupHashes.remove(group);
100         }
101     }
102 
103     /**
104      * Return whether or not the viewer config file contains a message with the specified hash.
105      * @param messageHash The hash message we are looking for in the viewer config file
106      * @return True iff the message with message hash is contained in the viewer config.
107      * @throws IOException if there was an issue reading the viewer config file.
108      */
messageHashIsAvailableInFile(long messageHash)109     public boolean messageHashIsAvailableInFile(long messageHash)
110             throws IOException {
111         try (var pisWrapper = mViewerConfigInputStreamProvider.getInputStream()) {
112             final var pis = pisWrapper.get();
113             while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
114                 if (pis.getFieldNumber() == (int) MESSAGES) {
115                     final long inMessageToken = pis.start(MESSAGES);
116 
117                     while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
118                         if (pis.getFieldNumber() == (int) MESSAGE_ID) {
119                             if (pis.readLong(MESSAGE_ID) == messageHash) {
120                                 return true;
121                             }
122                         }
123                     }
124 
125                     pis.end(inMessageToken);
126                 }
127             }
128         }
129 
130         return false;
131     }
132 
133     @NonNull
loadViewerConfigMappingForGroup(@onNull String group)134     private Map<Long, String> loadViewerConfigMappingForGroup(@NonNull String group)
135             throws IOException {
136         long targetGroupId = loadGroupId(group);
137 
138         final Map<Long, String> hashesForGroup = new TreeMap<>();
139         try (var pisWrapper = mViewerConfigInputStreamProvider.getInputStream()) {
140             final var pis = pisWrapper.get();
141             while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
142                 if (pis.getFieldNumber() == (int) MESSAGES) {
143                     final long inMessageToken = pis.start(MESSAGES);
144 
145                     long messageId = 0;
146                     String message = null;
147                     int groupId = 0;
148                     while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
149                         switch (pis.getFieldNumber()) {
150                             case (int) MESSAGE_ID:
151                                 messageId = pis.readLong(MESSAGE_ID);
152                                 break;
153                             case (int) MESSAGE:
154                                 message = pis.readString(MESSAGE);
155                                 break;
156                             case (int) GROUP_ID:
157                                 groupId = pis.readInt(GROUP_ID);
158                                 break;
159                         }
160                     }
161 
162                     if (groupId == 0) {
163                         throw new IOException("Failed to get group id");
164                     }
165 
166                     if (messageId == 0) {
167                         throw new IOException("Failed to get message id");
168                     }
169 
170                     if (message == null) {
171                         throw new IOException("Failed to get message string");
172                     }
173 
174                     if (groupId == targetGroupId) {
175                         hashesForGroup.put(messageId, message);
176                     }
177 
178                     pis.end(inMessageToken);
179                 }
180             }
181         }
182 
183         return hashesForGroup;
184     }
185 
loadGroupId(@onNull String group)186     private long loadGroupId(@NonNull String group) throws IOException {
187         try (var pisWrapper = mViewerConfigInputStreamProvider.getInputStream()) {
188             final var pis = pisWrapper.get();
189 
190             while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
191                 if (pis.getFieldNumber() == (int) GROUPS) {
192                     final long inMessageToken = pis.start(GROUPS);
193 
194                     long groupId = 0;
195                     String groupName = null;
196                     while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
197                         switch (pis.getFieldNumber()) {
198                             case (int) ID:
199                                 groupId = pis.readInt(ID);
200                                 break;
201                             case (int) NAME:
202                                 groupName = pis.readString(NAME);
203                                 break;
204                         }
205                     }
206 
207                     if (Objects.equals(groupName, group)) {
208                         return groupId;
209                     }
210 
211                     pis.end(inMessageToken);
212                 }
213             }
214         }
215 
216         throw new RuntimeException("Group " + group + " not found in viewer config");
217     }
218 }
219