// Copyright 2012 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Drawing; using System.Text; using System.Windows.Forms; using System.IO; namespace StatsViewer { public partial class StatsViewer : Form { /// /// Create a StatsViewer. /// public StatsViewer() { InitializeComponent(); } #region Protected Methods /// /// Callback when the form loads. /// /// protected override void OnLoad(EventArgs e) { base.OnLoad(e); timer_ = new Timer(); timer_.Interval = kPollInterval; timer_.Tick += new EventHandler(PollTimerTicked); timer_.Start(); } #endregion #region Private Methods /// /// Attempt to open the stats file. /// Return true on success, false otherwise. /// private bool OpenStatsFile() { StatsTable table = new StatsTable(); if (table.Open(kStatsTableName)) { stats_table_ = table; return true; } return false; } /// /// Close the open stats file. /// private void CloseStatsFile() { if (this.stats_table_ != null) { this.stats_table_.Close(); this.stats_table_ = null; this.listViewCounters.Items.Clear(); } } /// /// Updates the process list in the UI. /// private void UpdateProcessList() { int current_pids = comboBoxFilter.Items.Count; int table_pids = stats_table_.Processes.Count; if (current_pids != table_pids + 1) // Add one because of the "all" entry. { int selected_index = this.comboBoxFilter.SelectedIndex; this.comboBoxFilter.Items.Clear(); this.comboBoxFilter.Items.Add(kStringAllProcesses); foreach (int pid in stats_table_.Processes) this.comboBoxFilter.Items.Add(kStringProcess + pid.ToString()); this.comboBoxFilter.SelectedIndex = selected_index; } } /// /// Updates the UI for a counter. /// /// private void UpdateCounter(IStatsCounter counter) { ListView view; // Figure out which list this counter goes into. if (counter is StatsCounterRate) view = listViewRates; else if (counter is StatsCounter || counter is StatsTimer) view = listViewCounters; else return; // Counter type not supported yet. // See if the counter is already in the list. ListViewItem item = view.Items[counter.name]; if (item != null) { // Update an existing counter. Debug.Assert(item is StatsCounterListViewItem); StatsCounterListViewItem counter_item = item as StatsCounterListViewItem; counter_item.Update(counter, filter_pid_); } else { // Create a new counter StatsCounterListViewItem new_item = null; if (counter is StatsCounterRate) new_item = new RateListViewItem(counter, filter_pid_); else if (counter is StatsCounter || counter is StatsTimer) new_item = new CounterListViewItem(counter, filter_pid_); Debug.Assert(new_item != null); view.Items.Add(new_item); } } /// /// Sample the data and update the UI /// private void SampleData() { // If the table isn't open, try to open it again. if (stats_table_ == null) if (!OpenStatsFile()) return; if (stats_counters_ == null) stats_counters_ = stats_table_.Counters(); if (pause_updates_) return; stats_counters_.Update(); UpdateProcessList(); foreach (IStatsCounter counter in stats_counters_) UpdateCounter(counter); } /// /// Set the background color based on the value /// /// /// private void ColorItem(ListViewItem item, int value) { if (value < 0) item.ForeColor = Color.Red; else if (value > 0) item.ForeColor = Color.DarkGreen; else item.ForeColor = Color.Black; } /// /// Called when the timer fires. /// /// /// void PollTimerTicked(object sender, EventArgs e) { SampleData(); } /// /// Called when the interval is changed by the user. /// /// /// private void interval_changed(object sender, EventArgs e) { int interval = 1; if (int.TryParse(comboBoxInterval.Text, out interval)) { if (timer_ != null) { timer_.Stop(); timer_.Interval = interval * 1000; timer_.Start(); } } else { comboBoxInterval.Text = timer_.Interval.ToString(); } } /// /// Called when the user changes the filter /// /// /// private void filter_changed(object sender, EventArgs e) { // While in this event handler, don't allow recursive events! this.comboBoxFilter.SelectedIndexChanged -= new System.EventHandler(this.filter_changed); if (this.comboBoxFilter.Text == kStringAllProcesses) filter_pid_ = 0; else int.TryParse(comboBoxFilter.Text.Substring(kStringProcess.Length), out filter_pid_); SampleData(); this.comboBoxFilter.SelectedIndexChanged += new System.EventHandler(this.filter_changed); } /// /// Callback when the mouse enters a control /// /// /// private void mouse_Enter(object sender, EventArgs e) { // When the dropdown is expanded, we pause // updates, as it messes with the UI. pause_updates_ = true; } /// /// Callback when the mouse leaves a control /// /// /// private void mouse_Leave(object sender, EventArgs e) { pause_updates_ = false; } /// /// Called when the user clicks the zero-stats button. /// /// /// private void buttonZero_Click(object sender, EventArgs e) { this.stats_table_.Zero(); SampleData(); } /// /// Called when the user clicks a column heading. /// /// /// private void column_Click(object sender, ColumnClickEventArgs e) { if (e.Column != sort_column_) { sort_column_ = e.Column; this.listViewCounters.Sorting = SortOrder.Ascending; } else { if (this.listViewCounters.Sorting == SortOrder.Ascending) this.listViewCounters.Sorting = SortOrder.Descending; else this.listViewCounters.Sorting = SortOrder.Ascending; } this.listViewCounters.ListViewItemSorter = new ListViewItemComparer(e.Column, this.listViewCounters.Sorting); this.listViewCounters.Sort(); } /// /// Called when the user clicks the button "Export". /// /// /// private void buttonExport_Click(object sender, EventArgs e) { //Have to pick a textfile to export to. //Saves what is shown in listViewStats in the format: function value //(with a tab in between), so that it is easy to copy paste into a spreadsheet. //(Does not save the delta values.) TextWriter tw = null; try { saveFileDialogExport.CheckFileExists = false; saveFileDialogExport.ShowDialog(); tw = new StreamWriter(saveFileDialogExport.FileName); for (int i = 0; i < listViewCounters.Items.Count; i++) { tw.Write(listViewCounters.Items[i].SubItems[0].Text + "\t"); tw.WriteLine(listViewCounters.Items[i].SubItems[1].Text); } } catch (IOException ex) { MessageBox.Show(string.Format("There was an error while saving your results file. The results might not have been saved correctly.: {0}", ex.Message)); } finally{ if (tw != null) tw.Close(); } } #endregion class ListViewItemComparer : IComparer { public ListViewItemComparer() { this.col_ = 0; this.order_ = SortOrder.Ascending; } public ListViewItemComparer(int column, SortOrder order) { this.col_ = column; this.order_ = order; } public int Compare(object x, object y) { int return_value = -1; object x_tag = ((ListViewItem)x).SubItems[col_].Tag; object y_tag = ((ListViewItem)y).SubItems[col_].Tag; if (Comparable(x_tag, y_tag)) return_value = ((IComparable)x_tag).CompareTo(y_tag); else return_value = String.Compare(((ListViewItem)x).SubItems[col_].Text, ((ListViewItem)y).SubItems[col_].Text); if (order_ == SortOrder.Descending) return_value *= -1; return return_value; } #region Private Methods private bool Comparable(object x, object y) { if (x == null || y == null) return false; return x is IComparable && y is IComparable; } #endregion #region Private Members private int col_; private SortOrder order_; #endregion } #region Private Members private const string kStringAllProcesses = "All Processes"; private const string kStringProcess = "Process "; private const int kPollInterval = 1000; // 1 second private const string kStatsTableName = "ChromeStats"; private StatsTable stats_table_; private StatsTableCounters stats_counters_; private Timer timer_; private int filter_pid_; private bool pause_updates_; private int sort_column_ = -1; #endregion #region Private Event Callbacks private void openToolStripMenuItem_Click(object sender, EventArgs e) { OpenDialog dialog = new OpenDialog(); dialog.ShowDialog(); CloseStatsFile(); StatsTable table = new StatsTable(); bool rv = table.Open(dialog.FileName); if (!rv) { MessageBox.Show("Could not open statsfile: " + dialog.FileName); } else { stats_table_ = table; } } private void closeToolStripMenuItem_Click(object sender, EventArgs e) { CloseStatsFile(); } private void quitToolStripMenuItem_Click(object sender, EventArgs e) { Application.Exit(); } #endregion } /// /// Base class for counter list view items. /// internal class StatsCounterListViewItem : ListViewItem { /// /// Create the ListViewItem /// /// public StatsCounterListViewItem(string text) : base(text) { } /// /// Update the ListViewItem given a new counter value. /// /// /// public virtual void Update(IStatsCounter counter, int filter_pid) { } /// /// Set the background color based on the value /// /// protected void ColorItem(int value) { if (value < 0) ForeColor = Color.Red; else if (value > 0) ForeColor = Color.DarkGreen; else ForeColor = Color.Black; } /// /// Create a new subitem with a zeroed Tag. /// /// protected ListViewSubItem NewSubItem() { ListViewSubItem item = new ListViewSubItem(); item.Tag = -1; // Arbitrarily initialize to -1. return item; } /// /// Set the value for a subitem. /// /// /// /// True if the value changed, false otherwise protected bool SetSubItem(ListViewSubItem item, int val) { // The reason for doing this extra compare is because // we introduce flicker if we unnecessarily update the // subitems. The UI is much less likely to cause you // a seizure when we do this. if (val != (int)item.Tag) { item.Text = val.ToString(); item.Tag = val; return true; } return false; } } /// /// A listview item which contains a rate. /// internal class RateListViewItem : StatsCounterListViewItem { public RateListViewItem(IStatsCounter ctr, int filter_pid) : base(ctr.name) { StatsCounterRate rate = ctr as StatsCounterRate; Name = rate.name; SubItems.Add(NewSubItem()); SubItems.Add(NewSubItem()); SubItems.Add(NewSubItem()); Update(ctr, filter_pid); } public override void Update(IStatsCounter counter, int filter_pid) { Debug.Assert(counter is StatsCounterRate); StatsCounterRate new_rate = counter as StatsCounterRate; int new_count = new_rate.GetCount(filter_pid); int new_time = new_rate.GetTime(filter_pid); int old_avg = Tag != null ? (int)Tag : 0; int new_avg = new_count > 0 ? (new_time / new_count) : 0; int delta = new_avg - old_avg; SetSubItem(SubItems[column_count_index], new_count); SetSubItem(SubItems[column_time_index], new_time); if (SetSubItem(SubItems[column_avg_index], new_avg)) ColorItem(delta); Tag = new_avg; } private const int column_count_index = 1; private const int column_time_index = 2; private const int column_avg_index = 3; } /// /// A listview item which contains a counter. /// internal class CounterListViewItem : StatsCounterListViewItem { public CounterListViewItem(IStatsCounter ctr, int filter_pid) : base(ctr.name) { Name = ctr.name; SubItems.Add(NewSubItem()); SubItems.Add(NewSubItem()); Update(ctr, filter_pid); } public override void Update(IStatsCounter counter, int filter_pid) { Debug.Assert(counter is StatsCounter || counter is StatsTimer); int new_value = 0; if (counter is StatsCounter) new_value = ((StatsCounter)counter).GetValue(filter_pid); else if (counter is StatsTimer) new_value = ((StatsTimer)counter).GetValue(filter_pid); int old_value = Tag != null ? (int)Tag : 0; int delta = new_value - old_value; SetSubItem(SubItems[column_value_index], new_value); if (SetSubItem(SubItems[column_delta_index], delta)) ColorItem(delta); Tag = new_value; } private const int column_value_index = 1; private const int column_delta_index = 2; } }