/*
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
{
///
/// A log viewer displays fragments of logs or other text files.
///
public partial class LogViewer : Form
{
bool canceled;
DGVData shownText;
DGVData shownLogLines;
StatusWriter status;
///
/// A text file line is represented in this way.
///
class TextFileLine
{
private long Line;
private string Contents;
public TextFileLine(long line, string contents)
{
this.Line = line;
this.Contents = contents;
}
}
///
/// True if the form has been closed (or work has been cancelled in some way).
///
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();
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();
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;
}
///
/// Create a new log viewer.
///
/// If true the viewer will display a text file.
/// Information to display in the window header.
public LogViewer(bool textFile, string header)
{
this.InitializeComponent();
this.Initialize(textFile, header);
}
///
/// Create a new log viewer to display a file.
///
public LogViewer()
{
this.InitializeComponent();
}
///
/// Load a specified file.
///
/// File to load.
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 toAddText = new List();
List toAddLog = new List();
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);
}
///
/// Display a status message.
///
/// Message to display.
/// Message severity.
public void Status(string message, StatusKind statusKind)
{
if (this.canceled || this.IsDisposed)
return;
this.status.Status(message, statusKind);
}
///
/// Add a text line to the viewer.
///
/// Line number to add.
/// Text of the line.
/// File that the information comes from.
public void AddLine(string file, long lineno, string text)
{
if (this.canceled || this.IsDisposed)
return;
if (this.InvokeRequired)
{
var action = new Action( (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;
}
}
///
/// Update the progress bar.
///
/// How much has been done.
public void UpdateProgress(int percent)
{
if (this.InvokeRequired)
{
this.Invoke(new Action(this.UpdateProgress), percent);
}
else
{
if (this.canceled || this.IsDisposed)
return;
try
{
this.toolStripProgressBar.Value = percent;
}
catch
{ }
}
}
///
/// Close the log viewer.
///
/// Unused.
/// Unused.
private void closeToolStripMenuItem_Click(object sender, EventArgs e)
{
this.canceled = true;
this.Close();
}
///
/// We are done adding information.
///
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;
}
///
/// User wants to the the location of the message.
///
/// Unused.
/// Row selected.
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");
}
}
///
/// A line from a file.
///
/// Type of line representation.
public class PositionedLine
where T : IParse, new()
{
///
/// File containing the log entry.
///
public string File { get; protected set; }
///
/// Line number.
///
public long LineNo { get; protected set; }
///
/// Actual line from file.
///
public T Line { get; protected set; }
///
/// Create a positioned line.
///
/// File containing the line.
/// Line number in file.
/// Actual line in file.
public PositionedLine(string file, long lineno, string line)
{
this.File = file;
this.LineNo = lineno;
this.Line = new T();
this.Line.Parse(line);
}
}
///
/// Cosmos log entry with position information.
///
public class PositionedDryadLogEntry : DryadLogEntry
{
///
/// File containing the log entry.
///
public string File { get; protected set; }
///
/// Line number.
///
public long LineNo { get; protected set; }
///
/// Create a positioned log entry.
///
/// File containing the log entry.
/// Line number.
/// Line contents.
public PositionedDryadLogEntry(string file, long lineno, string line)
: base(line)
{
this.File = file;
this.LineNo = lineno;
}
}
}