2 * Copyright 2011 Google Inc.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.google.ipc.invalidation.ticl;
19 import com.google.ipc.invalidation.external.client.SystemResources.Logger;
20 import com.google.ipc.invalidation.external.client.types.SimplePair;
21 import com.google.ipc.invalidation.ticl.proto.ClientProtocol.PropertyRecord;
22 import com.google.ipc.invalidation.ticl.proto.JavaClient.StatisticsState;
23 import com.google.ipc.invalidation.util.InternalBase;
24 import com.google.ipc.invalidation.util.Marshallable;
25 import com.google.ipc.invalidation.util.TextBuilder;
26 import com.google.ipc.invalidation.util.TypedUtil;
28 import java.util.ArrayList;
29 import java.util.Collection;
30 import java.util.HashMap;
31 import java.util.List;
35 * Statistics for the Ticl, e.g., number of registration calls, number of token mismatches, etc.
38 public class Statistics extends InternalBase implements Marshallable<StatisticsState> {
40 // Implementation: To classify the statistics a bit better, we have a few enums to track different
41 // types of statistics, e.g., sent message types, errors, etc. For each statistic type, we create
42 // a map and provide a method to record an event for each type of statistic.
44 /** Types of messages sent to the server: {@code ClientToServerMessage} for their description. */
45 public enum SentMessageType {
51 TOTAL, // Refers to the actual ClientToServerMessage message sent on the network.
55 * Types of messages received from the server: {@code ServerToClientMessage} for their
58 public enum ReceivedMessageType {
62 REGISTRATION_SYNC_REQUEST,
66 STALE_INVALIDATION, // An already acked INVALIDATION.
67 TOTAL, // Refers to the actual ServerToClientMessage messages received from the network.
70 /** Interesting API calls coming from the application ({@code InvalidationClient}). */
71 public enum IncomingOperationType {
77 /** Different types of events issued by the {@code InvalidationListener}). */
78 public enum ListenerEventType {
80 INFORM_REGISTRATION_FAILURE,
81 INFORM_REGISTRATION_STATUS,
85 REISSUE_REGISTRATIONS,
88 /** Different types of errors observed by the Ticl. */
89 public enum ClientErrorType {
90 /** Acknowledge call received from client with a bad handle. */
91 ACKNOWLEDGE_HANDLE_FAILURE,
93 /** Incoming message dropped due to parsing, validation problems. */
94 INCOMING_MESSAGE_FAILURE,
96 /** Tried to send an outgoing message that was invalid. */
97 OUTGOING_MESSAGE_FAILURE,
99 /** Persistent state failed to deserialize correctly. */
100 PERSISTENT_DESERIALIZATION_FAILURE,
102 /** Read of blob from persistent state failed. */
103 PERSISTENT_READ_FAILURE,
105 /** Write of blob from persistent state failed. */
106 PERSISTENT_WRITE_FAILURE,
108 /** Message received with incompatible protocol version. */
109 PROTOCOL_VERSION_FAILURE,
112 * Registration at client and server is different, e.g., client thinks it is registered while
113 * the server says it is unregistered (of course, sync will fix it).
115 REGISTRATION_DISCREPANCY,
117 /** The nonce from the server did not match the current nonce by the client. */
120 /** The current token at the client is different from the token in the incoming message. */
123 /** No message sent due to token missing. */
124 TOKEN_MISSING_FAILURE,
126 /** Received a message with a token (transient) failure. */
127 TOKEN_TRANSIENT_FAILURE,
130 // Names of statistics types. Do not rely on reflection to determine type names because Proguard
131 // may change them for Android clients.
132 private static final String SENT_MESSAGE_TYPE_NAME = "SentMessageType";
133 private static final String INCOMING_OPERATION_TYPE_NAME = "IncomingOperationType";
134 private static final String RECEIVED_MESSAGE_TYPE_NAME = "ReceivedMessageType";
135 private static final String LISTENER_EVENT_TYPE_NAME = "ListenerEventType";
136 private static final String CLIENT_ERROR_TYPE_NAME = "ClientErrorType";
138 // Map from stats enum names to values. Used in place of Enum.valueOf() because this method
139 // invokes Enum.values() via reflection, and that method may be renamed by Proguard.
140 private static final Map<String, SentMessageType> SENT_MESSAGE_TYPE_NAME_TO_VALUE_MAP =
141 createValueOfMap(SentMessageType.values());
142 private static final Map<String, IncomingOperationType>
143 INCOMING_OPERATION_TYPE_NAME_TO_VALUE_MAP = createValueOfMap(IncomingOperationType.values());
144 private static final Map<String, ReceivedMessageType> RECEIVED_MESSAGE_TYPE_NAME_TO_VALUE_MAP =
145 createValueOfMap(ReceivedMessageType.values());
146 private static final Map<String, ListenerEventType> LISTENER_EVENT_TYPE_NAME_TO_VALUE_MAP =
147 createValueOfMap(ListenerEventType.values());
148 private static final Map<String, ClientErrorType> CLIENT_ERROR_TYPE_NAME_TO_VALUE_MAP =
149 createValueOfMap(ClientErrorType.values());
151 // Maps for each type of Statistic to keep track of how many times each event has occurred.
153 private final Map<SentMessageType, Integer> sentMessageTypes =
154 new HashMap<SentMessageType, Integer>();
155 private final Map<ReceivedMessageType, Integer> receivedMessageTypes =
156 new HashMap<ReceivedMessageType, Integer>();
157 private final Map<IncomingOperationType, Integer> incomingOperationTypes =
158 new HashMap<IncomingOperationType, Integer>();
159 private final Map<ListenerEventType, Integer> listenerEventTypes =
160 new HashMap<ListenerEventType, Integer>();
161 private final Map<ClientErrorType, Integer> clientErrorTypes =
162 new HashMap<ClientErrorType, Integer>();
164 public Statistics() {
165 initializeMap(sentMessageTypes, SentMessageType.values());
166 initializeMap(receivedMessageTypes, ReceivedMessageType.values());
167 initializeMap(incomingOperationTypes, IncomingOperationType.values());
168 initializeMap(listenerEventTypes, ListenerEventType.values());
169 initializeMap(clientErrorTypes, ClientErrorType.values());
172 /** Returns a copy of this. */
173 public Statistics getCopyForTest() {
174 Statistics statistics = new Statistics();
175 statistics.sentMessageTypes.putAll(sentMessageTypes);
176 statistics.receivedMessageTypes.putAll(receivedMessageTypes);
177 statistics.incomingOperationTypes.putAll(incomingOperationTypes);
178 statistics.listenerEventTypes.putAll(listenerEventTypes);
179 statistics.clientErrorTypes.putAll(clientErrorTypes);
183 /** Returns the counter value for {@code clientErrorType}. */
184 int getClientErrorCounterForTest(ClientErrorType clientErrorType) {
185 return TypedUtil.mapGet(clientErrorTypes, clientErrorType);
188 /** Returns the counter value for {@code sentMessageType}. */
189 int getSentMessageCounterForTest(SentMessageType sentMessageType) {
190 return TypedUtil.mapGet(sentMessageTypes, sentMessageType);
193 /** Returns the counter value for {@code receivedMessageType}. */
194 int getReceivedMessageCounterForTest(ReceivedMessageType receivedMessageType) {
195 return TypedUtil.mapGet(receivedMessageTypes, receivedMessageType);
198 /** Records the fact that a message of type {@code sentMessageType} has been sent. */
199 public void recordSentMessage(SentMessageType sentMessageType) {
200 incrementValue(sentMessageTypes, sentMessageType);
203 /** Records the fact that a message of type {@code receivedMessageType} has been received. */
204 public void recordReceivedMessage(ReceivedMessageType receivedMessageType) {
205 incrementValue(receivedMessageTypes, receivedMessageType);
209 * Records the fact that the application has made a call of type
210 * {@code incomingOperationType}.
212 public void recordIncomingOperation(IncomingOperationType incomingOperationType) {
213 incrementValue(incomingOperationTypes, incomingOperationType);
216 /** Records the fact that the listener has issued an event of type {@code listenerEventType}. */
217 public void recordListenerEvent(ListenerEventType listenerEventType) {
218 incrementValue(listenerEventTypes, listenerEventType);
221 /** Records the fact that the client has observed an error of type {@code clientErrorType}. */
222 public void recordError(ClientErrorType clientErrorType) {
223 incrementValue(clientErrorTypes, clientErrorType);
227 * Modifies {@code performanceCounters} to contain all the statistics that are non-zero. Each pair
228 * has the name of the statistic event and the number of times that event has occurred since the
231 public void getNonZeroStatistics(List<SimplePair<String, Integer>> performanceCounters) {
232 // Add the non-zero values from the different maps to performanceCounters.
233 fillWithNonZeroStatistics(sentMessageTypes, performanceCounters, SENT_MESSAGE_TYPE_NAME);
234 fillWithNonZeroStatistics(receivedMessageTypes, performanceCounters,
235 RECEIVED_MESSAGE_TYPE_NAME);
236 fillWithNonZeroStatistics(incomingOperationTypes, performanceCounters,
237 INCOMING_OPERATION_TYPE_NAME);
238 fillWithNonZeroStatistics(listenerEventTypes, performanceCounters, LISTENER_EVENT_TYPE_NAME);
239 fillWithNonZeroStatistics(clientErrorTypes, performanceCounters, CLIENT_ERROR_TYPE_NAME);
242 /** Modifies {@code result} to contain those statistics from {@code map} whose value is > 0. */
243 private static <Key extends Enum<Key>> void fillWithNonZeroStatistics(Map<Key, Integer> map,
244 List<SimplePair<String, Integer>> destination, String typeName) {
245 String prefix = typeName + ".";
246 for (Map.Entry<Key, Integer> entry : map.entrySet()) {
247 if (entry.getValue() > 0) {
248 destination.add(SimplePair.of(prefix + entry.getKey().name(), entry.getValue()));
253 /** Initializes a map from enum names to values of the given {@code keys}. */
254 private static <Key extends Enum<Key>> Map<String, Key> createValueOfMap(Key[] keys) {
255 HashMap<String, Key> map = new HashMap<String, Key>();
256 for (Key key : keys) {
257 map.put(key.name(), key);
262 /** Increments the value of {@code map}[{@code key}] by 1. */
263 private static <Key> void incrementValue(Map<Key, Integer> map, Key key) {
264 map.put(key, TypedUtil.mapGet(map, key) + 1);
267 /** Initializes all values for {@code keys} in {@code map} to be 0. */
268 private static <Key> void initializeMap(Map<Key, Integer> map, Key[] keys) {
269 for (Key key : keys) {
275 public void toCompactString(TextBuilder builder) {
276 List<SimplePair<String, Integer>> nonZeroValues = new ArrayList<SimplePair<String, Integer>>();
277 getNonZeroStatistics(nonZeroValues);
278 builder.appendFormat("Client Statistics: %s\n", nonZeroValues);
282 public StatisticsState marshal() {
283 // Get all the non-zero counters, convert them to proto PropertyRecord messages, and return
284 // a StatisticsState containing the records.
285 List<SimplePair<String, Integer>> counters = new ArrayList<SimplePair<String, Integer>>();
286 getNonZeroStatistics(counters);
287 List<PropertyRecord> propertyRecords = new ArrayList<PropertyRecord>(counters.size());
288 for (SimplePair<String, Integer> counter : counters) {
289 propertyRecords.add(PropertyRecord.create(counter.getFirst(), counter.getSecond()));
291 return StatisticsState.create(propertyRecords);
295 * Given the serialized {@code performanceCounters} of the client statistics, returns a Statistics
296 * object with the performance counter values from {@code performanceCounters}.
299 public static Statistics deserializeStatistics(Logger logger,
300 Collection<PropertyRecord> performanceCounters) {
301 Statistics statistics = new Statistics();
303 // For each counter, parse out the counter name and value.
304 for (PropertyRecord performanceCounter : performanceCounters) {
305 String counterName = performanceCounter.getName();
306 String[] parts = counterName.split("\\.");
307 if (parts.length != 2) {
308 logger.warning("Perf counter name must of form: class.value, skipping: %s", counterName);
311 String className = parts[0];
312 String fieldName = parts[1];
313 int counterValue = performanceCounter.getValue();
315 // Call the relevant method in a loop (i.e., depending on the type of the class).
316 if (TypedUtil.<String>equals(className, SENT_MESSAGE_TYPE_NAME)) {
317 incrementPerformanceCounterValue(logger, SENT_MESSAGE_TYPE_NAME_TO_VALUE_MAP,
318 statistics.sentMessageTypes, fieldName, counterValue);
319 } else if (TypedUtil.<String>equals(className, INCOMING_OPERATION_TYPE_NAME)) {
320 incrementPerformanceCounterValue(logger, INCOMING_OPERATION_TYPE_NAME_TO_VALUE_MAP,
321 statistics.incomingOperationTypes, fieldName, counterValue);
322 } else if (TypedUtil.<String>equals(className, RECEIVED_MESSAGE_TYPE_NAME)) {
323 incrementPerformanceCounterValue(logger, RECEIVED_MESSAGE_TYPE_NAME_TO_VALUE_MAP,
324 statistics.receivedMessageTypes, fieldName, counterValue);
325 } else if (TypedUtil.<String>equals(className, LISTENER_EVENT_TYPE_NAME)) {
326 incrementPerformanceCounterValue(logger, LISTENER_EVENT_TYPE_NAME_TO_VALUE_MAP,
327 statistics.listenerEventTypes, fieldName, counterValue);
328 } else if (TypedUtil.<String>equals(className, CLIENT_ERROR_TYPE_NAME)) {
329 incrementPerformanceCounterValue(logger, CLIENT_ERROR_TYPE_NAME_TO_VALUE_MAP,
330 statistics.clientErrorTypes, fieldName, counterValue);
332 logger.warning("Skipping unknown enum class name %s", className);
339 * Looks for an enum value with the given {@code fieldName} in {@code valueOfMap} and increments
340 * the corresponding entry in {@code counts} by {@code counterValue}. Call to update statistics
341 * for a single performance counter.
343 private static <Key extends Enum<Key>> void incrementPerformanceCounterValue(Logger logger,
344 Map<String, Key> valueOfMap, Map<Key, Integer> counts, String fieldName, int counterValue) {
345 Key type = TypedUtil.mapGet(valueOfMap, fieldName);
347 int currentValue = TypedUtil.mapGet(counts, type);
348 counts.put(type, currentValue + counterValue);
350 logger.warning("Skipping unknown enum value name %s", fieldName);