前言
在做項目的時候,我們有時候需要檢測項目的記憶體占用情況,有時候是檢測記憶體洩露~,有時候是檢視某段代碼執行前後的記憶體對比,以友善找出問題并以解決。
記憶體洩漏也稱作“存儲滲漏”,用動态存儲配置設定函數動态開辟的空間,在使用完畢後未釋放,結果導緻一直占據該記憶體單元。直到程式結束。(其實說白了就是該記憶體空間使用完畢之後未回收)即所謂記憶體洩漏。-百度百科
前幾天做項目的時候就遇到這種情況,項目是winform開發,大資料壓縮、解壓和綁定的時候,記憶體飙升的很快。雖然通過Windows任務管理器可以檢視到程序的記憶體使用情況,但隻是數值的展現,想要的效果:

這種波線圖可以直覺的展現出程式的CPU使用情況,找了一下關于記憶體的,雖然在資源螢幕中有記憶體的使用情況,但是并不是我想要的,也在網上找了下記憶體監視程式,找了大半天也沒找到。
自己動手,豐衣足食。
DevExpress安裝問題
這裡在多說句,其實用DevExpress也是沒辦法,網上找了好多圖形控件,但都不是我想要的,雖然DevExpress比較大,但是大有大的好處,那就是功能很強大。
關于DevExpress的安裝寫了一篇文章《DevExpress控件安裝、漢化使用教程》,大家可以參考下。
在安裝完之後記得要安裝DevExpress.Patch.13.1.5.exe這個更新檔包,不然開發的程式每次都會彈出:
還有一點就是安裝完解除安裝的時候,一定要正确的解除安裝,有次我正在解除安裝的時候強制關機了,重新開機安裝的時候,就會出現這種情況:
雖然程式已經解除安裝,但還是提示子產品已經安裝,程式已經被破壞了,然後我就用軟媒 - Win7優化大師(我認為是比較好的清理工具),深度清理了下垃圾檔案和系統資料庫,但是發現還是不行,肯定系統資料庫檔案沒有清理幹淨,有點想要重裝系統的沖動,單僅僅是沖動,沒有行動,畢竟重裝完系統要那麼多的軟體要重新安裝,沒辦法就手動清理系統資料庫吧,搜尋“DevExpress”有關的系統資料庫,那不是一般的多啊,還有些是GUID生成的:
一條一條的删啊,也不知道删了多少條,但我記得肯定沒删完,我就是試了下重裝,發現可以了。
做事要有耐心。
實作
以上廢話說的有點多,關于DevExpress的教程真的很少(中文),因為DevExpress太龐大了,隻能通過DevExpress提供的示例程式去學習,實作上面波形圖在DevExpress中有個控件叫SwiftPlotDiagram,位置在DevExpress.XtraCharts.v13.1.dll,使用DevExpress實作圖形程式必須包含在ChartControl控件下。
我們先看下設計器:
除去實作記憶體監控這個程式,我們隻考慮波形圖,要實作一般有幾個基本元素:
- 坐标(SeriesPoint)
- 時間刻度(TimeInterval)
- 走勢線(RegressionLine)
RegressionLine翻譯是回歸線的意思,就是一個總的大緻走勢方向,我稱作走勢線,隻是個叫法不同。
上面元素中坐标最重要,明白了元素我們看下代碼:
1 private const int interval = 20;
2 private double value = 10.0;
3 RegressionLine Regression { get { return GetRegressionLine(Series); } }
4 private int TimeInterval
5 {
6 get
7 {
8 return Convert.ToInt32(nud_Interval.Value);
9 }
10 }
11 private Series Series
12 {
13 get
14 {
15 return chartControl.Series.Count > 0 ? chartControl.Series[0] : null;
16 }
17 }
上面幾個字段就不解釋了,value是縱坐标的值,也就是記憶體值,Series英文翻譯是串聯的意思,這邊表示的是坐标組成曲線的集合,因為這邊我們就畫了一個曲線圖是以是Series[0]。
1 //擷取程序
2 private void getProcess()
3 {
4 foreach (Process item in Process.GetProcesses())
5 {
6 cb_Process.Items.Add(item.ProcessName);
7 }
8 cb_Process.SelectedIndex = 0;
9 }
10 //擷取下一個坐标值
11 private double CalculateNextValue(double value)
12 {
13 Process process = Process.GetProcessesByName(cb_Process.Text)[0];
14 return process.PrivateMemorySize64/1024.0;
15 }
getProcess()方法是擷取本地線程集合填充到下拉清單中,CalculateNextValue()方法是更新縱坐标的值,也就是記憶體值,這個方法在timer事件中調用。
我們看下timer事件代碼:
1 //timer事件
2 private void timer_Tick(object sender, EventArgs e)
3 {
4 if (Series == null )
5 {
6 return;
7 }
8 var argument = DateTime.Now;
9 //一個刻度需要畫的坐标
10 var pointsToUpdate = new SeriesPoint[interval];
11 for (var i = 0; i < interval; i++)
12 {
13 pointsToUpdate[i] = new SeriesPoint(argument, value);
14 argument = argument.AddMilliseconds(1);
15 UpdateValues();
16 }
17 //添加坐标
18 AddPoints(Series, pointsToUpdate);
19 var minDate = argument.AddSeconds(-TimeInterval);
20 //重新設定X軸MinMaxValues
21 var diagram = chartControl.Diagram as SwiftPlotDiagram;
22 if (diagram != null && diagram.AxisX.DateTimeScaleOptions.MeasureUnit == DateTimeMeasureUnit.Millisecond)
23 {
24 diagram.AxisX.Range.SetMinMaxValues(minDate, argument);
25 }
26 }
timer事件主要做個三個工作,一是根據設定好的interval擷取每個刻度的記憶體值,然後添加到Points數組中,interval的值越大,畫出的波線圖越精細,下面就是把Points數組的坐标值加到Series中,最後把X軸的最大小值設定下,以實作動态的效果,需要注意的是:
1 diagram.AxisX.Range.SetMinMaxValues(minDate, argument);
這句代碼的意思不是設定整個X軸的最大小值,而是這個刻度下的最大小值,大家可以把這段代碼注釋下看下效果就知道了,可以看出時間刻度是一樣的,并沒有清除疊加。
關于X軸-時間軸,我們可以在設計其中可以設定:
1 swiftPlotDiagram1.AxisX.DateTimeScaleOptions.GridAlignment = DevExpress.XtraCharts.DateTimeGridAlignment.Millisecond;
2 swiftPlotDiagram1.AxisX.DateTimeScaleOptions.MeasureUnit = DevExpress.XtraCharts.DateTimeMeasureUnit.Millisecond;
3 swiftPlotDiagram1.AxisX.Label.DateTimeOptions.Format = DevExpress.XtraCharts.DateTimeFormat.Custom;
4 swiftPlotDiagram1.AxisX.Label.DateTimeOptions.FormatString = "mm:ss";
5 series1.ArgumentScaleType = DevExpress.XtraCharts.ScaleType.DateTime;
上面是設定X軸-時間刻度為毫秒,下面是設定顯示的格式,最下面代碼的意思是設定曲線畫圖刻度的格式是DateTime格式,也就是上面SeriesPoint的argument。
下面關于圖示标注的設定就是圖形上面的标注PrivateMemory,因為所有的圖形控件都內建在ChartControl中,隻要找到設定起來很友善:
1 this.chartControl.Legend.AlignmentHorizontal = DevExpress.XtraCharts.LegendAlignmentHorizontal.Left;
2 this.chartControl.Legend.AlignmentVertical = DevExpress.XtraCharts.LegendAlignmentVertical.TopOutside;
3 this.chartControl.Legend.Direction = DevExpress.XtraCharts.LegendDirection.LeftToRight;
4 this.chartControl.Legend.Visible = true;
5 series1.Name = "PrivateMemory";
6 regressionLine1.Name = "Regression Line";
7 swiftPlotSeriesView1.Indicators.AddRange(new DevExpress.XtraCharts.Indicator[] {
8 regressionLine1});
9 series1.View = swiftPlotSeriesView1;
10 this.chartControl.SeriesSerializable = new DevExpress.XtraCharts.Series[] {
11 series1};
上面設定标注顯示格式,設定名稱隻要設定series的name屬性就行,不需要重新綁定,因為Legend是內建到chartControl中的,設定好後把series添加到chartControl中就可以顯示了,如果要顯示多個線性,隻要再添加series即可。
關于刻度标示的設定:
1 swiftPlotDiagram1.AxisX.Title.Text = "時間";
2 swiftPlotDiagram1.AxisX.Title.Visible = true;
3 swiftPlotDiagram1.AxisY.Title.Text = "記憶體大小(KB)";
4 swiftPlotDiagram1.AxisY.Title.Visible = true;
最後關于走勢線RegressionLine,因為是內建在chartControl,用的時候隻要訓示需要展現的series,添加到Indicators集合中就可以了,看下代碼:
1 regressionLine1.Name = "Regression Line";
2 swiftPlotSeriesView1.Indicators.AddRange(new DevExpress.XtraCharts.Indicator[] {
3 regressionLine1});
4
5 //擷取走勢線
6 private static RegressionLine GetRegressionLine(Series series)
7 {
8 if (series != null)
9 {
10 var swiftPlotView = series.View as SwiftPlotSeriesView;
11 if (swiftPlotView != null)
12 {
13 foreach (Indicator indicator in swiftPlotView.Indicators)
14 {
15 var regressionLine = indicator as RegressionLine;
16 if (regressionLine != null)
17 {
18 return regressionLine;
19 }
20 }
21 }
22 }
23 return null;
24 }
說了那麼多,我們看下最後實作的效果(20毫秒,錄制的比較卡):
完整代碼:
1 using System;
2 using DevExpress.DXperience.Demos;
3 using DevExpress.XtraCharts;
4 using System.Diagnostics;
5
6 namespace MemoryMonitor
7 {
8 public partial class ChartDemoRealtimeChart : TutorialControlBase
9 {
10 private const int interval = 20;
11 private double value = 10.0;
12 RegressionLine Regression { get { return GetRegressionLine(Series); } }
13 private int TimeInterval
14 {
15 get
16 {
17 return Convert.ToInt32(nud_Interval.Value);
18 }
19 }
20 private Series Series
21 {
22 get
23 {
24 return chartControl.Series.Count > 0 ? chartControl.Series[0] : null;
25 }
26 }
27 #region 界面事件
28 //執行個體化事件
29 public ChartDemoRealtimeChart()
30 {
31 InitializeComponent();
32 Regression.Visible = false;
33 getProcess();
34 }
35 //timer事件
36 private void timer_Tick(object sender, EventArgs e)
37 {
38 if (Series == null )
39 {
40 return;
41 }
42 var argument = DateTime.Now;
43 //一個刻度需要畫的坐标
44 var pointsToUpdate = new SeriesPoint[interval];
45 for (var i = 0; i < interval; i++)
46 {
47 pointsToUpdate[i] = new SeriesPoint(argument, value);
48 argument = argument.AddMilliseconds(1);
49 UpdateValues();
50 }
51 //添加坐标
52 AddPoints(Series, pointsToUpdate);
53 var minDate = argument.AddSeconds(-TimeInterval);
54 //重新設定X軸MinMaxValues
55 var diagram = chartControl.Diagram as SwiftPlotDiagram;
56 if (diagram != null && diagram.AxisX.DateTimeScaleOptions.MeasureUnit == DateTimeMeasureUnit.Millisecond)
57 {
58 diagram.AxisX.Range.SetMinMaxValues(minDate, argument);
59 }
60 }
61 //開始/暫停
62 private void btnStart_Click(object sender, EventArgs e)
63 {
64 timer.Enabled = !timer.Enabled;
65 btnStart.Text = timer.Enabled ? "暫停" : "開始";
66 }
67 //顯示走勢線
68 private void cb_RegressionLine_CheckedChanged(object sender, EventArgs e)
69 {
70 if (Regression != null)
71 Regression.Visible = cb_RegressionLine.Checked;
72 }
73 //選擇需要監控的程序
74 private void cb_Process_SelectedIndexChanged(object sender, EventArgs e)
75 {
76 Series.Points.Clear();
77 }
78 #endregion
79 #region Some方法
80 //擷取下一個坐标值
81 private double CalculateNextValue(double value)
82 {
83 Process process = Process.GetProcessesByName(cb_Process.Text)[0];
84 return process.PrivateMemorySize64/1024.0;
85 }
86 //更新坐标值值
87 private void UpdateValues()
88 {
89 value = CalculateNextValue(value);
90 }
91 //添加坐标
92 private void AddPoints(Series series, SeriesPoint[] pointsToUpdate)
93 {
94 if (series.View is SwiftPlotSeriesViewBase)
95 {
96 series.Points.AddRange(pointsToUpdate);
97 }
98 }
99 //擷取走勢線
100 private static RegressionLine GetRegressionLine(Series series)
101 {
102 if (series != null)
103 {
104 var swiftPlotView = series.View as SwiftPlotSeriesView;
105 if (swiftPlotView != null)
106 {
107 foreach (Indicator indicator in swiftPlotView.Indicators)
108 {
109 var regressionLine = indicator as RegressionLine;
110 if (regressionLine != null)
111 {
112 return regressionLine;
113 }
114 }
115 }
116 }
117 return null;
118 }
119 //擷取程序
120 private void getProcess()
121 {
122 foreach (Process item in Process.GetProcesses())
123 {
124 cb_Process.Items.Add(item.ProcessName);
125 }
126 cb_Process.SelectedIndex = 0;
127 }
128 #endregion
129 }
130 }
View Code
示例程式下載下傳:MemoryMonitor.rar
後記
swiftPlotDiagram隻是chartControl的冰山一角,chartControl隻是DevExpress的冰山一角,DevExpress隻是擴充控件的冰山一角,擴充控件隻是。。。
學無止境,不得不學。
作者:田園裡的蟋蟀
微信公衆号:你好架構
出處:http://www.cnblogs.com/xishuai/
公衆号會不定時的分享有關架構的方方面面,包含并不局限于:Microservices(微服務)、Service Mesh(服務網格)、DDD/TDD、Spring Cloud、Dubbo、Service Fabric、Linkerd、Envoy、Istio、Conduit、Kubernetes、Docker、MacOS/Linux、Java、.NET Core/ASP.NET Core、Redis、RabbitMQ、MongoDB、GitLab、CI/CD(持續內建/持續部署)、DevOps等等。
本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接。
分享到:
QQ空間
新浪微網誌
騰訊微網誌
微信
更多