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 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 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 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 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 */ 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 */ 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 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 296 int getDisplayType() { 297 return DISPLAY_TYPE_SYNC; 298 } 299 } 300