タグ:ターミナルサーバ ( 4 ) タグの人気記事
(.Net)ターミナルサーバでユーザのプロセスのメモリ使用量を取りたい。
WindowsServer2003上でターミナルサーバを運用してますが、どうやらメモリを使いすぎているユーザがいるようです。
それで、数日間どのユーザがどのプロセスでメモリを使いすぎているのか経過調査を行うことにしました。


当初はパフォーマンスログで取ろうかなと思っていたんですが、パフォーマンスログではそのプロセスを使っているのがどのユーザなのかがわかりません。

仕方が無いので .Net Framework で現在のプロセス情報を取得し、ユーザと紐付けてCSVに落とすアプリケーションを作ることにしました。
それをOSのタスクスケジューラーに仕込んで、一定間隔で動かす運用です。

CSVには全プロセスの情報と、ユーザ単位でメモリ使用量を集計した情報を別々に出力します。

現在動いているプロセスの情報は System.Diagnostics.Process.GetProcesses() で取れるんですが、そのプロセスがどのユーザがオーナーとなっているかがわかりません。
ココは(.Net)現在のユーザが起動した特定のプロセスを終了するで書いた WMI を使った方法を取ることにしました。


コードしては以下のような感じです。文字数制限のためハイライトはOFFです。(C# .NetFramework2.0)

try{
//ユーザとプロセスIDを取得。(WMI使用)
ManagementScope scope = new ManagementScope("\\\\.\\ROOT\\CIMV2");

//プロセス情報取得
System.Diagnostics.Process[] ps = System.Diagnostics.Process.GetProcesses();

scope.Connect();
ObjectQuery query = new ObjectQuery(@"SELECT * FROM Win32_Process");
ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query);
ManagementObjectCollection col = searcher.Get();

//プロセスIDとユーザ名のハッシュテーブル[プロセスID,ユーザ名]
Hashtable htPrcUsrMap = new Hashtable();
foreach (ManagementObject o in col){
string pid = o["ProcessId"].ToString();

Object[] UserInfo = new object[2];
//見つからない時があるので、try - catch
try{
o.InvokeMethod("GetOwner", UserInfo);
}catch (Exception){
UserInfo[0] = "unkwon";
}

string UserName = (string)UserInfo[0]; //実行ユーザ名称取得
//string DomainName = (string)UserInfo[1]; //実行ユーザのドメイン名称取得

//配列にプロセスIDとユーザ名格納
htPrcUsrMap.Add(pid, UserName);
}

//現在日時取得
DateTime dt = DateTime.Now;
//データを格納するためのテーブルインスタンス作成
DataSet1.tblProcessDataDataTable tbl = new DataSet1.tblProcessDataDataTable();

//出力ファイル名
string strExportFile_Process = @"C:\PerfLogs\processCounter_Process.csv";
string strExportFile_User = @"C:\PerfLogs\processCounter_User.csv";

StringBuilder strBld_Process = new StringBuilder();

foreach (System.Diagnostics.Process p in ps){
try{
DataSet1.tblProcessDataRow row = tbl.NewtblProcessDataRow();

//タイムスタンプ出力(日時分)
string timestamp = dt.ToString("yyyy-MM-dd HH:mm:00");
strBld_Process.Append(timestamp);
strBld_Process.Append(",");
row.timestamp = timestamp;

//ユーザ名出力
string user = (string)htPrcUsrMap[p.Id.ToString()];
strBld_Process.Append(user);
strBld_Process.Append(",");
row.user = user;

//プロセスID
string processId = p.Id.ToString();
strBld_Process.Append(processId);
strBld_Process.Append(",");
row.process_id = processId;

//プロセス名
string processName = p.ProcessName;
strBld_Process.Append(processName);
strBld_Process.Append(",");
row.process_name = processName;

//CPU使用率(未実装。とりあえず0にしておく)
double cpuPercent = 0;
strBld_Process.Append(cpuPercent);
strBld_Process.Append(",");
row.cpu = cpuPercent;

//CPU時間
TimeSpan cpuTime = p.TotalProcessorTime;
strBld_Process.Append(cpuTime.TotalSeconds);
strBld_Process.Append(",");
row.cpu_time = (decimal)cpuTime.TotalSeconds;

//workingset(物理メモリ使用量)
long workingset = p.WorkingSet64;
strBld_Process.Append(workingset);
strBld_Process.Append(",");
row.workingset = workingset;

//PrivateMemory(物理メモリ+スワップ使用量)
long privateMemory = p.PrivateMemorySize64;
strBld_Process.Append(privateMemory);
strBld_Process.Append(",");
row.privatememorysize = privateMemory;

//最大workingset
long perkworkingset = p.PeakWorkingSet64;
strBld_Process.Append(perkworkingset);
strBld_Process.Append(",");
row.peak_workingset = perkworkingset;

//プロセスパス
string processPath = p.MainModule.FileName;
strBld_Process.Append(processPath);
strBld_Process.Append(",");
row.process_path = processPath;

/*
//メインウィンドウキャプション(実行ユーザでしか出ない)
string windowCaption = p.MainWindowTitle;
strBld_Process.Append(windowCaption);
strBld_Process.Append(",");
*/

strBld_Process.Append(Environment.NewLine);
tbl.AddtblProcessDataRow(row);
}catch (Exception ex){
Console.WriteLine("エラー: {0}", ex.Message);
strBld_Process.Append(Environment.NewLine);
}
}

//ユーザ毎の統計データ生成
//重複を除去するため DataView を使う
DataView vw = new DataView(tbl);
//重複除去を第二引数に指定。第三引数で一意とすべき列を指定。(複数列でも可能)
DataTable tblRes = vw.ToTable("DistinctTable", true, new string[] { "user" });
//合計の列を追加
tblRes.Columns.Add("sum_cpu", Type.GetType("System.Double"));
tblRes.Columns.Add("sum_workingset", Type.GetType("System.Int64"));
tblRes.Columns.Add("sum_privatememorysize", Type.GetType("System.Int64"));
tblRes.Columns.Add("sum_processcount", Type.GetType("System.Int64"));
tblRes.Columns.Add("sum_cputime", Type.GetType("System.Decimal"));

//重複除いたDataTableをループし、元のDataTableから集計値を求める
foreach (DataRow row in tblRes.Rows) {
row["sum_cpu"] = tbl.Compute("SUM(cpu)", "user = '" + row["user"] + "'");
row["sum_cputime"] = tbl.Compute("SUM(cpu_time)", "user = '" + row["user"] + "'");
row["sum_workingset"] = tbl.Compute("SUM(workingset)", "user = '" + row["user"] + "'");
row["sum_privatememorysize"] = tbl.Compute("SUM(privatememorysize)", "user = '" + row["user"] + "'");
row["sum_processcount"] = tbl.Compute("Count(user)", "user = '" + row["user"] + "'");
}

//sum_workingset の降順でソートをかける
DataRow[] srtRows = (DataRow[])tblRes.Select("" , "sum_workingset DESC").Clone();
DataTable tblSrt = new DataTable();
tblSrt = tblRes.Clone();
foreach (DataRow row in srtRows){
tblSrt.ImportRow(row);
}

//テキスト生成
StringBuilder strBld_User = new StringBuilder();
strBld_User = new StringBuilder();
foreach (DataRow row in tblSrt.Rows){
strBld_User.Append(dt.ToString("yyyy-MM-dd HH:mm:00"));
strBld_User.Append(",");
strBld_User.Append(row["user"]);
strBld_User.Append(",");
strBld_User.Append(row["sum_cpu"]);
strBld_User.Append(",");
strBld_User.Append(row["sum_cputime"]);
strBld_User.Append(",");
strBld_User.Append(row["sum_workingset"]);
strBld_User.Append(",");
strBld_User.Append(row["sum_privatememorysize"]);
strBld_User.Append(",");
strBld_User.Append(row["sum_processcount"]);
strBld_User.Append(",");
strBld_User.Append(Environment.NewLine);
}

//ファイル出力
System.Text.Encoding enc = System.Text.Encoding.GetEncoding("shift_jis");
if (!File.Exists(strExportFile_Process)){
string strHead = "日時,ユーザ,プロセスID,プロセス名,CPU使用率(未実装)"
+ ",CPU時間,物理メモリ使用量,物理+スワップ使用量,最大物理メモリ使用量,プロセスパス";//,ウィンドウ名";
File.AppendAllText(strExportFile_Process, strHead + Environment.NewLine, enc);
}
if (!File.Exists(strExportFile_User)){
string strHead = "日時,ユーザ,CPU使用率合計(未実装)"
+ ",CPU時間合計(秒),物理メモリ使用量合計,物理+スワップ使用量合計,プロセス数";
File.AppendAllText(strExportFile_User, strHead + Environment.NewLine, enc);
}
File.AppendAllText(strExportFile_Process, strBld_Process.ToString(), enc);
File.AppendAllText(strExportFile_User, strBld_User.ToString(), enc);

}catch (Exception ex){
File.AppendAllText(@"C:\PerfLogs\err.txt" , DateTime.Now.ToString() + " " + ex.Message + " Trace:" + ex.StackTrace + Environment.NewLine);

}



ユーザの統計に使う DataTable は以下のような構成にしてます。

データセット名:DataSet
テーブル名:tblProcessData
列:timestamp :System.String
列:user :System.String
列:process_id :System.String
列:process_name :System.String
列:cpu :System.Double
列:workingset :System.Int64
列:privatememorysize :System.Int64
列:peak_workingset :System.Int64
列:process_path :System.String
列:cpu_time :System.Decimal

実際に動かすと、WMIを使ってプロセスとユーザ情報を取得する部分が非常に時間がかかります。
60ユーザくらいで800-900くらいのプロセスが動いている状態で、数分はかかります。
また、この部分、CPUも結構食うようで1コア専有しちゃうんですよね。

WMI以外にユーザとプロセスの情報を取る方法が知りたいと思う今日この頃です。

参考:
MSDN:Process メンバ (System.Diagnostics)
[VB / C#] 実行中プロセスの各種情報を取得
【C#】プロセス実行ユーザ名称の取得API - Insider.NET - @IT
[PR]
by jehoshaphat | 2014-02-11 00:46 | .Net開発 | Trackback | Comments(0)
ターミナルサーバで全ユーザのプリンタドライバの設定を変更する
ターミナルサーバ(WindowsServer2003 R2)でいくつかのプリンタドライバをインストールしているんですが、困ったことに初期値がカラーの片面かつ集約無し印刷になっています。
コスト削減のためにデフォルト値を白黒で集約有りの両面印刷にしたいと思っています。

個々のユーザに、プリンタの印刷設定の規定値を変更させたいのですが、徹底されるとは限りません。
という事で、全ユーザの印刷設定を一気に変える方を調べてみました。

サーバ管理者で、プリンタのプロパティを開き、[詳細設定]タブ→[標準の設定]で、白黒や集約等の設定を行います。

これをすると、すでにユーザが別の設定を指定場合でも、この標準の設定に置き換わります。
また、新しいユーザが使おうとしたときは、この標準の設定となります。

ちなみに、ターミナルサーバでなくても、プリンタの共有をしている場合も上記の設定を行うことで、クライアントの設定も変更できるようです。

これがWindowsServer2008で、ActiveDirectory組んでいると、グループポリーとかでもできるようですが。。。

参考:
Technet:印刷の既定値を設定する
MSサポート:Windows XP での印刷設定の変更
[PR]
by Jehoshaphat | 2011-09-23 23:02 | サーバがらみ | Trackback | Comments(0)
ターミナルサービス構成のセッション制限時間は手打ち入力が可能
ターミナルサーバを動かしているサーバで、ターミナルサービス構成 の RDP-Tcp のプロパティ → セッション タブでセッションタイムアウトの設定ができます。
ここの設定はコンボボックスになっていて時間を選べるんですが、当初ここは選べるだけと思ってました。
デフォルトで用意されている項目は しない,1分,5分,10分,15分,30分,1時間,2時間,3時間,1日,2日 しかありません。
しかし、手で値を入力できるようです。
例えば [5時間] といった感じです。

たまにこの設定に意味を忘れるのでメモしておきます。

・ユーザ設定より優先にする(切断されたセッションを終了)
 ActiveDirectoryの各ユーザアカウントにも同様の設定ができますが、これにチェックを入れるとサーバの設定が優先されるようです。

・切断されたセッションを終了
 ターミナルサービスでセッションを切断した場合(リモートデスクトップで×ボタンで閉じたときとか)に、どれくらい時間が経てばセッションを終了(強制ログオフ)するかを設定するようです。

・アクティブなセッションの制限
 セッションの最大可能時間を設定します。つまりターミナルサーバにログオンしててもこの時間が経つとセッションの切断もしくは終了させられます。

・アイドルセッションの制限
 アイドルつまりターミナルサーバにはログインしてるけど、操作してない場合、この時間が経つとセッションの切断もしくは終了させられます。


・ユーザ設定より優先にする(セッションの制限に達したり接続が中断した場合)
 ActiveDirectoryの各ユーザアカウントにも同様の設定ができますが、これにチェックを入れるとサーバの設定が優先されるようです。
この下の項目で、セッションの制限に達したり、接続が中断した場合に、セッションを切断するか終了するかを選択できます。

参考:
リモート・デスクトップの接続時間を制限する - @IT
[PR]
by jehoshaphat | 2011-02-17 23:45 | サーバがらみ | Trackback | Comments(0)
SunRayサーバからターミナルサーバ接続のRDPセッション

通常WindowsターミナルサーバをWindowsクライアントOSからリモートデスクトップで接続したときは、「ターミナルサービスマネージャ」でセッションの状態を見た時は「Active」になってます。
で、ログオフせずにリモートデスクトップのウィンドウを閉じるとセッション状態は「Disconnect」になります。つまり切断状態ですね。
リモートデスクトップウィンドウは表示してても、何も操作しなければアイドル時間がカウントされます。

で、SunRay2 でSun Ray Connector for Windows(uttsc) を使って Windows ターミナルサーバに接続してるんですが、デスクトップユニットのスマートカードを抜くと、ターミナルサーバのRDPセッションはどうなるのか実験してみました。
(実際のセッションは SunRayサーバ~ターミナルサーバ間で張られています。)

Disconnect(切断)になると期待してたんですが、Activeのままでした。
ということで、ターミナルサービス構成でRDPセッションのタイムアウトを設定するときは、SunRayをクライアントにしてる場合、「アイドルセッションの制限」を設定しないといけないということですね。
「切断されたセッションを終了」は、SunRayがクライアントの場合意味をなさないようです。
[PR]
by jehoshaphat | 2011-01-17 22:02 | サーバがらみ | Trackback | Comments(0)