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