اذهب إلى المحتوى

التشخيص Diagnostics في dot Net


رضوى العربي

يُوفِّر فضاء الاسم System.Diagnostics العديد من الأصناف بهَدَف التَشخِيص، مثال أصناف للتَعامُل مع عمليات النظام (processes)، وأُخرى تَعمَل كعَدادات لقياس الأداء.

أوامر الصدفة (shell commands)

تنفيذ أوامر الصَّدَفَة

يُستخدَم التابع Process.Start لتَّنْفيذ أمر صَّدَفَة (shell commands) تَّنْفيذًا برمجيًا. يتم ذلك من خلال إرساله للأمر المُمرَّر إليه إلى برنامج سطر الأوامر cmd.exe، كالتالي:

using System.Diagnostics;

string strCmdText = "/C copy /b Image1.jpg + Archive.rar Image2.jpg";

Process.Start("CMD.exe",strCmdText);

تَتحَكَم الخاصية WindowStyle بحالة نافذة سطر الأوامر (cmd) عند تَّنْفيذ الأمر، فمثلًا يُمكِن إخفائها كالتالي:

using System.Diagnostics;

Process process = new Process();

ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
startInfo.FileName = "cmd.exe";
startInfo.Arguments = "/C copy /b Image1.jpg + Archive.rar Image2.jpg";

process.StartInfo = startInfo;

process.Start();

إرسال أوامر إلى سطر الأوامر واستقبال خرجها

يُعيد التابع SendCommand -المُعرَّف بالمثال التالي- سِلسِلة نصية تتَضمَن مُحتوَى كلًا من مَجْرى الخَرْج القياسي (STDOUT) ومَجْرى الخَطأ القياسي (STDERR) بعد تَّنْفيذ أمر صدَفَة، بالاعتماد على الحَدَثين OutputDataReceived و ErrorDataReceived، كالتالي:

private static string SendCommand(string command)
{
    var cmdOut = string.Empty;

    var startInfo = new ProcessStartInfo("cmd", command)
    {
        WorkingDirectory = @"C:\Windows\System32", 
        WindowStyle = ProcessWindowStyle.Hidden, // لإخفاء نافذة سطر الأوامر
        UseShellExecute = false, // لا تستخدم طرفية نظام التشغيل لبدء العملية
        CreateNoWindow = true, // ابدأ العملية بنافذة جديدة
        RedirectStandardOutput = true, // مطلوب لإتاحة مجرى الخرج القياسي
        RedirectStandardError = true // مطلوب لإتاحة مجرى الخطأ القياسي
    };

    var p = new Process {StartInfo = startInfo};
    p.Start();
    p.OutputDataReceived += (x, y) => cmdOut += y.Data;
    p.ErrorDataReceived += (x, y) => cmdOut += y.Data;
    p.BeginOutputReadLine();
    p.BeginErrorReadLine();
    p.WaitForExit();
    return cmdOut;
}

يُمكِن استدعاء التابع بالأعلى كالآتي:

var servername = "SVR-01.domain.co.za";
var currentUsers = SendCommand($"/C QUERY USER /SERVER:{servername}")

يَكُون الخَرْج كالتالي:

string currentUsers = "USERNAME SESSIONNAME ID STATE IDLE TIME LOGON TIME Joe.Bloggs ica-cgp#0 2 Active 24692+13:29 25/07/2016 07:50 Jim.McFlannegan ica-cgp#1 3 Active . 25/07/2016 08:33 Andy.McAnderson ica-cgp#2 4 Active . 25/07/2016 08:54 John.Smith ica-cgp#4 5 Active 14 25/07/2016 08:57 Bob.Bobbington ica-cgp#5 6 Active 24692+13:29 25/07/2016 09:05 Tim.Tom ica-cgp#6 7 Active . 25/07/2016 09:08 Bob.Joges ica-cgp#7 8 Active 24692+13:29 25/07/2016 09:13"

ملحوظة:

سيُعيد التابع المُعرَّف بالأعلى مُحتوَى كلًا من مَجْرى الخَرْج القياسي (STDOUT) ومَجْرى الخٍَطأ القياسي (STDERR) مَضْمومين بسِلسِلة نصية واحدة؛ حيث يُلحِق كلًا من الحَدَثين OutputDataReceived و ErrorDataReceived البيانات المُستلَمة إلى نفس المُتَغيّر cmdOut.

يَقتصِر أحيانًا الولوج إلى الخادم المَعنِّى على مُستخدِمين بعينهم. إذا كان لديك بيانات دخول مُستخدِم مُعين، فمن المُمكن أن تُرسِل اِستعلامات (queries) إلى ذلك الخادم كالتالي:

private static string SendCommand(string command)
{
    var cmdOut = string.Empty;

    var startInfo = new ProcessStartInfo("cmd", command)
    {
        WorkingDirectory = @"C:\Windows\System32",
        // This does not actually work in conjunction with "runas" - the console window will still appear!
        WindowStyle = ProcessWindowStyle.Hidden, 
        UseShellExecute = false,
        CreateNoWindow = true,
        RedirectStandardOutput = true,
        RedirectStandardError = true,
        Verb = "runas",
        Domain = "doman1.co.za",
        UserName = "administrator",
        Password = GetPassword()
    };

    var p = new Process {StartInfo = startInfo};
    p.Start();
    p.OutputDataReceived += (x, y) => cmdOut += y.Data;
    p.ErrorDataReceived += (x, y) => cmdOut += y.Data;
    p.BeginOutputReadLine();
    p.BeginErrorReadLine();
    p.WaitForExit();
    return cmdOut;
}

التابع GetPassword لجلْب كلمة السر:

static SecureString GetPassword()
{
    var plainText = "password123";
    var ss = new SecureString();
    foreach (char c in plainText)
    {
        ss.AppendChar(c);
    }
    return ss;
}

ضبط الإعداد ProcessThread.ProcessorAffinity

تُعبر الخاصية ProcessorAffinity من النوع IntPtr عن المُعالج (processor) الذي يُنفِّذ خيط العملية (process thread). يَعتمِد هذا الإعداد افتراضيًا على عدد مُعالِجات الحاسوب.

اِستخدِم الشيفرة التالية لجَلْب خاصية ProcessorAffinity لعملية:

public static int GetProcessAffinityMask(string processName = null)
{
    Process myProcess = GetProcessByName(ref processName);
    int processorAffinity = (int)myProcess.ProcessorAffinity;

    Console.WriteLine("Process {0} Affinity Mask is : {1}", processName, FormatAffinity(processorAffinity));

    return processorAffinity;
}

public static Process GetProcessByName(ref string processName)
{
    Process myProcess;
    if (string.IsNullOrEmpty(processName))
    {
        myProcess = Process.GetCurrentProcess();
        processName = myProcess.ProcessName;
    }
    else
    {
        Process[] processList = Process.GetProcessesByName(processName);
        myProcess = processList[0];
    }
    return myProcess;
}

private static string FormatAffinity(int affinity)
{
    return Convert.ToString(affinity, 2)
                .PadLeft(Environment.ProcessorCount, '0');
}

يُستخدَم كالآتي:

private static void Main(string[] args)
{
    GetProcessAffinityMask();
    Console.ReadKey();
}

الخَرْج:

// Output:
// Process Test.vshost Affinity Mask is : 11111111

اِستخدِم الشيفرة التالية لضبط خاصية ProcessorAffinity لعملية:

public static void SetProcessAffinityMask(int affinity, string processName = null)
{
    {
        Process myProcess = GetProcessByName(ref processName);

        Console.WriteLine("Process {0} Old Affinity Mask is : {1}", processName, FormatAffinity((int)myProcess.ProcessorAffinity));

        myProcess.ProcessorAffinity = new IntPtr(affinity);

        Console.WriteLine("Process {0} New Affinity Mask is : {1}", processName, FormatAffinity((int)myProcess.ProcessorAffinity));
    }

يُستخدَم كالآتي:

private static void Main(string[] args)
{
 int newAffinity = Convert.ToInt32("10101010", 2);
 SetProcessAffinityMask(newAffinity);
 Console.ReadKey();
}

الخَرْج:

// Output :
// Process Test.vshost Old Affinity Mask is : 11111111
// Process Test.vshost New Affinity Mask is : 10101010

قياس الأداء باستخدام النوع Stopwatch

يُمكِن اِستخدَام النوع Stopwatch بفضاء الاسم System.Diagnostics لقياس أداء (benchmark) كُتلة من الشيفرة، كالتالي:

using System;
using System.Diagnostics;

public class Benchmark : IDisposable
{
    private Stopwatch sw;

    public Benchmark()
    {
        sw = Stopwatch.StartNew();
    }

    public void Dispose()
    {
        sw.Stop();
        Console.WriteLine(sw.Elapsed);
    }
}

public class Program
{
    public static void Main()
    {
        using (var bench = new Benchmark())
        {
            Console.WriteLine("Hello World");
        }
    }
}

مواصفات اصطلاحية للشيفرة (code contracts)

يُوفِّر فضاء الاسم System.Diagnostics.Contracts العديد من الأصناف لتَعزيز الشيفرة الخاصة بك بمزيد من الشروط (conditions) اللازم تَحقيقها إمّا خلال وقت التَصْريف أو التَّنْفيذ، مما يُحسِن من فَحْص الشيفرة واكتشاف الأخطاء.

تثبيت المواصفات الاصطلاحية للشيفرة وتفعيلها

يأتي فضاء الاسم System.Diagnostics.Contracts ضِمْن إطار عمل ‎.NET. لكن ما زلت في حَاجة إلى تَثْبيت الإضافة Code Contracts Tools ببيئة التطوير المتكاملة فيجوال ستوديو (Visual Studio IDE) حتى تستطيع اِستخدَام المُواصَفَات الاصطلاحيّة للشيفرة (code contracts).

يُمكنك البحث عن Code Contracts بنافذة الإضافات والتحديثات Extensions and Updates بفيجوال ستوديو.

002Install_Code_Contracts_Tools.png

يجب أن تُفعِّل خاصية المُواصَفَات الاصطلاحيّة للشيفرة (code contracts) بحل المشروع بعد الإنتهاء من تَثْبيت الإضافة.

تحتاج إلى تَّفْعِيل خاصية الفَحْص الساكن (Static Checking) -فَحْص ما بعد البناء (build)- على الأقل. قد تَرغَب أيضًا بتَّفْعِيل خاصية الفَحْص أثناء التشغيل (Runtime Checking) بالتحديد إذا كنت تُطَوِّر مكتبة (library) ستُستَعَمَل من قِبل حلول (solutions) أُخرى.

003Configure_Code_Contracts_Tools.png

الشروط المسبقة (Preconditions)

يَضمَن استخدام الشروط المُسبَقة للتوابع (preconditions) الحد الأدنى من مُتطلَّبات قيم مُعامِلات الدَخْل لتلك التوابع. انظر المثال التالي:

void DoWork(string input)
{
    Contract.Requires(!string.IsNullOrEmpty(input));


    //do work
}

نتائج تحليل الفَحْص الساكن:

004Precondition_Static_Analysis_Result.png

الشروط اللاحقة (Postconditions)

يَضمَن اِستخدَام الشروط اللاحقة للتوابع (postconditions) تَوَافُق النتائج التي تُعيدها تلك التوابع مع التَعرِيف المُخصص للنتائج المتوقعة. يُساعد ذلك على تنْفيذ مُبسَط من خلال إمداد المُحلِّل الساكن (static analyizer) بالعديد من النتائج المُحتَملة. انظر المثال التالي:

string GetValue()
{
    Contract.Ensures(Contract.Result<string>() != null);


    return null;
}

نتائج تحليل الفَحْص الساكن:

005Postcondition_Static_Analysis_Result.png

إضافة مواصفات اصطلاحيّة للشيفرة إلى الواجهات (interfaces)

يُمكِن أيضًا فَرْض مُواصَفَات اصطلاحيّة للشيفرة (code contracts) على وَاجِهة (interface) عن طريق الإعلان عن صنف مُجرَّد (abstract class) يُنفِّذ هذه الواجهة بشرط أن تُزخرَف الواجهة والصنف المُجرَّد بالسمتين ContractClassAttribute و ContractClassForAttribute على الترتيب. انظر المثال التالي:

[ContractClass(typeof(MyInterfaceContract))]
public interface IMyInterface
{
    string DoWork(string input);
}

[ContractClassFor(typeof(IMyInterface))]
internal abstract class MyInterfaceContract : IMyInterface
{
    private MyInterfaceContract() { }
    public string DoWork(string input)
    {
        Contract.Requires(!string.IsNullOrEmpty(input));
        Contract.Ensures(!string.IsNullOrEmpty(Contract.Result<string>()));
        throw new NotSupportedException();
    }
}

public class MyInterfaceImplmentation : IMyInterface
{
    public string DoWork(string input)
    {
        return input;
    }
}

في المثال بالأعلى، تُعلِّن الواجهة IMyInterface عن التابع DoWork الذي يَستقبِل مُعامِلًا من النوع string. في الحالة العادية، يُمكنك أن تُمرِّر القيمة الفارغة null إليه. لكن لا يُصبِح ذلك مُمكنًا بعد إضافة المُواصفة الإصطلاحيّة بالأعلى والتي تَستخدِم التابع Contract.Requires لفَرْض شرط مُسبَق (precondition) بألا يَكون المُعامِل المُمرّر فارغًا.

نتائج تحليل الفَحْص الساكن:

001Example_Result.png

ترجمة -وبتصرف- للفصول System.Diagnostics - Code Contracts - Process and Thread Affinity setting من كتاب ‎.NET Framework Notes for Professionals


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...