| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // 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 { |
| /// <summary> |
| /// Create a StatsViewer. |
| /// </summary> |
| public StatsViewer() { |
| InitializeComponent(); |
| } |
| |
| #region Protected Methods |
| /// <summary> |
| /// Callback when the form loads. |
| /// </summary> |
| /// <param name="e"></param> |
| 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 |
| /// <summary> |
| /// Attempt to open the stats file. |
| /// Return true on success, false otherwise. |
| /// </summary> |
| private bool OpenStatsFile() { |
| StatsTable table = new StatsTable(); |
| if (table.Open(kStatsTableName)) { |
| stats_table_ = table; |
| return true; |
| } |
| return false; |
| } |
| |
| /// <summary> |
| /// Close the open stats file. |
| /// </summary> |
| private void CloseStatsFile() { |
| if (this.stats_table_ != null) |
| { |
| this.stats_table_.Close(); |
| this.stats_table_ = null; |
| this.listViewCounters.Items.Clear(); |
| } |
| } |
| |
| /// <summary> |
| /// Updates the process list in the UI. |
| /// </summary> |
| 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; |
| } |
| } |
| |
| /// <summary> |
| /// Updates the UI for a counter. |
| /// </summary> |
| /// <param name="counter"></param> |
| 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); |
| } |
| } |
| |
| /// <summary> |
| /// Sample the data and update the UI |
| /// </summary> |
| 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); |
| } |
| |
| /// <summary> |
| /// Set the background color based on the value |
| /// </summary> |
| /// <param name="item"></param> |
| /// <param name="value"></param> |
| 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; |
| } |
| |
| /// <summary> |
| /// Called when the timer fires. |
| /// </summary> |
| /// <param name="sender"></param> |
| /// <param name="e"></param> |
| void PollTimerTicked(object sender, EventArgs e) { |
| SampleData(); |
| } |
| |
| /// <summary> |
| /// Called when the interval is changed by the user. |
| /// </summary> |
| /// <param name="sender"></param> |
| /// <param name="e"></param> |
| 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(); |
| } |
| } |
| |
| /// <summary> |
| /// Called when the user changes the filter |
| /// </summary> |
| /// <param name="sender"></param> |
| /// <param name="e"></param> |
| 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); |
| } |
| |
| /// <summary> |
| /// Callback when the mouse enters a control |
| /// </summary> |
| /// <param name="sender"></param> |
| /// <param name="e"></param> |
| 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; |
| } |
| |
| /// <summary> |
| /// Callback when the mouse leaves a control |
| /// </summary> |
| /// <param name="sender"></param> |
| /// <param name="e"></param> |
| private void mouse_Leave(object sender, EventArgs e) { |
| pause_updates_ = false; |
| } |
| |
| /// <summary> |
| /// Called when the user clicks the zero-stats button. |
| /// </summary> |
| /// <param name="sender"></param> |
| /// <param name="e"></param> |
| private void buttonZero_Click(object sender, EventArgs e) { |
| this.stats_table_.Zero(); |
| SampleData(); |
| } |
| |
| /// <summary> |
| /// Called when the user clicks a column heading. |
| /// </summary> |
| /// <param name="sender"></param> |
| /// <param name="e"></param> |
| 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(); |
| } |
| |
| /// <summary> |
| /// Called when the user clicks the button "Export". |
| /// </summary> |
| /// <param name="sender"></param> |
| /// <param name="e"></param> |
| 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 |
| } |
| |
| /// <summary> |
| /// Base class for counter list view items. |
| /// </summary> |
| internal class StatsCounterListViewItem : ListViewItem |
| { |
| /// <summary> |
| /// Create the ListViewItem |
| /// </summary> |
| /// <param name="text"></param> |
| public StatsCounterListViewItem(string text) : base(text) { } |
| |
| /// <summary> |
| /// Update the ListViewItem given a new counter value. |
| /// </summary> |
| /// <param name="counter"></param> |
| /// <param name="filter_pid"></param> |
| public virtual void Update(IStatsCounter counter, int filter_pid) { } |
| |
| /// <summary> |
| /// Set the background color based on the value |
| /// </summary> |
| /// <param name="value"></param> |
| protected void ColorItem(int value) |
| { |
| if (value < 0) |
| ForeColor = Color.Red; |
| else if (value > 0) |
| ForeColor = Color.DarkGreen; |
| else |
| ForeColor = Color.Black; |
| } |
| |
| /// <summary> |
| /// Create a new subitem with a zeroed Tag. |
| /// </summary> |
| /// <returns></returns> |
| protected ListViewSubItem NewSubItem() |
| { |
| ListViewSubItem item = new ListViewSubItem(); |
| item.Tag = -1; // Arbitrarily initialize to -1. |
| return item; |
| } |
| |
| /// <summary> |
| /// Set the value for a subitem. |
| /// </summary> |
| /// <param name="item"></param> |
| /// <param name="val"></param> |
| /// <returns>True if the value changed, false otherwise</returns> |
| 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; |
| } |
| } |
| |
| /// <summary> |
| /// A listview item which contains a rate. |
| /// </summary> |
| 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; |
| } |
| |
| /// <summary> |
| /// A listview item which contains a counter. |
| /// </summary> |
| 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; |
| } |
| } |