蛙蛙推荐:编写一个服务监控的软件
如果一个服务被部署到了几十台机器上,我们往往需要每天花费很多的时间去查看每台机器上的服务的运行状况,虽然微软有MOM(Microsoft Operations Manager)和SMS(Systems Management Server),但处于成本上及其它方面的考虑,好多时候我们还用不上这些东西,其实微软公开了好多管理和监控方面的API和工具,比如WMIC,System.Managerment等,把这些零散的API和工具集中起来,便可以开发一些满足自定义需求的小软件。
我们要实现以下任务
1、确认指定的进程已启动
2、获取指定机器,指定进程的CPU、内存、线程使用情况
3、确认指定机器的指定端口在监听
4、获取指定机器指定计数器的情况
5、获取指定机器上的系统日志/应用日志
6、获取指定服务相关trace
7、确认指定服务的功能拨测能通过
8、抓取指定机器指定进程的dump
9、在指定机器上进行网络抓包
10、重启指定机器上的某服务,某应用程序进程池等
下面来一一考虑一下
1、确认指定的进程已启动
可以用WMI接口来获取远程机器的进程列表,然后遍历这个列表,确认指定服务的进程名字是否在这个列表里,核心代码(来自网络)如下:
{
// The second way of constructing a query
string queryString =
"Select Name, ProcessId, Caption, ExecutablePath" +
" FROM Win32_Process";
SelectQuery query = new SelectQuery(queryString);
ConnectionOptions options = new ConnectionOptions();
options.Username = @"administrator";
options.Password = "";
ManagementScope scope = new System.Management.ManagementScope(@"\\.\root\CIMV2");
scope.Connect();
ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query);
ManagementObjectCollection processes = searcher.Get();
DataTable result = new DataTable();
result.Columns.Add("Name", Type.GetType("System.String"));
result.Columns.Add("ProcessId", Type.GetType("System.Int32"));
result.Columns.Add("Caption", Type.GetType("System.String"));
result.Columns.Add("Path", Type.GetType("System.String"));
foreach (ManagementObject mo in processes)
{
DataRow row = result.NewRow();
row["Name"] = mo["Name"].ToString();
row["ProcessId"] = Convert.ToInt32(mo["ProcessId"]);
if (mo["Caption"] != null)
row["Caption"] = mo["Caption"].ToString();
if (mo["ExecutablePath"] != null)
row["Path"] = mo["ExecutablePath"].ToString();
result.Rows.Add(row);
}
return result;
}
2、获取指定机器,指定进程的CPU、内存、线程使用情况
这几样数据,我们用计数器来获取,具体表格如下
某进程的CPU使用量:process类别下的% Processor Time,实例名是你服务的进程名字(不加后缀名)
某进程的内存使用量:process类别下的Private Bytes,实例名是你服务的进程名字(不加后缀名)
某进程的内存使用量:process类别下的Thread Count,实例名是你服务的进程名字(不加后缀名)
其它的计数器的说明请打开perfmon工具一个一个看每个计数器的说明文字
关于获取某机器某计数器数值的代码大约如下
{
public PerfCounter(string categoryName,
string counterName,
string instanceName,
string machineName
)
{
this.categoryName = categoryName;
this.counterName = counterName;
this.instanceName = instanceName;
this.machineName = machineName;
}
public string categoryName;
public string counterName;
public string instanceName;
public string machineName;
}
public class PerfCounterHelper
{
public static string GetPerfCount(List<PerfCounter> counters)
{
List<PerformanceCounter> pcs = new List<PerformanceCounter>();
foreach (PerfCounter counter in counters)
{
pcs.Add(new PerformanceCounter(counter.categoryName,
counter.counterName,
counter.instanceName,
counter.machineName));
}
StringBuilder text = new StringBuilder();
try
{
int i = 0;
while(true)
{
Thread.Sleep(1000);
i++;
text.AppendFormat("第{0}次采样\r\n", i);
foreach (PerformanceCounter pc in pcs)
{
text.AppendFormat("{0} \t{1}\t{2} \r\n",pc.CounterName,pc.InstanceName, pc.NextValue());
}
if (i > 3) break;
}
}
catch (Exception ex)
{
Trace.WriteLine(ex);
}
finally
{
foreach (PerformanceCounter pc in pcs)
{
pc.Close();
}
}
return text.ToString();
}
}
使用如下
counters.Add(new PerfCounter("Processor", "% Processor Time", "_Total", "."));
counters.Add(new PerfCounter("Memory", "Pages/sec", "", "."));
Console.WriteLine(PerfCounterHelper.GetPerfCount(counters));
其实获取每个进程的内存,线程,CPU等信息,用WMI查询Win32_Process也可以得到,但像CPU等信息还要往出算,所以简单的办法就是通过计数器获取了
3、确认指定机器的指定端口在监听
一般提供网络接口的服务都要监听一个或者几个端口,我们手工来确认端口是否监听的时候一般是登录到那台机器上运行netstat -na | find "LISTENING"来查看输出,或者在本机上用telnet 192.168.0.1 80来看是否能打开,其实我们用程序也能调用者两个命令,但是调用远程机器上的netstat命令,你一般没办法获取它的输出,通常的办法是把输出结果用命名管道重定向到某个文件,然后以编程的方式去访问网络路径去取这个文本文件并读取文本,这种方式太麻烦。而以编程的方式调用telnet需要交互,也很麻烦,所以我们用socket来建立一个连接来测试远程端口是否在监听,大约如下
{
Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
try
{
s.Connect(Dns.GetHostAddresses(remoteHost)[0], port);
}
catch (Exception ex)
{
return false;
}
finally
{
s.Close();
}
return true;
}
调用ping和telnet的代码大概如下,其中telnet可能没价值,如果真要模拟telnet,可以下载个开源的telnet client的.net实现
{
Process proc = new Process();
proc.StartInfo.FileName = "ping.exe";
proc.StartInfo.Arguments = remoteHost;
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.RedirectStandardOutput = true;
proc.Start();
return proc.StandardOutput.ReadToEnd();
}
public static string Telnet(string remoteHost, int port)
{
Process proc = new Process();
proc.StartInfo.FileName = "telnet.exe";
proc.StartInfo.Arguments = string.Format("{0} {1}",remoteHost, port);
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.RedirectStandardOutput = true;
proc.Start();
return proc.StandardOutput.ReadToEnd();
}
如果要获取某台机器上的网络链接情况,可以调用GetTcpTable的win32 API,因为执行这个任务我们一般是用netstat命令,而且我没有找到使用wmi获取类似netstat输出的接口,当然,GetTcpTable也不能远程执行。如果谁找到在A机器上执行B机器上的命令行程序并获取标准输出的方法请告诉我。
4、获取指定机器指定计数器的情况
具体做法上面已经发过了,只是有时候我们要查看一些服务的业务逻辑方面的计数器,关于创建自定义计数器,可以参考以下链接
http://www.cnblogs.com/onlytiancai/archive/2007/09/24/902310.html
5、获取指定机器上的系统日志/应用日志
我们一般获取最近一段时间的日志来看是否正常,代码如下(用WMI也可以应该)
{
public string GetEventLog(List<string> machines)
{
StringBuilder sb = new StringBuilder();
foreach (string mache in machines)
{
try
{
getLog(sb, mache);
Console.WriteLine("process {0} finished", mache);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
return sb.ToString();
}
private void getLog(StringBuilder sb, string machine)
{
sb.AppendFormat("<h3>{0} 's EventLog</h3>", machine);
EventLog myLog = new EventLog("Application", machine);
for (int x = myLog.Entries.Count - 1; x >= 0; x--)
{
EventLogEntry entry = myLog.Entries[x];
if (entry.TimeGenerated < DateTime.Now.AddDays(-1))
{
break;
}
switch (entry.EntryType)
{
case EventLogEntryType.Error:
sb.AppendFormat("<p><font color='red'>{0}-{1}</font></p>", entry.TimeGenerated, entry.Message);
break;
case EventLogEntryType.Warning:
sb.AppendFormat("<p><font color='yellow'>{0}-{1}</font></p>", entry.TimeGenerated, entry.Message);
break;
default:
sb.AppendFormat("<p><font color='black'>{0}-{1}</font></p>", entry.TimeGenerated, entry.Message);
break;
}
}
}
}
6、获取指定服务相关trace
trace一般都是写入本机的一些文本文件或者集中写入的一个trace数据库,trace一般都会有机器名,服务名等,根据这些条件去找到文本trace的路径然后用streamreader读取,或者用SqlDataAdapter来获取数据库里的trace记录。具体代码就不写了。
7、确认指定服务的功能测试能通过
我们可以把功能测试的代码封装成一个console程序,然后以编程的方式去执行,然后获取这些输出,输出结果应类似:“a用例通过,b用例出错。。。”,关于执行外部程序可以参考上文中调用ping的代码,如果你的服务只能在本机测试,要先部署在远程机器上,然后用WMI远程执行它,并把测试结果用网络API发给你。
8、抓取指定机器指定进程的dump
这需要执行远程机器上的程序,实现在所有的服务器上装好windbg,并约定好路径,抓网络包也要实现装好Wireshark。用WMI执行程序的代码大约如下,下面的代码没有写管理作用域,如果要加的话参考上面的代码
{
//Get the object on which the method will be invoked
ManagementClass processClass = new ManagementClass("Win32_Process");
//Get an input parameters object for this method
ManagementBaseObject inParams = processClass.GetMethodParameters("Create");
//Fill in input parameter values
inParams["CommandLine"] = command;
//Execute the method
ManagementBaseObject outParams = processClass.InvokeMethod("Create",inParams, null);
Console.WriteLine(outParams.GetText(TextFormat.Mof));
//Display results
//Note: The return code of the method is provided in the "returnvalue" property of the outParams object
Console.WriteLine("Creation of calculator process returned: " + outParams["returnvalue"]);
Console.WriteLine("Process ID: " + outParams["processId"]);
}
可以先用Wmi获取远程机器上服务的PID,然后远程执行"adplus -hang -p 2233 -quiet -o d:\dumps",然后可以以编程的方式通过网络共享把dump拷贝到本地。
9、在指定机器上进行网络抓包
具体方式和抓dump差不多,实现要在远程机器上装好Wireshark,命令行模式抓包大概如下
at 14:33 "C:\Program Files\Wireshark\dumpcap.exe" -i 2 -a duration:604800 -b files:1000 -f "host 10.0.0.1 and port 8081" -b filesize:10240 -w d:\network_dump\1.cap
具体参数参考wireshare的帮助,这是起一个任务去抓,你可以把at命令去了,也许你的服务器有多个网卡,所以实现要指定好要抓包的网卡,获取远程机器网卡信息的vbs代码如下,请自己修改为C#
strComputer = "."
Set objWMIService = GetObject("winmgmts:" _
& "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
Set colItems = objWMIService.ExecQuery("Select * from Win32_NetworkAdapter where NetConnectionStatus =2")
For Each objItem in colItems
Wscript.Echo "Name: " & objItem.Name
Next
dumpcap.exe的-i参数解释如下,有了网络适配器的名字或者索引就可以抓包了
-i <interface> name or idx of interface (def: first none loopback)
10、重启指定机器上的某服务,某应用程序进程池等
重启服务可以先停止,后启动,wmic命令如下,可用程序来执行
::停止spooler服务
wmic SERVICE where name="Spooler" call stopservice
::运行spooler服务
wmic SERVICE where name="Spooler" call startservice
获取某web应用的应用程序池的代码如下
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\MicrosoftIISv2")
Set colItems = objWMIService.ExecQuery( _
"SELECT * FROM IIsWebVirtualDirSetting",,48)
For Each objItem in colItems
Wscript.Echo "Name: " & objItem.Name & " -> Pool: " & objItem.AppPoolId
Next
获取后就可以进行对指定应用程序池的回收操作了,回收应用程序进程池代码大约如下,来自网络,未测试
{
private static void Main(string[] args)
{
ManagementScope scope = new ManagementScope("root\\MicrosoftIISv2");
scope.Connect();
ManagementObject appPool =
new ManagementObject(scope,
new ManagementPath("IIsApplicationPool.Name='W3SVC/AppPools/DefaultAppPool'"), null);
appPool.InvokeMethod("Recycle", null, null);
Console.WriteLine("Recycled DefaultAppPool");
}
}
更多的IIS管理操作可以使用Adsutil.vbs
这以上的一切都是为了集中的管理,而不用一个一个的登录到远程机器上去操作。综合以上这些东西,可以写一个综合的管理工具,在一条机器上就可以管理几十台机器,获取某几台机器的运行状况报表,远程重启多台服务器的某服务等。
WMIC:从命令行对Windows的全面管理
http://www.yesky.com/20030424/1665552.shtml
WMIC Process Call Create
http://wmug.co.uk/blogs/r0b/archive/2007/10/12/wmic-process-call-create.aspx
WMIC 全新的超级命令行管理工具
http://tech.sina.com.cn/other/2003-05-20/1702189076.shtml
一些wmic例子
http://blog.vkill.net/read.php?41
Can one recycle an application from a script in IIS 6.0?
http://blogs.iis.net/chrisad/archive/2006/08/30/Recycling-Application-Pools-using-WMI-in-IIS-6.0.aspx
关于 WMIC 的使用
http://bbs.hackerxfiles.net/viewthread.php?tid=91580