1 /* 2 * Copyright (C) 2008 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.ddmuilib.log.event; 18 19 import com.android.ddmlib.log.EventContainer; 20 import com.android.ddmlib.log.EventLogParser; 21 import com.android.ddmlib.log.InvalidTypeException; 22 import org.eclipse.swt.widgets.Composite; 23 import org.eclipse.swt.widgets.Control; 24 import org.jfree.chart.labels.CustomXYToolTipGenerator; 25 import org.jfree.chart.plot.XYPlot; 26 import org.jfree.chart.renderer.xy.XYBarRenderer; 27 import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer; 28 import org.jfree.data.time.FixedMillisecond; 29 import org.jfree.data.time.SimpleTimePeriod; 30 import org.jfree.data.time.TimePeriodValues; 31 import org.jfree.data.time.TimePeriodValuesCollection; 32 import org.jfree.data.time.TimeSeries; 33 import org.jfree.data.time.TimeSeriesCollection; 34 import org.jfree.util.ShapeUtilities; 35 36 import java.awt.Color; 37 import java.util.ArrayList; 38 import java.util.List; 39 import java.util.Scanner; 40 import java.util.regex.Pattern; 41 42 public class DisplaySync extends SyncCommon { 43 44 // Information to graph for each authority 45 private TimePeriodValues mDatasetsSync[]; 46 private List<String> mTooltipsSync[]; 47 private CustomXYToolTipGenerator mTooltipGenerators[]; 48 private TimeSeries mDatasetsSyncTickle[]; 49 50 // Dataset of error events to graph 51 private TimeSeries mDatasetError; 52 DisplaySync(String name)53 public DisplaySync(String name) { 54 super(name); 55 } 56 57 /** 58 * Creates the UI for the event display. 59 * @param parent the parent composite. 60 * @param logParser the current log parser. 61 * @return the created control (which may have children). 62 */ 63 @Override createComposite(final Composite parent, EventLogParser logParser, final ILogColumnListener listener)64 public Control createComposite(final Composite parent, EventLogParser logParser, 65 final ILogColumnListener listener) { 66 Control composite = createCompositeChart(parent, logParser, "Sync Status"); 67 resetUI(); 68 return composite; 69 } 70 71 /** 72 * Resets the display. 73 */ 74 @Override resetUI()75 void resetUI() { 76 super.resetUI(); 77 XYPlot xyPlot = mChart.getXYPlot(); 78 79 XYBarRenderer br = new XYBarRenderer(); 80 mDatasetsSync = new TimePeriodValues[NUM_AUTHS]; 81 mTooltipsSync = new List[NUM_AUTHS]; 82 mTooltipGenerators = new CustomXYToolTipGenerator[NUM_AUTHS]; 83 84 TimePeriodValuesCollection tpvc = new TimePeriodValuesCollection(); 85 xyPlot.setDataset(tpvc); 86 xyPlot.setRenderer(0, br); 87 88 XYLineAndShapeRenderer ls = new XYLineAndShapeRenderer(); 89 ls.setBaseLinesVisible(false); 90 mDatasetsSyncTickle = new TimeSeries[NUM_AUTHS]; 91 TimeSeriesCollection tsc = new TimeSeriesCollection(); 92 xyPlot.setDataset(1, tsc); 93 xyPlot.setRenderer(1, ls); 94 95 mDatasetError = new TimeSeries("Errors", FixedMillisecond.class); 96 xyPlot.setDataset(2, new TimeSeriesCollection(mDatasetError)); 97 XYLineAndShapeRenderer errls = new XYLineAndShapeRenderer(); 98 errls.setBaseLinesVisible(false); 99 errls.setSeriesPaint(0, Color.RED); 100 xyPlot.setRenderer(2, errls); 101 102 for (int i = 0; i < NUM_AUTHS; i++) { 103 br.setSeriesPaint(i, AUTH_COLORS[i]); 104 ls.setSeriesPaint(i, AUTH_COLORS[i]); 105 mDatasetsSync[i] = new TimePeriodValues(AUTH_NAMES[i]); 106 tpvc.addSeries(mDatasetsSync[i]); 107 mTooltipsSync[i] = new ArrayList<String>(); 108 mTooltipGenerators[i] = new CustomXYToolTipGenerator(); 109 br.setSeriesToolTipGenerator(i, mTooltipGenerators[i]); 110 mTooltipGenerators[i].addToolTipSeries(mTooltipsSync[i]); 111 112 mDatasetsSyncTickle[i] = new TimeSeries(AUTH_NAMES[i] + " tickle", 113 FixedMillisecond.class); 114 tsc.addSeries(mDatasetsSyncTickle[i]); 115 ls.setSeriesShape(i, ShapeUtilities.createUpTriangle(2.5f)); 116 } 117 } 118 119 /** 120 * Updates the display with a new event. 121 * 122 * @param event The event 123 * @param logParser The parser providing the event. 124 */ 125 @Override newEvent(EventContainer event, EventLogParser logParser)126 void newEvent(EventContainer event, EventLogParser logParser) { 127 super.newEvent(event, logParser); // Handle sync operation 128 try { 129 if (event.mTag == EVENT_TICKLE) { 130 int auth = getAuth(event.getValueAsString(0)); 131 if (auth >= 0) { 132 long msec = (long)event.sec * 1000L + (event.nsec / 1000000L); 133 mDatasetsSyncTickle[auth].addOrUpdate(new FixedMillisecond(msec), -1); 134 } 135 } 136 } catch (InvalidTypeException e) { 137 } 138 } 139 140 /** 141 * Generate the height for an event. 142 * Height is somewhat arbitrarily the count of "things" that happened 143 * during the sync. 144 * When network traffic measurements are available, code should be modified 145 * to use that instead. 146 * @param details The details string associated with the event 147 * @return The height in arbirary units (0-100) 148 */ getHeightFromDetails(String details)149 private int getHeightFromDetails(String details) { 150 if (details == null) { 151 return 1; // Arbitrary 152 } 153 int total = 0; 154 String parts[] = details.split("[a-zA-Z]"); 155 for (String part : parts) { 156 if ("".equals(part)) continue; 157 total += Integer.parseInt(part); 158 } 159 if (total == 0) { 160 total = 1; 161 } 162 return total; 163 } 164 165 /** 166 * Generates the tooltips text for an event. 167 * This method decodes the cryptic details string. 168 * @param auth The authority associated with the event 169 * @param details The details string 170 * @param eventSource server, poll, etc. 171 * @return The text to display in the tooltips 172 */ getTextFromDetails(int auth, String details, int eventSource)173 private String getTextFromDetails(int auth, String details, int eventSource) { 174 175 StringBuffer sb = new StringBuffer(); 176 sb.append(AUTH_NAMES[auth]).append(": \n"); 177 178 Scanner scanner = new Scanner(details); 179 Pattern charPat = Pattern.compile("[a-zA-Z]"); 180 Pattern numPat = Pattern.compile("[0-9]+"); 181 while (scanner.hasNext()) { 182 String key = scanner.findInLine(charPat); 183 int val = Integer.parseInt(scanner.findInLine(numPat)); 184 if (auth == GMAIL && "M".equals(key)) { 185 sb.append("messages from server: ").append(val).append("\n"); 186 } else if (auth == GMAIL && "L".equals(key)) { 187 sb.append("labels from server: ").append(val).append("\n"); 188 } else if (auth == GMAIL && "C".equals(key)) { 189 sb.append("check conversation requests from server: ").append(val).append("\n"); 190 } else if (auth == GMAIL && "A".equals(key)) { 191 sb.append("attachments from server: ").append(val).append("\n"); 192 } else if (auth == GMAIL && "U".equals(key)) { 193 sb.append("op updates from server: ").append(val).append("\n"); 194 } else if (auth == GMAIL && "u".equals(key)) { 195 sb.append("op updates to server: ").append(val).append("\n"); 196 } else if (auth == GMAIL && "S".equals(key)) { 197 sb.append("send/receive cycles: ").append(val).append("\n"); 198 } else if ("Q".equals(key)) { 199 sb.append("queries to server: ").append(val).append("\n"); 200 } else if ("E".equals(key)) { 201 sb.append("entries from server: ").append(val).append("\n"); 202 } else if ("u".equals(key)) { 203 sb.append("updates from client: ").append(val).append("\n"); 204 } else if ("i".equals(key)) { 205 sb.append("inserts from client: ").append(val).append("\n"); 206 } else if ("d".equals(key)) { 207 sb.append("deletes from client: ").append(val).append("\n"); 208 } else if ("f".equals(key)) { 209 sb.append("full sync requested\n"); 210 } else if ("r".equals(key)) { 211 sb.append("partial sync unavailable\n"); 212 } else if ("X".equals(key)) { 213 sb.append("hard error\n"); 214 } else if ("e".equals(key)) { 215 sb.append("number of parse exceptions: ").append(val).append("\n"); 216 } else if ("c".equals(key)) { 217 sb.append("number of conflicts: ").append(val).append("\n"); 218 } else if ("a".equals(key)) { 219 sb.append("number of auth exceptions: ").append(val).append("\n"); 220 } else if ("D".equals(key)) { 221 sb.append("too many deletions\n"); 222 } else if ("R".equals(key)) { 223 sb.append("too many retries: ").append(val).append("\n"); 224 } else if ("b".equals(key)) { 225 sb.append("database error\n"); 226 } else if ("x".equals(key)) { 227 sb.append("soft error\n"); 228 } else if ("l".equals(key)) { 229 sb.append("sync already in progress\n"); 230 } else if ("I".equals(key)) { 231 sb.append("io exception\n"); 232 } else if (auth == CONTACTS && "g".equals(key)) { 233 sb.append("aggregation query: ").append(val).append("\n"); 234 } else if (auth == CONTACTS && "G".equals(key)) { 235 sb.append("aggregation merge: ").append(val).append("\n"); 236 } else if (auth == CONTACTS && "n".equals(key)) { 237 sb.append("num entries: ").append(val).append("\n"); 238 } else if (auth == CONTACTS && "p".equals(key)) { 239 sb.append("photos uploaded from server: ").append(val).append("\n"); 240 } else if (auth == CONTACTS && "P".equals(key)) { 241 sb.append("photos downloaded from server: ").append(val).append("\n"); 242 } else if (auth == CALENDAR && "F".equals(key)) { 243 sb.append("server refresh\n"); 244 } else if (auth == CALENDAR && "s".equals(key)) { 245 sb.append("server diffs fetched\n"); 246 } else { 247 sb.append(key).append("=").append(val); 248 } 249 } 250 if (eventSource == 0) { 251 sb.append("(server)"); 252 } else if (eventSource == 1) { 253 sb.append("(local)"); 254 } else if (eventSource == 2) { 255 sb.append("(poll)"); 256 } else if (eventSource == 3) { 257 sb.append("(user)"); 258 } 259 return sb.toString(); 260 } 261 262 263 /** 264 * Callback to process a sync event. 265 */ 266 @Override processSyncEvent(EventContainer event, int auth, long startTime, long stopTime, String details, boolean newEvent, int syncSource)267 void processSyncEvent(EventContainer event, int auth, long startTime, long stopTime, 268 String details, boolean newEvent, int syncSource) { 269 if (!newEvent) { 270 // Details arrived for a previous sync event 271 // Remove event before reinserting. 272 int lastItem = mDatasetsSync[auth].getItemCount(); 273 mDatasetsSync[auth].delete(lastItem-1, lastItem-1); 274 mTooltipsSync[auth].remove(lastItem-1); 275 } 276 double height = getHeightFromDetails(details); 277 height = height / (stopTime - startTime + 1) * 10000; 278 if (height > 30) { 279 height = 30; 280 } 281 mDatasetsSync[auth].add(new SimpleTimePeriod(startTime, stopTime), height); 282 mTooltipsSync[auth].add(getTextFromDetails(auth, details, syncSource)); 283 mTooltipGenerators[auth].addToolTipSeries(mTooltipsSync[auth]); 284 if (details.indexOf('x') >= 0 || details.indexOf('X') >= 0) { 285 long msec = (long)event.sec * 1000L + (event.nsec / 1000000L); 286 mDatasetError.addOrUpdate(new FixedMillisecond(msec), -1); 287 } 288 } 289 290 /** 291 * Gets display type 292 * 293 * @return display type as an integer 294 */ 295 @Override getDisplayType()296 int getDisplayType() { 297 return DISPLAY_TYPE_SYNC; 298 } 299 } 300