Analysis-Services/AsPerfMon/AsPerfMon/ASPerfMon.cs
2016-12-03 17:06:28 -08:00

395 lines
16 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;
using System.Timers;
using Microsoft.AnalysisServices;
using Tom = Microsoft.AnalysisServices.Tabular;
using System.Xml;
using System.Drawing;
namespace ASPerfMon
{
public partial class ASPerfMon : Form
{
public ASPerfMon()
{
InitializeComponent();
}
private Tom.Server _server = new Tom.Server();
private System.Timers.Timer _timer;
private const int _smallestFont = 12;
private string _serverConnectionString;
private DateTime _connectionTime;
//--------------------------------------------------
//120 X axis markers, one for each array item.
//Each item holds a dictionary with lookup key of series name (e.g. database name), which returns a SeriesPoint object.
Dictionary<string, SeriesPoint>[] _seriesData = new Dictionary<string, SeriesPoint>[120];
class SeriesPoint
{
public string XAxisLabel;
public long MemoryUsedBytes;
public long MemoryUsedMegabytes
{
get
{
return MemoryUsedBytes / 1024 / 1024;
}
}
public SeriesPoint(string xAxisCategoryName)
{
this.XAxisLabel = xAxisCategoryName;
this.MemoryUsedBytes = 0;
}
}
private void ASPerfMon_Load(object sender, EventArgs e)
{
InitializeChart(false);
}
private void InitializeChart(bool withSampleSeries)
{
chartControl.ChartAreas[0].AxisX.MajorGrid.Enabled = false;
chartControl.ChartAreas[0].AxisX.Interval = 5;
chartControl.ChartAreas[0].AxisY.IsMarksNextToAxis = true;
chartControl.ChartAreas[0].AxisY.LabelStyle.Format = "{0:n0}";
chartControl.ChartAreas[0].AxisY.Title = "Memory Usage MB";
chartControl.Palette = ChartColorPalette.Pastel;
chartControl.ChartAreas[0].AxisY.TitleFont = new Font(Font.FontFamily, _smallestFont + 2);
chartControl.Legends[0].Font = new Font(Font.FontFamily, _smallestFont);
chartControl.Legends[0].LegendItemOrder = LegendItemOrder.ReversedSeriesOrder;
//initialize the chart with blanks
Dictionary<string, SeriesPoint> pointsSample;
if (withSampleSeries)
pointsSample = GetNewXAxisMarkerPointsFromAs();
else
{
pointsSample = new Dictionary<string, SeriesPoint>();
}
chartControl.Series.Clear();
if (withSampleSeries)
{
for (int i = _seriesData.Length - 1; i >= 0; i--)
{
foreach (KeyValuePair<string, SeriesPoint> entry in pointsSample)//to get series
{
entry.Value.XAxisLabel = " ";
entry.Value.MemoryUsedBytes = 0;
//add the series if not there yet
if (chartControl.Series.FindByName(entry.Key) == null)
{
Series series = new Series(entry.Key);
chartControl.Series.Add(series);
}
chartControl.Series[entry.Key].Points.AddXY(" ", 0);
}
_seriesData[i] = pointsSample;
}
PaintChart();
}
else
{
chartControl.Series.Add("");
for (int i = 0; i < _seriesData.Length; i++)
{
chartControl.Series[0].Points.AddXY(" ", 0);
}
chartControl.Invalidate();
}
_timer = new System.Timers.Timer(Settings.Default.SampleInterval);
_timer.Elapsed += OnTimedEvent;
_timer.AutoReset = true;
}
private void PaintChart()
{
//set dynamic scale
long maxValue = 0;
for (int i = _seriesData.Length - 1; i >= 0; i--)
{
long pointsStack = 0;
foreach (SeriesPoint point in _seriesData[i].Values)
{
pointsStack += Math.Abs(point.MemoryUsedMegabytes);
}
if (pointsStack > maxValue)
maxValue = pointsStack;
}
chartControl.ChartAreas[0].AxisY.IsStartedFromZero = true;
chartControl.ChartAreas[0].AxisY.Minimum = double.NaN;
chartControl.ChartAreas[0].AxisY.Maximum = double.NaN;
chartControl.ChartAreas[0].RecalculateAxesScale();
chartControl.Invalidate();
}
delegate void SetTimerCallback(Object source, ElapsedEventArgs e);
private void OnTimedEvent(Object source, ElapsedEventArgs e)
{
try
{
if (chartControl.InvokeRequired)
{
SetTimerCallback callback = new SetTimerCallback(OnTimedEvent);
//todo safer invoke look up bism
try
{
Invoke(callback, new object[] { source, e });
}
catch { }
}
else
{
//Todo delete this once http stability fix is done
if ((DateTime.Now - _connectionTime).TotalMinutes > 8)
{
_server.Disconnect();
_server.Connect(_serverConnectionString);
_connectionTime = DateTime.Now;
}
_seriesData[0] = GetNewXAxisMarkerPointsFromAs();
//rebind
chartControl.Series.Clear();
//for each X axis marker
for (int i = _seriesData.Length - 1; i > 0; i--)
{
//push back one category
_seriesData[i] = _seriesData[i - 1];
//foreach series for the X axis marker we are populating: ...
foreach (KeyValuePair<string, SeriesPoint> entry in _seriesData[i])
{
//add the series if not there yet
if (chartControl.Series.FindByName(entry.Key) == null)
{
Series series = new Series(entry.Key);
series.ChartType = SeriesChartType.StackedColumn;
chartControl.Series.Add(series);
}
//add the data point for the series
DataPoint point = new DataPoint();
point.SetValueXY(entry.Value.XAxisLabel, entry.Value.MemoryUsedMegabytes);
point.ToolTip = string.Format($"{entry.Value.XAxisLabel} - {entry.Key}: {string.Format("{0:#,###0}", entry.Value.MemoryUsedMegabytes)} MB");
chartControl.Series[entry.Key].Points.Add(point);
}
}
PaintChart();
}
}
catch (Exception exc)
{
//Workaround for timeout over HTTP. Try to reconnect - todo delete
try
{
System.Diagnostics.Debug.WriteLine($"Exception occurred at {DateTime.Now}: {exc.Message}");
_server.Disconnect();
_server.Connect(_serverConnectionString);
_connectionTime = DateTime.Now;
}
catch
{
_timer.Enabled = false;
MessageBox.Show($"Error: {exc.Message}", "AS PerfMon", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
private Dictionary<string, SeriesPoint> GetNewXAxisMarkerPointsFromAs()
{
Dictionary<string, SeriesPoint> points = new Dictionary<string, SeriesPoint>();
string xAxisLabel = DateTime.Now.ToString("hh:mm:ss tt");
string commandStatement = String.Format("SELECT [OBJECT_PARENT_PATH], [OBJECT_MEMORY_NONSHRINKABLE] FROM $SYSTEM.DISCOVER_OBJECT_MEMORY_USAGE");
XmlNodeList rows = ExecuteXmlaCommand(_server, commandStatement);
foreach (XmlNode row in rows)
{
long result, label;
if (long.TryParse(row.LastChild.InnerText, out result) && result > 0)
{
string objectName = "";
string[] pathMembers = row.ChildNodes[0].InnerText.Split('.');
if (pathMembers.Length > 2 && _server.Databases.ContainsName(pathMembers[2]))
{
//Database name identified
objectName = pathMembers[2];
}
else
{
string firstMember = row.ChildNodes[0].InnerText;
//Ignore if number or blank. Assuming this is a double counted, shared allocation.
if (!long.TryParse(firstMember, out label) && firstMember != "")
{
objectName = firstMember;
}
}
if (objectName != "" && objectName != "Global") //todo: delete global condition
{
if (!points.ContainsKey(objectName))
points.Add(objectName, new SeriesPoint(xAxisLabel));
points[objectName].MemoryUsedBytes += Math.Abs(result);
}
}
else if (result > 0)
{
System.Diagnostics.Debug.WriteLine("We have a problem parsing");
}
}
return points;
}
public static XmlNodeList ExecuteXmlaCommand(Microsoft.AnalysisServices.Core.Server amoServer, string commandStatement)
{
XmlWriter xmlWriter = amoServer.StartXmlaRequest(XmlaRequestType.Undefined);
WriteSoapEnvelopeWithCommandStatement(xmlWriter, amoServer.SessionID, commandStatement);
System.Xml.XmlReader xmlReader = amoServer.EndXmlaRequest();
xmlReader.MoveToContent();
string fullEnvelopeResponseFromServer = xmlReader.ReadOuterXml();
xmlReader.Close();
XmlDocument documentResponse = new XmlDocument();
documentResponse.LoadXml(fullEnvelopeResponseFromServer);
XmlNamespaceManager nsmgr = new XmlNamespaceManager(documentResponse.NameTable);
nsmgr.AddNamespace("myns1", "urn:schemas-microsoft-com:xml-analysis");
nsmgr.AddNamespace("myns2", "urn:schemas-microsoft-com:xml-analysis:rowset");
XmlNodeList rows = documentResponse.SelectNodes("//myns1:ExecuteResponse/myns1:return/myns2:root/myns2:row", nsmgr);
return rows;
}
public static void WriteSoapEnvelopeWithCommandStatement(XmlWriter xmlWriter, string sessionId, string commandStatement)
{
//--------------------------------------------------------------------------------
// This is a sample of the XMLA request we'll write:
//
// <Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/">
// <Header>
// <Session soap:mustUnderstand="1" SessionId="THE SESSION ID HERE" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns="urn:schemas-microsoft-com:xml-analysis" />
// </Header>
// <Body>
// <Execute xmlns="urn:schemas-microsoft-com:xml-analysis">
// <Command>
// <Statement>
// ...
// </Statement>
// </Command>
// <Properties/>
// </Execute>
// </Body>
// </Envelope>
//--------------------------------------------------------------------------------
xmlWriter.WriteStartElement("Envelope", "http://schemas.xmlsoap.org/soap/envelope/");
xmlWriter.WriteStartElement("Header");
if (sessionId != null)
{
xmlWriter.WriteStartElement("Session", "urn:schemas-microsoft-com:xml-analysis");
xmlWriter.WriteAttributeString("soap", "mustUnderstand", "http://schemas.xmlsoap.org/soap/envelope/", "1");
xmlWriter.WriteAttributeString("SessionId", sessionId);
xmlWriter.WriteEndElement(); // </Session>
}
xmlWriter.WriteEndElement(); // </Header>
xmlWriter.WriteStartElement("Body");
xmlWriter.WriteStartElement("Execute", "urn:schemas-microsoft-com:xml-analysis");
xmlWriter.WriteStartElement("Command");
xmlWriter.WriteElementString("Statement", commandStatement);
xmlWriter.WriteEndElement(); // </Command>
xmlWriter.WriteStartElement("Properties");
xmlWriter.WriteEndElement(); // </Properties>
xmlWriter.WriteEndElement(); // </Execute>
xmlWriter.WriteEndElement(); // </Body>
xmlWriter.WriteEndElement(); // </Envelope>
}
private void btnConnect_Click(object sender, EventArgs e)
{
Connect();
}
private void Connect()
{
try
{
Connect connForm = new Connect();
connForm.StartPosition = FormStartPosition.CenterParent;
connForm.ShowDialog();
if (connForm.DialogResult == DialogResult.OK)
{
_timer.Enabled = false;
if (connForm.IntegratedAuth)
_serverConnectionString = $"Provider=MSOLAP;Data Source={connForm.ServerName};";
else
_serverConnectionString = $"Provider=MSOLAP;Data Source={connForm.ServerName};User ID={connForm.UserName};Password={connForm.Passwrod};Persist Security Info=True;Impersonation Level=Impersonate;";
_server = new Tom.Server();
_server.Connect(_serverConnectionString);
_connectionTime = DateTime.Now;
if (_server.ServerProperties.Count == 0)
{
throw new ConnectionException("User is not AS admin.");
}
chartControl.Titles.Clear();
chartControl.Titles.Add(connForm.ServerName);
chartControl.Titles[0].Font = new Font(Font.FontFamily, _smallestFont + 4);
InitializeChart(true);
_timer.Interval = connForm.SampleInterval;
_timer.Enabled = true;
Settings.Default.ServerName = connForm.ServerName;
Settings.Default.UserName = connForm.UserName;
Settings.Default.SampleInterval = connForm.SampleInterval;
Settings.Default.IntegratedAuth = connForm.IntegratedAuth;
Settings.Default.Save();
}
}
catch (Exception exc)
{
MessageBox.Show($"Cannot connect.\n{exc.Message}", "AS PerfMon", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void ASPerfMon_FormClosing(object sender, FormClosingEventArgs e)
{
Disconnect();
}
private void Disconnect()
{
try
{
if (_server != null) _server.Disconnect();
}
catch { }
}
private void chartControl_GetToolTipText(object sender, ToolTipEventArgs e)
{
//Event handler has to be hooked up in order to display tooltip (!)
}
private void ASPerfMon_Shown(object sender, EventArgs e)
{
Connect();
}
}
}