前言
在做项目的时候,我们有时候需要检测项目的内存占用情况,有时候是检测内存泄露~,有时候是查看某段代码执行前后的内存对比,以方便找出问题并以解决。
内存泄漏也称作“存储渗漏”,用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元。直到程序结束。(其实说白了就是该内存空间使用完毕之后未回收)即所谓内存泄漏。-百度百科
前几天做项目的时候就遇到这种情况,项目是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翻译是回归线的意思,就是一个总的大致走势方向,我称作走势线,只是个叫法不同。
上面元素中坐标最重要,明白了元素我们看下代码:
01.
1
private
const
int
interval =
20
;
02.
2
private
double
value =
10.0
;
03.
3
RegressionLine Regression { get {
return
GetRegressionLine(Series); } }
04.
4
private
int
TimeInterval
05.
5
{
06.
6
get
07.
7
{
08.
8
return
Convert.ToInt32(nud_Interval.Value);
09.
9
}
10.
10
}
11.
11
private
Series Series
12.
12
{
13.
13
get
14.
14
{
15.
15
return
chartControl.Series.Count >
0
? chartControl.Series[
0
] :
null
;
16.
16
}
17.
17
}
上面几个字段就不解释了,value是纵坐标的值,也就是内存值,Series英文翻译是串联的意思,这边表示的是坐标组成曲线的集合,因为这边我们就画了一个曲线图所以是Series[0]。
01.
1
//获取进程
02.
2
private
void
getProcess()
03.
3
{
04.
4
foreach (Process item in Process.GetProcesses())
05.
5
{
06.
6
cb_Process.Items.Add(item.ProcessName);
07.
7
}
08.
8
cb_Process.SelectedIndex =
0
;
09.
9
}
10.
10
//获取下一个坐标值
11.
11
private
double
CalculateNextValue(
double
value)
12.
12
{
13.
13
Process process = Process.GetProcessesByName(cb_Process.Text)[
0
];
14.
14
return
process.PrivateMemorySize64/
1024.0
;
15.
15
}
getProcess()方法是获取本地线程集合填充到下拉列表中,CalculateNextValue()方法是更新纵坐标的值,也就是内存值,这个方法在timer事件中调用。
我们看下timer事件代码:
01.
1
//timer事件
02.
2
private
void
timer_Tick(object sender, EventArgs e)
03.
3
{
04.
4
if
(Series ==
null
)
05.
5
{
06.
6
return
;
07.
7
}
08.
8
var argument = DateTime.Now;
09.
9
//一个刻度需要画的坐标
10.
10
var pointsToUpdate =
new
SeriesPoint[interval];
11.
11
for
(var i =
0
; i < interval; i++)
12.
12
{
13.
13
pointsToUpdate[i] =
new
SeriesPoint(argument, value);
14.
14
argument = argument.AddMilliseconds(
1
);
15.
15
UpdateValues();
16.
16
}
17.
17
//添加坐标
18.
18
AddPoints(Series, pointsToUpdate);
19.
19
var minDate = argument.AddSeconds(-TimeInterval);
20.
20
//重新设置X轴MinMaxValues
21.
21
var diagram = chartControl.Diagram as SwiftPlotDiagram;
22.
22
if
(diagram !=
null
&& diagram.AxisX.DateTimeScaleOptions.MeasureUnit == DateTimeMeasureUnit.Millisecond)
23.
23
{
24.
24
diagram.AxisX.Range.SetMinMaxValues(minDate, argument);
25.
25
}
26.
26
}
timer事件主要做个三个工作,一是根据设置好的interval获取每个刻度的内存值,然后添加到Points数组中,interval的值越大,画出的波线图越精细,下面就是把Points数组的坐标值加到Series中,最后把X轴的最大小值设置下,以实现动态的效果,需要注意的是:
1.
1
diagram.AxisX.Range.SetMinMaxValues(minDate, argument);
这句代码的意思不是设置整个X轴的最大小值,而是这个刻度下的最大小值,大家可以把这段代码注释下看下效果就知道了,可以看出时间刻度是一样的,并没有清除叠加。
关于X轴-时间轴,我们可以在设计其中可以设置:
1.
1
swiftPlotDiagram1.AxisX.DateTimeScaleOptions.GridAlignment = DevExpress.XtraCharts.DateTimeGridAlignment.Millisecond;
2.
2
swiftPlotDiagram1.AxisX.DateTimeScaleOptions.MeasureUnit = DevExpress.XtraCharts.DateTimeMeasureUnit.Millisecond;
3.
3
swiftPlotDiagram1.AxisX.Label.DateTimeOptions.Format = DevExpress.XtraCharts.DateTimeFormat.Custom;
4.
4
swiftPlotDiagram1.AxisX.Label.DateTimeOptions.FormatString =
'mm:ss'
;
5.
5
series1.ArgumentScaleType = DevExpress.XtraCharts.ScaleType.DateTime;
上面是设置X轴-时间刻度为毫秒,下面是设置显示的格式,最下面代码的意思是设置曲线画图刻度的格式是DateTime格式,也就是上面SeriesPoint的argument。
下面关于图标标注的设置就是图形上面的标注PrivateMemory,因为所有的图形控件都集成在ChartControl中,只要找到设置起来很方便:
01.
1
this
.chartControl.Legend.AlignmentHorizontal = DevExpress.XtraCharts.LegendAlignmentHorizontal.Left;
02.
2
this
.chartControl.Legend.AlignmentVertical = DevExpress.XtraCharts.LegendAlignmentVertical.TopOutside;
03.
3
this
.chartControl.Legend.Direction = DevExpress.XtraCharts.LegendDirection.LeftToRight;
04.
4
this
.chartControl.Legend.Visible =
true
;
05.
5
series1.Name =
'PrivateMemory'
;
06.
6
regressionLine1.Name =
'Regression Line'
;
07.
7
swiftPlotSeriesView1.Indicators.AddRange(
new
DevExpress.XtraCharts.Indicator[] {
08.
8
regressionLine1});
09.
9
series1.View = swiftPlotSeriesView1;
10.
10
this
.chartControl.SeriesSerializable =
new
DevExpress.XtraCharts.Series[] {
11.
11
series1};
上面设置标注显示格式,设置名称只要设置series的name属性就行,不需要重新绑定,因为Legend是集成到chartControl中的,设置好后把series添加到chartControl中就可以显示了,如果要显示多个线性,只要再添加series即可。
关于刻度标示的设置:
1.
1
swiftPlotDiagram1.AxisX.Title.Text =
'时间'
;
2.
2
swiftPlotDiagram1.AxisX.Title.Visible =
true
;
3.
3
swiftPlotDiagram1.AxisY.Title.Text =
'内存大小(KB)'
;
4.
4
swiftPlotDiagram1.AxisY.Title.Visible =
true
;
最后关于走势线RegressionLine,因为是集成在chartControl,用的时候只要指示需要体现的series,添加到Indicators集合中就可以了,看下代码:
01.
1
regressionLine1.Name =
'Regression Line'
;
02.
2
swiftPlotSeriesView1.Indicators.AddRange(
new
DevExpress.XtraCharts.Indicator[] {
03.
3
regressionLine1});
04.
4
05.
5
//获取走势线
06.
6
private
static
RegressionLine GetRegressionLine(Series series)
07.
7
{
08.
8
if
(series !=
null
)
09.
9
{
10.
10
var swiftPlotView = series.View as SwiftPlotSeriesView;
11.
11
if
(swiftPlotView !=
null
)
12.
12
{
13.
13
foreach (Indicator indicator in swiftPlotView.Indicators)
14.
14
{
15.
15
var regressionLine = indicator as RegressionLine;
16.
16
if
(regressionLine !=
null
)
17.
17
{
18.
18
return
regressionLine;
19.
19
}
20.
20
}
21.
21
}
22.
22
}
23.
23
return
null
;
24.
24
}
说了那么多,我们看下最后实现的效果(20毫秒,录制的比较卡):
完整代码:
001.
1
using System;
002.
2
using DevExpress.DXperience.Demos;
003.
3
using DevExpress.XtraCharts;
004.
4
using System.Diagnostics;
005.
5
006.
6
namespace MemoryMonitor
007.
7
{
008.
8
public
partial
class
ChartDemoRealtimeChart : TutorialControlBase
009.
9
{
010.
10
private
const
int
interval =
20
;
011.
11
private
double
value =
10.0
;
012.
12
RegressionLine Regression { get {
return
GetRegressionLine(Series); } }
013.
13
private
int
TimeInterval
014.
14
{
015.
15
get
016.
16
{
017.
17
return
Convert.ToInt32(nud_Interval.Value);
018.
18
}
019.
19
}
020.
20
private
Series Series
021.
21
{
022.
22
get
023.
23
{
024.
24
return
chartControl.Series.Count >
0
? chartControl.Series[
0
] :
null
;
025.
25
}
026.
26
}
027.
27
#region 界面事件
028.
28
//实例化事件
029.
29
public
ChartDemoRealtimeChart()
030.
30
{
031.
31
InitializeComponent();
032.
32
Regression.Visible =
false
;
033.
33
getProcess();
034.
34
}
035.
35
//timer事件
036.
36
private
void
timer_Tick(object sender, EventArgs e)
037.
37
{
038.
38
if
(Series ==
null
)
039.
39
{
040.
40
return
;
041.
41
}
042.
42
var argument = DateTime.Now;
043.
43
//一个刻度需要画的坐标
044.
44
var pointsToUpdate =
new
SeriesPoint[interval];
045.
45
for
(var i =
0
; i < interval; i++)
046.
46
{
047.
47
pointsToUpdate[i] =
new
SeriesPoint(argument, value);
048.
48
argument = argument.AddMilliseconds(
1
);
049.
49
UpdateValues();
050.
50
}
051.
51
//添加坐标
052.
52
AddPoints(Series, pointsToUpdate);
053.
53
var minDate = argument.AddSeconds(-TimeInterval);
054.
54
//重新设置X轴MinMaxValues
055.
55
var diagram = chartControl.Diagram as SwiftPlotDiagram;
056.
56
if
(diagram !=
null
&& diagram.AxisX.DateTimeScaleOptions.MeasureUnit == DateTimeMeasureUnit.Millisecond)
057.
57
{
058.
58
diagram.AxisX.Range.SetMinMaxValues(minDate, argument);
059.
59
}
060.
60
}
061.
61
//开始/暂停
062.
62
private
void
btnStart_Click(object sender, EventArgs e)
063.
63
{
064.
64
timer.Enabled = !timer.Enabled;
065.
65
btnStart.Text = timer.Enabled ?
'暂停'
:
'开始'
;
066.
66
}
067.
67
//显示走势线
068.
68
private
void
cb_RegressionLine_CheckedChanged(object sender, EventArgs e)
069.
69
{
070.
70
if
(Regression !=
null
)
071.
71
Regression.Visible = cb_RegressionLine.Checked;
072.
72
}
073.
73
//选择需要监控的进程
074.
74
private
void
cb_Process_SelectedIndexChanged(object sender, EventArgs e)
075.
75
{
076.
76
Series.Points.Clear();
077.
77
}
078.
78
#endregion
079.
79
#region Some方法
080.
80
//获取下一个坐标值
081.
81
private
double
CalculateNextValue(
double
value)
082.
82
{
083.
83
Process process = Process.GetProcessesByName(cb_Process.Text)[
0
];
084.
84
return
process.PrivateMemorySize64/
1024.0
;
085.
85
}
086.
86
//更新坐标值值
087.
87
private
void
UpdateValues()
088.
88
{
089.
89
value = CalculateNextValue(value);
090.
90
}
091.
91
//添加坐标
092.
92
private
void
AddPoints(Series series, SeriesPoint[] pointsToUpdate)
093.
93
{
094.
94
if
(series.View is SwiftPlotSeriesViewBase)
095.
95
{
096.
96
series.Points.AddRange(pointsToUpdate);
097.
97
}
098.
98
}
099.
99
//获取走势线
100.
100
private
static
RegressionLine GetRegressionLine(Series series)
101.
101
{
102.
102
if
(series !=
null
)
103.
103
{
104.
104
var swiftPlotView = series.View as SwiftPlotSeriesView;
105.
105
if
(swiftPlotView !=
null
)
106.
106
{
107.
107
foreach (Indicator indicator in swiftPlotView.Indicators)
108.
108
{
109.
109
var regressionLine = indicator as RegressionLine;
110.
110
if
(regressionLine !=
null
)
111.
111
{
112.
112
return
regressionLine;
113.
113
}
114.
114
}
115.
115
}
116.
116
}
117.
117
return
null
;
118.
118
}
119.
119
//获取进程
120.
120
private
void
getProcess()
121.
121
{
122.
122
foreach (Process item in Process.GetProcesses())
123.
123
{
124.
124
cb_Process.Items.Add(item.ProcessName);
125.
125
}
126.
126
cb_Process.SelectedIndex =
0
;
127.
127
}
128.
128
#endregion
129.
129
}
130.
130
}
后记
swiftPlotDiagram只是chartControl的冰山一角,chartControl只是DevExpress的冰山一角,DevExpress只是扩展控件的冰山一角,扩展控件只是。。。