Dryad/JobBrowser/JobBrowser/LogViewer.cs

347 lines
12 KiB
C#

/*
Copyright (c) Microsoft Corporation
All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
compliance with the License. You may obtain a copy of the License
at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER
EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF
TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions and
limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Windows.Forms;
using Microsoft.Research.JobObjectModel;
using Microsoft.Research.Tools;
namespace Microsoft.Research.DryadAnalysis
{
/// <summary>
/// A log viewer displays fragments of logs or other text files.
/// </summary>
public partial class LogViewer : Form
{
bool canceled;
DGVData<TextFileLine> shownText;
DGVData<PositionedDryadLogEntry> shownLogLines;
StatusWriter status;
/// <summary>
/// A text file line is represented in this way.
/// </summary>
class TextFileLine
{
private long Line;
private string Contents;
public TextFileLine(long line, string contents)
{
this.Line = line;
this.Contents = contents;
}
}
/// <summary>
/// True if the form has been closed (or work has been cancelled in some way).
/// </summary>
public bool Cancelled { get { return this.canceled; } }
private void Initialize(bool textFile, string header)
{
this.Text = header;
this.status = new StatusWriter(this.toolStripStatusLabel_status, this.statusStrip, this.Status);
this.canceled = false;
if (textFile)
{
this.shownText = new DGVData<TextFileLine>();
this.filteredDataGridView.SetDataSource(this.shownText);
// ReSharper disable PossibleNullReferenceException
this.filteredDataGridView.DataGridView.Columns["Line"].FillWeight = 5;
this.filteredDataGridView.DataGridView.Columns["Line"].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
this.contextMenuStrip.Enabled = false;
}
else
{
this.shownLogLines = new DGVData<PositionedDryadLogEntry>();
this.filteredDataGridView.SetDataSource(this.shownLogLines);
foreach (string s in new string[] { "Malformed", "IsError", "OriginalLogLine", "File", "LineNo" })
{
this.filteredDataGridView.DataGridView.Columns[s].Visible = false;
}
this.filteredDataGridView.DataGridView.Columns["Timestamp"].DefaultCellStyle.Format = "HH:mm:ss.fff";
// ReSharper restore PossibleNullReferenceException
}
this.filteredDataGridView.DataGridView.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.DisplayedCellsExceptHeader;
this.filteredDataGridView.DataGridView.AllowUserToResizeColumns = true;
this.filteredDataGridView.DataGridView.Columns[this.filteredDataGridView.DataGridView.Columns.Count - 1].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
}
/// <summary>
/// Create a new log viewer.
/// </summary>
/// <param name="textFile">If true the viewer will display a text file.</param>
/// <param name="header">Information to display in the window header.</param>
public LogViewer(bool textFile, string header)
{
this.InitializeComponent();
this.Initialize(textFile, header);
}
/// <summary>
/// Create a new log viewer to display a file.
/// </summary>
public LogViewer()
{
this.InitializeComponent();
}
/// <summary>
/// Load a specified file.
/// </summary>
/// <param name="file">File to load.</param>
public void LoadFile(IClusterResidentObject file)
{
string filename = file.Name;
bool text = true;
string basefilename = Path.GetFileName(filename);
if (basefilename != null && (basefilename.EndsWith(".log") && basefilename.StartsWith("cosmos")))
text = false;
long len = file.Size;
this.Initialize(text, basefilename);
//ISharedStreamReader sr = new FileSharedStreamReader(filename);
ISharedStreamReader sr = file.GetStream();
long lineno = 0;
long bytes = 0;
List<TextFileLine> toAddText = new List<TextFileLine>();
List<PositionedDryadLogEntry> toAddLog = new List<PositionedDryadLogEntry>();
while (!sr.EndOfStream)
{
string line = sr.ReadLine();
bytes += line.Length;
if (this.shownText != null)
toAddText.Add(new TextFileLine(lineno, line));
else
{
PositionedDryadLogEntry cle = new PositionedDryadLogEntry(filename, lineno, line);
if (cle.Malformed)
{
Trace.TraceInformation("Malformed log entry: " + cle.OriginalLogLine);
}
else
{
toAddLog.Add(cle);
}
}
if (lineno++ % 100 == 0 && len > 0)
{
this.UpdateProgress((int)(bytes * 100 / len));
}
}
if (this.shownText != null)
this.shownText.SetItems(toAddText);
else
this.shownLogLines.SetItems(toAddLog);
this.Status("Loaded " + lineno + " lines.", StatusKind.OK);
this.UpdateProgress(100);
sr.Close();
this.filteredDataGridView.DataGridView.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.DisplayedCellsExceptHeader);
}
/// <summary>
/// Display a status message.
/// </summary>
/// <param name="message">Message to display.</param>
/// <param name="statusKind">Message severity.</param>
public void Status(string message, StatusKind statusKind)
{
if (this.canceled || this.IsDisposed)
return;
this.status.Status(message, statusKind);
}
/// <summary>
/// Add a text line to the viewer.
/// </summary>
/// <param name="lineno">Line number to add.</param>
/// <param name="text">Text of the line.</param>
/// <param name="file">File that the information comes from.</param>
public void AddLine(string file, long lineno, string text)
{
if (this.canceled || this.IsDisposed)
return;
if (this.InvokeRequired)
{
var action = new Action<LogViewer, string, long, string>( (lv, f, l, t) => lv.AddLine(f, l, t));
this.Invoke(action, this, file, lineno, text);
}
else
{
if (this.shownText != null)
{
TextFileLine tfl = new TextFileLine(lineno, text);
this.shownText.AddItem(tfl);
}
else
{
PositionedDryadLogEntry cle = new PositionedDryadLogEntry(file, lineno, text);
if (cle.Malformed)
return;
this.shownLogLines.AddItem(cle);
}
return;
}
}
/// <summary>
/// Update the progress bar.
/// </summary>
/// <param name="percent">How much has been done.</param>
public void UpdateProgress(int percent)
{
if (this.InvokeRequired)
{
this.Invoke(new Action<int>(this.UpdateProgress), percent);
}
else
{
if (this.canceled || this.IsDisposed)
return;
try
{
this.toolStripProgressBar.Value = percent;
}
catch
{ }
}
}
/// <summary>
/// Close the log viewer.
/// </summary>
/// <param name="sender">Unused.</param>
/// <param name="e">Unused.</param>
private void closeToolStripMenuItem_Click(object sender, EventArgs e)
{
this.canceled = true;
this.Close();
}
/// <summary>
/// We are done adding information.
/// </summary>
internal void Done()
{
if (this.canceled || this.IsDisposed)
return;
this.UpdateProgress(100);
this.Status("OK", StatusKind.OK);
}
private void LogViewer_FormClosing(object sender, FormClosingEventArgs e)
{
this.canceled = true;
}
/// <summary>
/// User wants to the the location of the message.
/// </summary>
/// <param name="sender">Unused.</param>
/// <param name="e">Row selected.</param>
private void locationToolStripMenuItem_Click(object sender, EventArgs e)
{
string position = "";
var rows = this.filteredDataGridView.DataGridView.SelectedRows;
for (int i = 0; i < rows.Count; i++)
{
PositionedDryadLogEntry entry = ((PositionedDryadLogEntry)rows[i].DataBoundItem);
position += entry.File + ":" + entry.LineNo + Environment.NewLine;
}
MessageBox.Show(position, "File containing log entries");
}
}
/// <summary>
/// A line from a file.
/// </summary>
/// <typeparam name="T">Type of line representation.</typeparam>
public class PositionedLine<T>
where T : IParse, new()
{
/// <summary>
/// File containing the log entry.
/// </summary>
public string File { get; protected set; }
/// <summary>
/// Line number.
/// </summary>
public long LineNo { get; protected set; }
/// <summary>
/// Actual line from file.
/// </summary>
public T Line { get; protected set; }
/// <summary>
/// Create a positioned line.
/// </summary>
/// <param name="file">File containing the line.</param>
/// <param name="lineno">Line number in file.</param>
/// <param name="line">Actual line in file.</param>
public PositionedLine(string file, long lineno, string line)
{
this.File = file;
this.LineNo = lineno;
this.Line = new T();
this.Line.Parse(line);
}
}
/// <summary>
/// Cosmos log entry with position information.
/// </summary>
public class PositionedDryadLogEntry : DryadLogEntry
{
/// <summary>
/// File containing the log entry.
/// </summary>
public string File { get; protected set; }
/// <summary>
/// Line number.
/// </summary>
public long LineNo { get; protected set; }
/// <summary>
/// Create a positioned log entry.
/// </summary>
/// <param name="file">File containing the log entry.</param>
/// <param name="lineno">Line number.</param>
/// <param name="line">Line contents.</param>
public PositionedDryadLogEntry(string file, long lineno, string line)
: base(line)
{
this.File = file;
this.LineNo = lineno;
}
}
}