511 lines
16 KiB
C#
511 lines
16 KiB
C#
|
// 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;
|
||
|
}
|
||
|
}
|