前言
在做项目的时候,我们有时候需要检测项目的内存占用情况,有时候是检测内存泄露~,有时候是查看某段代码执行前后的内存对比,以方便找出问题并以解决。
内存泄漏也称作“存储渗漏”,用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元。直到程序结束。(其实说白了就是该内存空间使用完毕之后未回收)即所谓内存泄漏。-百度百科
前几天做项目的时候就遇到这种情况,项目是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只是扩展控件的冰山一角,扩展控件只是。。。
学无止境,不得不学。