如果一個服務被部署到了幾十台機器上,我們往往需要每天花費很多的時間去查看每台機器上的服務的健全狀態,雖然微軟有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介面來擷取遠程機器的進程列表,然後遍曆這個列表,確認指定服務的進程名字是否在這個列表裡,核心代碼(來自網路)如下:
public static DataTable RunningProcesses()
{
// 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 class PerfCounter
{
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();
}
}
使用如下
List<PerfCounter> counters = new List<PerfCounter>();
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來建立一個串連來測試遠程連接埠是否在監聽,大約如下
public static bool IsListenPort(string remoteHost, int port)
{
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實現
public static string ping(string remoteHost)
{
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 class EventLogHelper
{
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執行程式的代碼大約如下,下面的代碼沒有寫管理範圍,如果要加的話參考上面的代碼
public static void Execute(string command)
{
//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#
On Error Resume Next
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應用的應用程式集區的代碼如下
strComputer = "."
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
擷取後就可以進行對指定應用程式集區的回收操作了,回收應用程式進程池代碼大約如下,來自網路,未測試
internal class RecycleAppPool
{
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