1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 using System; 6 using System.Collections; 7 using System.Collections.Generic; 8 using System.ComponentModel; 9 using System.Data; 10 using System.Diagnostics; 11 using System.Drawing; 12 using System.Text; 13 using System.Windows.Forms; 14 using System.IO; 15 16 namespace StatsViewer { 17 public partial class StatsViewer : Form { 18 /// <summary> 19 /// Create a StatsViewer. 20 /// </summary> StatsViewer()21 public StatsViewer() { 22 InitializeComponent(); 23 } 24 25 #region Protected Methods 26 /// <summary> 27 /// Callback when the form loads. 28 /// </summary> 29 /// <param name="e"></param> OnLoad(EventArgs e)30 protected override void OnLoad(EventArgs e) { 31 base.OnLoad(e); 32 33 timer_ = new Timer(); 34 timer_.Interval = kPollInterval; 35 timer_.Tick += new EventHandler(PollTimerTicked); 36 timer_.Start(); 37 } 38 #endregion 39 40 #region Private Methods 41 /// <summary> 42 /// Attempt to open the stats file. 43 /// Return true on success, false otherwise. 44 /// </summary> OpenStatsFile()45 private bool OpenStatsFile() { 46 StatsTable table = new StatsTable(); 47 if (table.Open(kStatsTableName)) { 48 stats_table_ = table; 49 return true; 50 } 51 return false; 52 } 53 54 /// <summary> 55 /// Close the open stats file. 56 /// </summary> CloseStatsFile()57 private void CloseStatsFile() { 58 if (this.stats_table_ != null) 59 { 60 this.stats_table_.Close(); 61 this.stats_table_ = null; 62 this.listViewCounters.Items.Clear(); 63 } 64 } 65 66 /// <summary> 67 /// Updates the process list in the UI. 68 /// </summary> UpdateProcessList()69 private void UpdateProcessList() { 70 int current_pids = comboBoxFilter.Items.Count; 71 int table_pids = stats_table_.Processes.Count; 72 if (current_pids != table_pids + 1) // Add one because of the "all" entry. 73 { 74 int selected_index = this.comboBoxFilter.SelectedIndex; 75 this.comboBoxFilter.Items.Clear(); 76 this.comboBoxFilter.Items.Add(kStringAllProcesses); 77 foreach (int pid in stats_table_.Processes) 78 this.comboBoxFilter.Items.Add(kStringProcess + pid.ToString()); 79 this.comboBoxFilter.SelectedIndex = selected_index; 80 } 81 } 82 83 /// <summary> 84 /// Updates the UI for a counter. 85 /// </summary> 86 /// <param name="counter"></param> UpdateCounter(IStatsCounter counter)87 private void UpdateCounter(IStatsCounter counter) { 88 ListView view; 89 90 // Figure out which list this counter goes into. 91 if (counter is StatsCounterRate) 92 view = listViewRates; 93 else if (counter is StatsCounter || counter is StatsTimer) 94 view = listViewCounters; 95 else 96 return; // Counter type not supported yet. 97 98 // See if the counter is already in the list. 99 ListViewItem item = view.Items[counter.name]; 100 if (item != null) 101 { 102 // Update an existing counter. 103 Debug.Assert(item is StatsCounterListViewItem); 104 StatsCounterListViewItem counter_item = item as StatsCounterListViewItem; 105 counter_item.Update(counter, filter_pid_); 106 } 107 else 108 { 109 // Create a new counter 110 StatsCounterListViewItem new_item = null; 111 if (counter is StatsCounterRate) 112 new_item = new RateListViewItem(counter, filter_pid_); 113 else if (counter is StatsCounter || counter is StatsTimer) 114 new_item = new CounterListViewItem(counter, filter_pid_); 115 Debug.Assert(new_item != null); 116 view.Items.Add(new_item); 117 } 118 } 119 120 /// <summary> 121 /// Sample the data and update the UI 122 /// </summary> SampleData()123 private void SampleData() { 124 // If the table isn't open, try to open it again. 125 if (stats_table_ == null) 126 if (!OpenStatsFile()) 127 return; 128 129 if (stats_counters_ == null) 130 stats_counters_ = stats_table_.Counters(); 131 132 if (pause_updates_) 133 return; 134 135 stats_counters_.Update(); 136 137 UpdateProcessList(); 138 139 foreach (IStatsCounter counter in stats_counters_) 140 UpdateCounter(counter); 141 } 142 143 /// <summary> 144 /// Set the background color based on the value 145 /// </summary> 146 /// <param name="item"></param> 147 /// <param name="value"></param> ColorItem(ListViewItem item, int value)148 private void ColorItem(ListViewItem item, int value) 149 { 150 if (value < 0) 151 item.ForeColor = Color.Red; 152 else if (value > 0) 153 item.ForeColor = Color.DarkGreen; 154 else 155 item.ForeColor = Color.Black; 156 } 157 158 /// <summary> 159 /// Called when the timer fires. 160 /// </summary> 161 /// <param name="sender"></param> 162 /// <param name="e"></param> PollTimerTicked(object sender, EventArgs e)163 void PollTimerTicked(object sender, EventArgs e) { 164 SampleData(); 165 } 166 167 /// <summary> 168 /// Called when the interval is changed by the user. 169 /// </summary> 170 /// <param name="sender"></param> 171 /// <param name="e"></param> interval_changed(object sender, EventArgs e)172 private void interval_changed(object sender, EventArgs e) { 173 int interval = 1; 174 if (int.TryParse(comboBoxInterval.Text, out interval)) { 175 if (timer_ != null) { 176 timer_.Stop(); 177 timer_.Interval = interval * 1000; 178 timer_.Start(); 179 } 180 } else { 181 comboBoxInterval.Text = timer_.Interval.ToString(); 182 } 183 } 184 185 /// <summary> 186 /// Called when the user changes the filter 187 /// </summary> 188 /// <param name="sender"></param> 189 /// <param name="e"></param> filter_changed(object sender, EventArgs e)190 private void filter_changed(object sender, EventArgs e) { 191 // While in this event handler, don't allow recursive events! 192 this.comboBoxFilter.SelectedIndexChanged -= new System.EventHandler(this.filter_changed); 193 if (this.comboBoxFilter.Text == kStringAllProcesses) 194 filter_pid_ = 0; 195 else 196 int.TryParse(comboBoxFilter.Text.Substring(kStringProcess.Length), out filter_pid_); 197 SampleData(); 198 this.comboBoxFilter.SelectedIndexChanged += new System.EventHandler(this.filter_changed); 199 } 200 201 /// <summary> 202 /// Callback when the mouse enters a control 203 /// </summary> 204 /// <param name="sender"></param> 205 /// <param name="e"></param> mouse_Enter(object sender, EventArgs e)206 private void mouse_Enter(object sender, EventArgs e) { 207 // When the dropdown is expanded, we pause 208 // updates, as it messes with the UI. 209 pause_updates_ = true; 210 } 211 212 /// <summary> 213 /// Callback when the mouse leaves a control 214 /// </summary> 215 /// <param name="sender"></param> 216 /// <param name="e"></param> mouse_Leave(object sender, EventArgs e)217 private void mouse_Leave(object sender, EventArgs e) { 218 pause_updates_ = false; 219 } 220 221 /// <summary> 222 /// Called when the user clicks the zero-stats button. 223 /// </summary> 224 /// <param name="sender"></param> 225 /// <param name="e"></param> buttonZero_Click(object sender, EventArgs e)226 private void buttonZero_Click(object sender, EventArgs e) { 227 this.stats_table_.Zero(); 228 SampleData(); 229 } 230 231 /// <summary> 232 /// Called when the user clicks a column heading. 233 /// </summary> 234 /// <param name="sender"></param> 235 /// <param name="e"></param> column_Click(object sender, ColumnClickEventArgs e)236 private void column_Click(object sender, ColumnClickEventArgs e) { 237 if (e.Column != sort_column_) { 238 sort_column_ = e.Column; 239 this.listViewCounters.Sorting = SortOrder.Ascending; 240 } else { 241 if (this.listViewCounters.Sorting == SortOrder.Ascending) 242 this.listViewCounters.Sorting = SortOrder.Descending; 243 else 244 this.listViewCounters.Sorting = SortOrder.Ascending; 245 } 246 247 this.listViewCounters.ListViewItemSorter = 248 new ListViewItemComparer(e.Column, this.listViewCounters.Sorting); 249 this.listViewCounters.Sort(); 250 } 251 252 /// <summary> 253 /// Called when the user clicks the button "Export". 254 /// </summary> 255 /// <param name="sender"></param> 256 /// <param name="e"></param> buttonExport_Click(object sender, EventArgs e)257 private void buttonExport_Click(object sender, EventArgs e) { 258 //Have to pick a textfile to export to. 259 //Saves what is shown in listViewStats in the format: function value 260 //(with a tab in between), so that it is easy to copy paste into a spreadsheet. 261 //(Does not save the delta values.) 262 TextWriter tw = null; 263 try { 264 saveFileDialogExport.CheckFileExists = false; 265 saveFileDialogExport.ShowDialog(); 266 tw = new StreamWriter(saveFileDialogExport.FileName); 267 268 for (int i = 0; i < listViewCounters.Items.Count; i++) { 269 tw.Write(listViewCounters.Items[i].SubItems[0].Text + "\t"); 270 tw.WriteLine(listViewCounters.Items[i].SubItems[1].Text); 271 } 272 } 273 catch (IOException ex) { 274 MessageBox.Show(string.Format("There was an error while saving your results file. The results might not have been saved correctly.: {0}", ex.Message)); 275 } 276 finally{ 277 if (tw != null) tw.Close(); 278 } 279 } 280 281 #endregion 282 283 class ListViewItemComparer : IComparer { ListViewItemComparer()284 public ListViewItemComparer() { 285 this.col_ = 0; 286 this.order_ = SortOrder.Ascending; 287 } 288 ListViewItemComparer(int column, SortOrder order)289 public ListViewItemComparer(int column, SortOrder order) { 290 this.col_ = column; 291 this.order_ = order; 292 } 293 Compare(object x, object y)294 public int Compare(object x, object y) { 295 int return_value = -1; 296 297 object x_tag = ((ListViewItem)x).SubItems[col_].Tag; 298 object y_tag = ((ListViewItem)y).SubItems[col_].Tag; 299 300 if (Comparable(x_tag, y_tag)) 301 return_value = ((IComparable)x_tag).CompareTo(y_tag); 302 else 303 return_value = String.Compare(((ListViewItem)x).SubItems[col_].Text, 304 ((ListViewItem)y).SubItems[col_].Text); 305 306 if (order_ == SortOrder.Descending) 307 return_value *= -1; 308 309 return return_value; 310 } 311 312 #region Private Methods Comparable(object x, object y)313 private bool Comparable(object x, object y) { 314 if (x == null || y == null) 315 return false; 316 317 return x is IComparable && y is IComparable; 318 } 319 #endregion 320 321 #region Private Members 322 private int col_; 323 private SortOrder order_; 324 #endregion 325 } 326 327 #region Private Members 328 private const string kStringAllProcesses = "All Processes"; 329 private const string kStringProcess = "Process "; 330 private const int kPollInterval = 1000; // 1 second 331 private const string kStatsTableName = "ChromeStats"; 332 private StatsTable stats_table_; 333 private StatsTableCounters stats_counters_; 334 private Timer timer_; 335 private int filter_pid_; 336 private bool pause_updates_; 337 private int sort_column_ = -1; 338 #endregion 339 340 #region Private Event Callbacks openToolStripMenuItem_Click(object sender, EventArgs e)341 private void openToolStripMenuItem_Click(object sender, EventArgs e) 342 { 343 OpenDialog dialog = new OpenDialog(); 344 dialog.ShowDialog(); 345 346 CloseStatsFile(); 347 348 StatsTable table = new StatsTable(); 349 bool rv = table.Open(dialog.FileName); 350 if (!rv) 351 { 352 MessageBox.Show("Could not open statsfile: " + dialog.FileName); 353 } 354 else 355 { 356 stats_table_ = table; 357 } 358 } 359 closeToolStripMenuItem_Click(object sender, EventArgs e)360 private void closeToolStripMenuItem_Click(object sender, EventArgs e) 361 { 362 CloseStatsFile(); 363 } 364 quitToolStripMenuItem_Click(object sender, EventArgs e)365 private void quitToolStripMenuItem_Click(object sender, EventArgs e) 366 { 367 Application.Exit(); 368 } 369 #endregion 370 } 371 372 /// <summary> 373 /// Base class for counter list view items. 374 /// </summary> 375 internal class StatsCounterListViewItem : ListViewItem 376 { 377 /// <summary> 378 /// Create the ListViewItem 379 /// </summary> 380 /// <param name="text"></param> StatsCounterListViewItem(string text)381 public StatsCounterListViewItem(string text) : base(text) { } 382 383 /// <summary> 384 /// Update the ListViewItem given a new counter value. 385 /// </summary> 386 /// <param name="counter"></param> 387 /// <param name="filter_pid"></param> Update(IStatsCounter counter, int filter_pid)388 public virtual void Update(IStatsCounter counter, int filter_pid) { } 389 390 /// <summary> 391 /// Set the background color based on the value 392 /// </summary> 393 /// <param name="value"></param> ColorItem(int value)394 protected void ColorItem(int value) 395 { 396 if (value < 0) 397 ForeColor = Color.Red; 398 else if (value > 0) 399 ForeColor = Color.DarkGreen; 400 else 401 ForeColor = Color.Black; 402 } 403 404 /// <summary> 405 /// Create a new subitem with a zeroed Tag. 406 /// </summary> 407 /// <returns></returns> NewSubItem()408 protected ListViewSubItem NewSubItem() 409 { 410 ListViewSubItem item = new ListViewSubItem(); 411 item.Tag = -1; // Arbitrarily initialize to -1. 412 return item; 413 } 414 415 /// <summary> 416 /// Set the value for a subitem. 417 /// </summary> 418 /// <param name="item"></param> 419 /// <param name="val"></param> 420 /// <returns>True if the value changed, false otherwise</returns> SetSubItem(ListViewSubItem item, int val)421 protected bool SetSubItem(ListViewSubItem item, int val) 422 { 423 // The reason for doing this extra compare is because 424 // we introduce flicker if we unnecessarily update the 425 // subitems. The UI is much less likely to cause you 426 // a seizure when we do this. 427 if (val != (int)item.Tag) 428 { 429 item.Text = val.ToString(); 430 item.Tag = val; 431 return true; 432 } 433 return false; 434 } 435 } 436 437 /// <summary> 438 /// A listview item which contains a rate. 439 /// </summary> 440 internal class RateListViewItem : StatsCounterListViewItem 441 { RateListViewItem(IStatsCounter ctr, int filter_pid)442 public RateListViewItem(IStatsCounter ctr, int filter_pid) : 443 base(ctr.name) 444 { 445 StatsCounterRate rate = ctr as StatsCounterRate; 446 Name = rate.name; 447 SubItems.Add(NewSubItem()); 448 SubItems.Add(NewSubItem()); 449 SubItems.Add(NewSubItem()); 450 Update(ctr, filter_pid); 451 } 452 Update(IStatsCounter counter, int filter_pid)453 public override void Update(IStatsCounter counter, int filter_pid) 454 { 455 Debug.Assert(counter is StatsCounterRate); 456 457 StatsCounterRate new_rate = counter as StatsCounterRate; 458 int new_count = new_rate.GetCount(filter_pid); 459 int new_time = new_rate.GetTime(filter_pid); 460 int old_avg = Tag != null ? (int)Tag : 0; 461 int new_avg = new_count > 0 ? (new_time / new_count) : 0; 462 int delta = new_avg - old_avg; 463 464 SetSubItem(SubItems[column_count_index], new_count); 465 SetSubItem(SubItems[column_time_index], new_time); 466 if (SetSubItem(SubItems[column_avg_index], new_avg)) 467 ColorItem(delta); 468 Tag = new_avg; 469 } 470 471 private const int column_count_index = 1; 472 private const int column_time_index = 2; 473 private const int column_avg_index = 3; 474 } 475 476 /// <summary> 477 /// A listview item which contains a counter. 478 /// </summary> 479 internal class CounterListViewItem : StatsCounterListViewItem 480 { CounterListViewItem(IStatsCounter ctr, int filter_pid)481 public CounterListViewItem(IStatsCounter ctr, int filter_pid) : 482 base(ctr.name) 483 { 484 Name = ctr.name; 485 SubItems.Add(NewSubItem()); 486 SubItems.Add(NewSubItem()); 487 Update(ctr, filter_pid); 488 } 489 Update(IStatsCounter counter, int filter_pid)490 public override void Update(IStatsCounter counter, int filter_pid) { 491 Debug.Assert(counter is StatsCounter || counter is StatsTimer); 492 493 int new_value = 0; 494 if (counter is StatsCounter) 495 new_value = ((StatsCounter)counter).GetValue(filter_pid); 496 else if (counter is StatsTimer) 497 new_value = ((StatsTimer)counter).GetValue(filter_pid); 498 499 int old_value = Tag != null ? (int)Tag : 0; 500 int delta = new_value - old_value; 501 SetSubItem(SubItems[column_value_index], new_value); 502 if (SetSubItem(SubItems[column_delta_index], delta)) 503 ColorItem(delta); 504 Tag = new_value; 505 } 506 507 private const int column_value_index = 1; 508 private const int column_delta_index = 2; 509 } 510 } 511