1 /*
2  * Copyright (C) 2019 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 package com.android.launcher3.logging;
17 
18 
19 import android.util.Log;
20 import java.io.PrintWriter;
21 import java.text.SimpleDateFormat;
22 import java.util.Arrays;
23 import java.util.Date;
24 import java.util.Locale;
25 import java.util.Random;
26 
27 /**
28  * A utility class to record and log events. Events are stored in a fixed size array and old logs
29  * are purged as new events come.
30  */
31 public class EventLogArray {
32 
33     private static final int TYPE_ONE_OFF = 0;
34     private static final int TYPE_FLOAT = 1;
35     private static final int TYPE_INTEGER = 2;
36     private static final int TYPE_BOOL_TRUE = 3;
37     private static final int TYPE_BOOL_FALSE = 4;
38 
39     private final String name;
40     private final EventEntry[] logs;
41     private int nextIndex;
42     private int mLogId;
43 
EventLogArray(String name, int size)44     public EventLogArray(String name, int size) {
45         this.name = name;
46         logs = new EventEntry[size];
47         nextIndex = 0;
48     }
49 
addLog(String event)50     public void addLog(String event) {
51         addLog(TYPE_ONE_OFF, event, 0);
52     }
53 
addLog(String event, int extras)54     public void addLog(String event, int extras) {
55         addLog(TYPE_INTEGER, event, extras);
56     }
57 
addLog(String event, boolean extras)58     public void addLog(String event, boolean extras) {
59         addLog(extras ? TYPE_BOOL_TRUE : TYPE_BOOL_FALSE, event, 0);
60     }
61 
addLog(int type, String event, float extras)62     private void addLog(int type, String event, float extras) {
63         // Merge the logs if its a duplicate
64         int last = (nextIndex + logs.length - 1) % logs.length;
65         int secondLast = (nextIndex + logs.length - 2) % logs.length;
66         if (isEntrySame(logs[last], type, event) && isEntrySame(logs[secondLast], type, event)) {
67             logs[last].update(type, event, extras, mLogId);
68             logs[secondLast].duplicateCount++;
69             return;
70         }
71 
72         if (logs[nextIndex] == null) {
73             logs[nextIndex] = new EventEntry();
74         }
75         logs[nextIndex].update(type, event, extras, mLogId);
76         nextIndex = (nextIndex + 1) % logs.length;
77     }
78 
clear()79     public void clear() {
80         Arrays.setAll(logs, (i) -> null);
81     }
82 
dump(String prefix, PrintWriter writer)83     public void dump(String prefix, PrintWriter writer) {
84         writer.println(prefix + "EventLog (" + name + ") history:");
85         SimpleDateFormat sdf = new SimpleDateFormat("  HH:mm:ss.SSSZ  ", Locale.US);
86         Date date = new Date();
87 
88         for (int i = 0; i < logs.length; i++) {
89             EventEntry log = logs[(nextIndex + logs.length - i - 1) % logs.length];
90             if (log == null) {
91                 continue;
92             }
93             date.setTime(log.time);
94 
95             StringBuilder msg = new StringBuilder(prefix).append(sdf.format(date))
96                     .append(log.event);
97             switch (log.type) {
98                 case TYPE_BOOL_FALSE:
99                     msg.append(": false");
100                     break;
101                 case TYPE_BOOL_TRUE:
102                     msg.append(": true");
103                     break;
104                 case TYPE_FLOAT:
105                     msg.append(": ").append(log.extras);
106                     break;
107                 case TYPE_INTEGER:
108                     msg.append(": ").append((int) log.extras);
109                     break;
110                 default: // fall out
111             }
112             if (log.duplicateCount > 0) {
113                 msg.append(" & ").append(log.duplicateCount).append(" similar events");
114             }
115             msg.append(" traceId: ").append(log.traceId);
116             writer.println(msg);
117         }
118     }
119 
120     /** Returns a 3 digit random number between 100-999 */
generateAndSetLogId()121     public int generateAndSetLogId() {
122         Random r = new Random();
123         mLogId = r.nextInt(900) + 100;
124         return mLogId;
125     }
126 
isEntrySame(EventEntry entry, int type, String event)127     private boolean isEntrySame(EventEntry entry, int type, String event) {
128         return entry != null && entry.type == type && entry.event.equals(event);
129     }
130 
131     /** A single event entry. */
132     private static class EventEntry {
133 
134         private int type;
135         private String event;
136         private float extras;
137         private long time;
138         private int duplicateCount;
139         private int traceId;
140 
update(int type, String event, float extras, int traceId)141         public void update(int type, String event, float extras, int traceId) {
142             this.type = type;
143             this.event = event;
144             this.extras = extras;
145             this.traceId = traceId;
146             time = System.currentTimeMillis();
147             duplicateCount = 0;
148         }
149     }
150 }
151