| // 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; | |
| } | |
| } |