diff --git a/AsPerfMon/ASPerfMon.sln b/AsPerfMon/ASPerfMon.sln
new file mode 100644
index 0000000..7894e48
--- /dev/null
+++ b/AsPerfMon/ASPerfMon.sln
@@ -0,0 +1,22 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 14
+VisualStudioVersion = 14.0.25420.1
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ASPerfMon", "ASPerfMon\ASPerfMon.csproj", "{B2442578-2C46-47D2-B5FB-57096C234552}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {B2442578-2C46-47D2-B5FB-57096C234552}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B2442578-2C46-47D2-B5FB-57096C234552}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B2442578-2C46-47D2-B5FB-57096C234552}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B2442578-2C46-47D2-B5FB-57096C234552}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/AsPerfMon/AsPerfMon.png b/AsPerfMon/AsPerfMon.png
new file mode 100644
index 0000000..dbc2171
Binary files /dev/null and b/AsPerfMon/AsPerfMon.png differ
diff --git a/AsPerfMon/AsPerfMon/ASPerfMon.Designer.cs b/AsPerfMon/AsPerfMon/ASPerfMon.Designer.cs
new file mode 100644
index 0000000..daa17e8
--- /dev/null
+++ b/AsPerfMon/AsPerfMon/ASPerfMon.Designer.cs
@@ -0,0 +1,94 @@
+namespace ASPerfMon
+{
+ partial class ASPerfMon
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ System.Windows.Forms.DataVisualization.Charting.ChartArea chartArea1 = new System.Windows.Forms.DataVisualization.Charting.ChartArea();
+ System.Windows.Forms.DataVisualization.Charting.Legend legend1 = new System.Windows.Forms.DataVisualization.Charting.Legend();
+ System.Windows.Forms.DataVisualization.Charting.Series series1 = new System.Windows.Forms.DataVisualization.Charting.Series();
+ System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ASPerfMon));
+ this.chartControl = new System.Windows.Forms.DataVisualization.Charting.Chart();
+ this.btnConnect = new System.Windows.Forms.Button();
+ ((System.ComponentModel.ISupportInitialize)(this.chartControl)).BeginInit();
+ this.SuspendLayout();
+ //
+ // chartControl
+ //
+ chartArea1.Name = "ChartArea1";
+ this.chartControl.ChartAreas.Add(chartArea1);
+ this.chartControl.Dock = System.Windows.Forms.DockStyle.Fill;
+ legend1.Name = "Legend1";
+ this.chartControl.Legends.Add(legend1);
+ this.chartControl.Location = new System.Drawing.Point(0, 0);
+ this.chartControl.Name = "chartControl";
+ series1.ChartArea = "ChartArea1";
+ series1.Legend = "Legend1";
+ series1.Name = "Series1";
+ this.chartControl.Series.Add(series1);
+ this.chartControl.Size = new System.Drawing.Size(948, 645);
+ this.chartControl.TabIndex = 0;
+ this.chartControl.Text = "chart1";
+ this.chartControl.GetToolTipText += new System.EventHandler(this.chartControl_GetToolTipText);
+ //
+ // btnConnect
+ //
+ this.btnConnect.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
+ this.btnConnect.Location = new System.Drawing.Point(788, 567);
+ this.btnConnect.Name = "btnConnect";
+ this.btnConnect.Size = new System.Drawing.Size(127, 44);
+ this.btnConnect.TabIndex = 6;
+ this.btnConnect.Text = "Connect";
+ this.btnConnect.UseVisualStyleBackColor = true;
+ this.btnConnect.Click += new System.EventHandler(this.btnConnect_Click);
+ //
+ // ASPerfMon
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.BackColor = System.Drawing.Color.White;
+ this.ClientSize = new System.Drawing.Size(948, 645);
+ this.Controls.Add(this.btnConnect);
+ this.Controls.Add(this.chartControl);
+ this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
+ this.Name = "ASPerfMon";
+ this.Text = "AS PerfMon";
+ this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.ASPerfMon_FormClosing);
+ this.Load += new System.EventHandler(this.ASPerfMon_Load);
+ this.Shown += new System.EventHandler(this.ASPerfMon_Shown);
+ ((System.ComponentModel.ISupportInitialize)(this.chartControl)).EndInit();
+ this.ResumeLayout(false);
+
+ }
+
+ #endregion
+
+ private System.Windows.Forms.DataVisualization.Charting.Chart chartControl;
+ private System.Windows.Forms.Button btnConnect;
+ }
+}
+
diff --git a/AsPerfMon/AsPerfMon/ASPerfMon.cs b/AsPerfMon/AsPerfMon/ASPerfMon.cs
new file mode 100644
index 0000000..f92a8f3
--- /dev/null
+++ b/AsPerfMon/AsPerfMon/ASPerfMon.cs
@@ -0,0 +1,394 @@
+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[] _seriesData = new Dictionary[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 pointsSample;
+ if (withSampleSeries)
+ pointsSample = GetNewXAxisMarkerPointsFromAs();
+ else
+ {
+ pointsSample = new Dictionary();
+ }
+
+ chartControl.Series.Clear();
+ if (withSampleSeries)
+ {
+ for (int i = _seriesData.Length - 1; i >= 0; i--)
+ {
+ foreach (KeyValuePair 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 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 GetNewXAxisMarkerPointsFromAs()
+ {
+ Dictionary points = new Dictionary();
+ 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:
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ // ...
+ //
+ //
+ //
+ //
+ //
+ //
+ //--------------------------------------------------------------------------------
+ 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(); //
+ }
+ xmlWriter.WriteEndElement(); //
+ xmlWriter.WriteStartElement("Body");
+ xmlWriter.WriteStartElement("Execute", "urn:schemas-microsoft-com:xml-analysis");
+ xmlWriter.WriteStartElement("Command");
+ xmlWriter.WriteElementString("Statement", commandStatement);
+ xmlWriter.WriteEndElement(); //
+ xmlWriter.WriteStartElement("Properties");
+ xmlWriter.WriteEndElement(); //
+ xmlWriter.WriteEndElement(); //
+ xmlWriter.WriteEndElement(); //