יום שישי, 11 באוקטובר 2013

Linq Deep Dive - Where

כאשר חלק מהמתכנתים מביטים לראשונה על שאילתות Linq , הם נראות להם מאיימות.

בפוסט זה אסביר איך בעצם ממומשים מתודות Linq "מאחורי הקלעים" - באמצעות הדגמה על Where.

למשל: יש לנו collection של מספרים:

var nums = new[] {1,2,3,4,5,6,7,8,9,10};

אם נרצה לקבל את כל המספרים האי זוגיים, אז באמצעות Linq נבצע:

result = nums.Where(n => n % 2 != 0);

בואו נעשה drill-down ונבין איך זה ממומש. נתחיל מהתחלה:

נממש את פונקציית FilterIntegers שמקבלת רשימה של מספרים, ומחזירה רשימה חדשה רק של המספרים האי זוגיים.
הפונקציה נעזרת בפונקציית Predicate שמטרתה לבדוק אם המספר הוא אי-זוגי:


/// <summary>
        /// return IEnumerable that contains only the numbers that are not Even
        /// </summary>
        /// <param name="nums"></param>
        /// <returns></returns>
        private static IEnumerable<int> FilterIntegers(IEnumerable<int> nums)
        {
            var result = new List<int>();

            foreach (var item in nums)
            {
                if (Predicate(item))
                {
                    result.Add(item);
                }
            }

            return result;
        }

        /// <summary>
        /// return true if the item is not Even.
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public static bool Predicate(int item)
        {
            return item % 2 != 0;
        }

וכעת אם נבצע את הקריאה הבאה:

var result = FilterIntegers(nums);

אז נקבל אל result את רשימת המספרים האי זוגיים.

איך אפשר לשפר את הפונקציה?

בואו נאמר שאנחנו רוצים גם פונקציה שתחזיר לנו את המספרים הזוגיים. הרי לא נכתוב עוד פונקציה לכך נכון? אז נשתמש באותה הפונקציה-FilterIntegers ופשוט נעביר לפונקציה delegate שיצביע לפונקציית סינון כלשהי, וכך FilterIntegers יידע לסנן רשימת מספרים ע"י כל פונקציית סינון שנחליט.

זה ייראה כך:

private static IEnumerable<int> FilterIntegers(IEnumerable<int> nums,PredicateDelegate<int> predicateDelegate )
        {
            var result = new List<int>();

            foreach (var item in nums)
            {
                if (predicateDelegate(item))
                {
                    result.Add(item);
                }
            }

            return result;
        }


נוסיף עוד פונקציית מיון (לשם הדוגמא נכתוב פונקצייה שמחזירה מספרים זוגיים:
public static bool IsEven(int item)
{
    return item % 2 == 0;
}

וכמובן ניצור delegate מתאים:

public delegate bool PredicateDelegate<T1>(T1 value);

כעת נוכל לבצע פילטור כך:

//point the delegate to any function
PredicateDelegate<int> myDelegate = IsEven;

var result = FilterIntegers(nums,myDelegate);

ונוכל לשלוח ב delegate מצביע לאיזה פונקציית סינון שנרצה.

בואו נתקדם עוד שלב:

נשנה את הפונקציה FilterIntegers כך שתהיה יותר גנרית, ונשנה לה את השם ל Filter, כי כעת היא תוכל לסנן רשימה של כל Type:

public static IEnumerable<T1> Filter<T1>(IEnumerable<T1> nums, PredicateDelegate<T1> predicateDelegate)
{
    var result = new List<T1>();

    foreach (T1 item in nums)
    {
            if (predicateDelegate(item))
            {
              result.Add(item);
            }
    }

       return result;
 }

כעת רק כדי שיהיה לנו יותר קל (ולא נצטרך ליצור delegate) נשתמש ב Func כמו שראינו בפוסט הקודם:


public static IEnumerable<T1> Filter<T1>(IEnumerable<T1> list, Func<T1, bool> predicateDelegate)
{
    var result = new List<T1>();

    foreach (T1 item in nums)
    {
            if (predicateDelegate(item))
            {
              result.Add(item);
            }
    }

       return result;
 }

ונקרא כך לפונקציה שלנו:
ה delegate שלנו יצביע על פונקציית הסינון (הפעם נסנן מספרים אי-זוגיים):
Func<int, bool> predicateDelegate = IsEven;

ונפעיל את הפונקציה:
result= Filter(nums,predicateDelegate)

אז מה יש לנו עד עכשיו?

פונקצייה גנרית בשם Filter, שמקבלת רשימה של אובייקטים, ומקבלת delegate לפונקצייה, ומחזירה לנו רשימה של האיברים שעונים על התנאי של הפונקציה שאליה מצביע ה delegate.

נמשיך-

במקום שה delegate שלנו יצביע לפונקציה שהיא בעצם ממומשת ע"י שורה אחת, נוכל להשתמש ב anonymous methods:

predicateDelegate = item => { return item % 2 == 0 ; };

ואפשר לכתוב את הנ"ל בכתיב מקוצר:

predicateDelegate = item =>  item % 2 == 0;

אז במקום לשלוח לפונקציה שלנו את predicateDelegate, אנחנו יכולים ישר לשלוח את ה  anonymous method שלנו:

result= Filter(nums, item =>  item % 2 == 0);

עכשיו אנחנו ממש מתקרבים למימוש האמיתי של Where.

בעצם נותר לנו לבצע 2 פעולות:

  1. לשנות את הפונקציה ל  Extension method- כדי להפוך את הפונקציה ל Extension method פשוט נוסיף את המילה this לפני הארגומנט הראשון, ונעביר את הפונקציה ל class סטטי.
  2. לבצע yield return מהפונקציה- כך לא צריך לחכות עד שהפונקציה תסיים לחשב את מה שהיא צריכה, ועל כל איבר שהיא החליטה אם הוא תקין, אז היא תחזיר אותו (או יותר נכון Ienumerable )
  public static IEnumerable<T1> Filter<T1>( this IEnumerable<T1> list, Func<T1, bool> predicateDelegate)
{
    var result = new List<T1>();

    foreach (T1 item in nums)
    {
            if (predicateDelegate(item))
            {
              yield return item;
            }
    }
}

וזהו! סיימנו! מימשנו את פונקציית Where.

כעת נוכל לקרוא לפונקציית Filter:

result= nums.Filter( item =>  item % 2 == 0);

שזה בדיוק כמו:

result= nums.Where( item =>  item % 2 == 0);


תהנו!

יום חמישי, 3 באוקטובר 2013

Delegate, Action, Func ומה שבינהם

חבר בעבודה שאל אותי השבוע: מה זה Func?

אז בשביל להסביר לו, עניתי לו שזה פשוט צורה יותר קלה להגדרת Delegate.

אז נתחיל מההתחלה: מה זה Delegate?

 Delegate זה אובייקט Reference שמצביע לפונקציה.היתרון בשימוש ב Delegate זה שצד שלישי יכול להגדיר איזה פונקציה הוא בדיוק רוצה שתופעל, והמימוש שלנו לא יהיה תלוי בכך.

הגדרת Delegate:

הגדרת ה Delegate תהיה תחת ה namespace שלנו, ולא תחת Class מסוים. לדוגמא:

delegate void PrintDelegate(string st);

מה הגדרנו כאן: הגדרנו שה Delegate שלנו יכול להצביע לכל פונקציה שהחתימה שלה: מחזירה Void ומקבלת String.

נאמר שיש לנו בתוכנית את שתי הפונקציות הנ"ל:

static void PrintOne(string st)
        {
            System.Console.WriteLine("{0} is in PrintOne Function", st);
        }

        static void PrintTwo(string st)
        {
            System.Console.WriteLine("{0} is in PrintTwo Function", st);
        }

לאחר שנריץ את הקוד הבא:

static void Main(string[] args)
        {
            PrintDelegate p = new PrintDelegate(PrintOne);
            
            p("Eyal");
        }

נקבל:
Eyal is in PrintOne Function

בשורה מספר 3 הגדרנו שה Delegate שלנו יצביע לפונקציה PrintOne, ובשורה 5 פשוט קראנו לDelegate שלנו בדיוק כמו שהיינו קוראים אם היינו קוראים לפונקציה.

Multycast Delegate

Delegate יכול להצביע גם על מספר פונקציות (נקרא Multycast Delegate). וכאשר "מפעילים" את ה Delegate, הוא יקרא לפונקציות בסדר שהם נרשמו. את הרישום נעשה באמצעות אופרטור =+

למשל:

static void Main(string[] args)
        {
            PrintDelegate p = new PrintDelegate(PrintOne);
            p += PrintTwo;
            p("Eyal");
        }


ונקבל:
Eyal is in PrintOne Function
Eyal is in PrintTwo Function

Closure

שימוש נפוץ ל Delegate הוא ביצוע Closure, כלומר העברת פונקציה כפרמטר.

נניח ויש לנו את הפונקציה הנ"ל:

static void MyPrintMethod(PrintDelegate p)
        {
            p("MyPrintMethod");
        }

הפונקציה מקבלת כפרמטר Delegate ומפעילה אותו.

ואז בתכנית שלנו פשוט נוכל לאתחל את ה Delegate עם הפונקציה שאנו רוצים לשלוח, וכך להפעיל את הפונקציה:

MyPrintMethod(new PrintDelegate(PrintTwo));

ונקבל:
MyPrintMethod is in PrintTwo Function

Generic Delegate

אנחנו יכולים גם להגדיר שה Delegate שלנו יהיה גנרי, ואז לא נצטרך לרשום Delegate שונים עבור פונקציות שמקבלות Type שונים:

לשם ההדגמה נניח ויש לנו פונקציית הדפסה חדשה:


static void PrintThree(int num)
        {
            System.Console.WriteLine("{0} is in PrintThree Function", num);
        }

עכשיו תחת ה namespace נגדיר את ה Delegate הגנרי הבא:

delegate void PrintGenericDelegate(T t);

וכעת נוכל להשתמש עם אותו ה Delegate שיצביע על פונקציות עם חתימות שונות:



PrintGenericDelegate<string> pgs = new PrintGenericDelegate<string>(PrintTwo);

 pgs("Eyal");

 PrintGenericDelegate<int> pgi = new PrintGenericDelegate<int>(PrintThree);

 pgi(111);
ונקבל:
Eyal is in PrintTwo Function
111 is in PrintThree Function

<Action<T

יופי, אז ראינו שימושים שונים ל Delegate.
אבל זה קצת מסורבל, לא?

החל מ NET 2.0 קיבלנו את Action, שזהו פשוט סוג של Delegate, שיודע לקבל עד 16 פרמטרים, ומחזיר void. השימוש ב Action מקל לנו על כתיבת הקוד.

תראו איזה פשוט נהיה השימוש ב Delegate, באמצעות Action:
אין צורך להגדיר Delegate ב namespace, אלא פשוט יוצרים Action, ונותנים לו הצבעה לפונקציה:


Action<string> MyAction = PrintTwo;

 MyAction("action");

ונקבל:
action is in PrintTwo Function

ונראה איזה פשוט זה לשלוח פונקציה כפרמטר באמצעות Action (ביצוע Closure):

נניח ויש לנו את הפונקציה:

static void MyPrintMethodAction(Action<string> action)
        {
            action("MyPrintMethodAction");
        }

נפעיל את הקריאה ע"י:

Action<string> MyAction = PrintTwo;

MyPrintMethodAction(MyAction);

ונקבל:
MyPrintMethodAction is in PrintTwo Function

<Func<T,Result

ולשאלתנו, מה זה Func? זה פשוט כל מה ש Action, רק ש Func מחזיר פרמטר.

יש לנו לדוגמא את הפונקציה הפשוטה הבאה שמקבלת פרמטר מסוג int, ומחזירה int:

static int MultiplyTwice(int num)
        {
            return num * 2;
        }


נשתמש עם ה Delegate הבא:

Func<int,int> MyFunc = MultiplyTwice;

Console.WriteLine(MyFunc(4));


ונקבל: 8

שימו לב שב Func, הפרמטר הגנרי האחרון יהיה הטיפוס שאנחנו מחזירים. מכיוון ש Func תמיד מחזיר ערך אז Func T לא מקבל שום פרמטר, אך מחזיר פרמטר מסוג T.

תהנו!!

יום שישי, 27 בספטמבר 2013

Memory Allocation in the CLR

אז מה קורה בזכרון כאשר אנחנו יוצרים אובייקט מסוג ReferenceType בזיכרון?

נניח ויש לנו אובייקט מסוג Student שמכיל מספר פונקציות ומספר משתנים.

לאחר שנבצע ()new Student , הייצוג של האובייקט בזיכרון יראה כך:


ועכשיו להסבר:

כשנאתחל את האובייקט - מכיוון שהוא מסוג ReferenceType ,יוקצה לו מקום על גבי ה GC Heap ויהיה לעברו מצביע (בציור הנ"ל המצביע הוא על גבי הStack, אך לא מן הנמנע שהמצביע יהיה על גבי ה GC Heap)

לכל אובייקט שנמצא על גבי ה GC Heap יש שני שדות שמתווספים אוטומטית לאובייקט (ושגודל של כל אחד מהם 4bytes) השדות הם:

  1.   Sync Block Index -SBI - מצביע לטבלת synchronisation הבנויה מ Sync Blocks. הטבלה מכילה את המידע האם בוצע על האובייקט lock או לא. כל פעם שמבוצע lock על האובייקט, נוצר Sync Block חדש שמכיל מידע אודות ה lock.
  2.  Type Object Pointer - TOP- מצביע למקום שנקרא Method Table הנמצא על ה High Frequency Heap, המתאר את האובייקט. (ארחיב בהמשך)
High Frequency Heap- מקום על הHeap המכיל נתונים שניגשים אליהם בתדירות גבוהה. הוא לא מנוהל ע"י ה GC, הוא נוצר עם יצירת ה AppDomain, לכל AppDomain יש אחד כזה, ומטבע הדברים הוא מתנקה כאשר ה AppDomain "מת". דבר חשוב מאוד לדעת - כל המשתנים הסטטיים נמצאים על גבי ה High Frequency Heap, ומכאן שהם לא מתנקים ע"י ה GC..

Method Table - לפני שאנחנו יוצרים את ה Instance הראשון של האובייקט, כאשר כל ה Class וה Interface נטענים לראשונה ל AppDomain, נוצר על גבי ה High Frequency Heap ה Method Table, שמכיל את כל הנתונים על האובייקט, כמו: מאיזה type הוא, איזה פונקציות הוא מכיל (כולל כל ה ctor) ואת המשתנים הסטטיים.
מכאן הקומפיילר יודע איזה פונקציה האובייקט יפעיל (במקרים של הורשה למשל).
 לדוגמא: אם ניצור אובייקט בלי שנדרוס את ToString, בMethod Table תופיע הפונקציה ToString שה MethodDescription שלו יהיה שייך למחלקה System.Object, ומכאן - כאשר האובייקט יפעיל את ToString, אז יופעל ה ToString הדיפולטי שהמחלקה שלנו יורשת ממנו (במקרה הנ"ל זה System.Object). אך אם ניצור אובייקט שכתבנו לו פונקציה שדורסת את ToString, אז ה MethodDescription שלנו יהיה שייך למחלקה שלנו (Student), וכאשר האובייקט יפעיל את ToString, אז יופעל ה ToString שאנחנו כתבנו..


מי שרוצה לרדת עוד יותר לעומק הדברים, מאמר מצוין נמצא כאן

תהנו!

יום שישי, 20 בספטמבר 2013

יצירת Performance Counter

בהמשך לפוסטים האחרונים שלי על Tracing, הנה דרך מצוינת לאסוף סטטיסטיקות על האפליקציה שלנו:

בכל זמן נתון מערכת ההפעלה של Windows אוספת סטטיסטיקות על המערכת (כגון CPU, IO, סטטיסטיקות על IIS ועוד המון אחרים..). את הסטטיסטיקות אפשר לראות דרך ה Perfmon.exe.
בדרך זו אפשר ללמוד המון על המערכת שלנו (למשל כאשר היא רצה בסביבת Production).
יתרון גדול שמערכת ההפעלה מאפשרת לנו הוא שאנחנו יכולים ליצור Counter's משלנו, וכך יכולים לעקוב אחר ביצועי האפליקציה שלנו (למשל נוכל לראות כמה זמן בממוצע לוקח לשרות שלנו לעבד בקשה, כמו login בוצעו לאפליקציה, וכל סטטיסטיקה אחרת שאפשר לדמיין).

באמצעות ה PerformanceCounter class ש .Net מספק לנו, אנחנו יכולים לגשת לנתוני Counter שונים, ואף ליצור Counter חדשים לאפליקציה שלנו.

יש כל מיני Counter Types, אך השכיחים ביותר:
  1. NumberOfItems32- סופר מספר פריטים (למשל אפשר לספור כמה בקשות בוצעו)
  2. AverageTimer32- סופר את הזמן הממוצע שנדרש לפעולה מסוימת להיות מבוצעת
  3. RateOfCountsPerSecond32- סופר את מספר הפעולות שבוצעו בכל שנייה
יצירת Performance Counter חדש:

הדרך הראשונה (והפחות עדיפה) היא דרך ה Server Explorer  ב VS:



הדרך השניה היא ליצור באמצעות קוד שנכתוב:

ניצור את ה Counter באמצעות הקוד (ככה שנראה אותו ב Perfmon..):

 
        if (!PerformanceCounterCategory.Exists("MyNewCategory"))
            {
                CounterCreationDataCollection counters = new CounterCreationDataCollection();

                //counting totals: PerformanceCounterType.NumberOfItems32
                CounterCreationData totalOperations = new CounterCreationData();
                totalOperations.CounterName = "# operations executed";
                totalOperations.CounterType = PerformanceCounterType.NumberOfItems32;
                counters.Add(totalOperations);

                //counting operations per second: PerformanceCounterType.RateOfCountsPerSecond32
                CounterCreationData opsPerSecond = new CounterCreationData();
                opsPerSecond.CounterName = "# operations / sec";
                opsPerSecond.CounterType = PerformanceCounterType.RateOfCountsPerSecond32;
                counters.Add(opsPerSecond);

                //counting average time per operation: PerformanceCounterType.AverageTimer32
                CounterCreationData avgDuration = new CounterCreationData();
                avgDuration.CounterName = "average time per operation";
                avgDuration.CounterType = PerformanceCounterType.AverageTimer32;
                counters.Add(avgDuration);

                //base counter for counting average time per operation: PerformanceCounterType.AverageBase
                CounterCreationData avgDurationBase = new CounterCreationData();
                avgDurationBase.CounterName = "average time per operation base";
                avgDurationBase.CounterType = PerformanceCounterType.AverageBase;
                counters.Add(avgDurationBase);

                // create new category with the counters above
                PerformanceCounterCategory.Create("MyNewCategory", 
                        "Sample category", counters);
            }

שימו לב!! אם יצרתם  AverageTimer אז חייבים מיד אחריו ליצור AverageBase.

לאחר שנריץ את הקוד הנ"ל יהיה אפשר להכנס ל Perfmon ולקבל:


עכשיו כל אפליקציה תוכל לכתוב/לקרוא נתונים על ה Counter שיצרנו (MyNewCategory)

יצירת האובייקט של ה Performance Counter באפליקציה שלנו:

כעת באפליקציה שלנו נרצה לאתחל את ה Counter's כדי שנוכל להתממשק לקטגוריה שיצרנו

     _TotalOps = new PerformanceCounter();
            _TotalOps.CategoryName = "MyNewCategory";
            _TotalOps.CounterName = "# operations executed";
            _TotalOps.MachineName = ".";
            _TotalOps.ReadOnly = false;

            _OpsPerSecond = new PerformanceCounter();
            _OpsPerSecond.CategoryName = "MyNewCategory";
            _OpsPerSecond.CounterName = "# operations / sec";
            _OpsPerSecond.MachineName = ".";
            _OpsPerSecond.ReadOnly = false;

            //another way to create...
            _AvgDuration = new PerformanceCounter("MyNewCategory",
            "average time per operation",
            false);

            _AverageDurationBase = new PerformanceCounter("MyNewCategory",
            "average time per operation base",
            false);

שימו לב!! כשאתחלנו את המשתנים באפליקציה שלנו- אם אחד מהפרמטרים לא תואם את מה שקיים על השרת (על פי מה שיצרנו בשלב הראשון- למשל ה Category שונה או ה CounterName שונה אז נקבל InvalidOperationException

הכנסת הנתונים ל Counter:

מה שנותר לנו כעת זה להכניס נתונים ל Counter..

לדוגמא בעת ריצת האפליקציה הבאה:

      Stopwatch s = new Stopwatch();
    
            for (int i = 0; i < 1000; i++)
            {
                s.Start();
                
                // simply increment the counters
                _TotalOps.Increment();

                _OpsPerSecond.Increment();

                Thread.Sleep(300+10*i);

                // increment the timer 
                _AvgDuration.IncrementBy(s.ElapsedTicks);
                _AverageDurationBase.IncrementBy(1);
            
                s.Stop();
                s.Reset();
            }      
יהיה אפשר לראות ב Perfmon את הסטטיסטיקה הבאה (למשל):


תהנו!

יום חמישי, 19 בספטמבר 2013

Best practices for Instrumenting Biztalk solution

בהמשך לפוסט האחרון שלי על Tracing high performance applications , היכן אנחנו צריכים לכתוב Tracing?

Pipeline Components
  • בעת כניסה ויציאה מהקומפוננטה.
  • פירוט של ה runtime exception שנזרק.
  • מדידת הזמן שלקח לפונקצייה מסוימת לרוץ/ במקרים מסוימים נרצה לדעת כמה לקח לכל הקומפוננטה לרוץ
  • ביצועTrace למצבים מסוימים בתוך הקומפוננטה (בתוך if) או למשתנה מסוים (שיעזור לנו בהמשך במקרה של בחינת תקלות)
Biztalk Maps
  • נרצה לדעת ערכים מסוימים שאנחנו רוצים למפות. נבצע באמצעות Scripting functoid. 

Biztalk Orchestrations

ביצוע Trace לאורכסטרציות זהו המפתח למעקב, דיאגנוסטיקה ולפתרון תקלות. זאת במיוחד כי חלק מהאורכסטרציות שלנו הופכות להיות למאוד מסובכות. (גם בעת פיתוח יותר קל לדאבג אורכסטרציות שיש עליהם Tracing)
נבצע:
  • מיד כאשר נכנסים לאורכסטרציה (לאחר receive shape)
  • ביצועTrace למצבים מסוימים בתוך האורכסטרציה (בתוך if) או למשתנה מסוים (שיעזור לנו בהמשך במקרה של בחינת תקלות)
  • פירוט של ה runtime exception שנזרק.
  • מדידת הזמן שלקח ל scope מסויים לרוץ/ במקרים מסוימים נרצה לדעת כמה לקח לכל האורכסטרציה לרוץ
  • לפני שיוצאים מהאורכסטרציה (לפני ה exit point)
מעקב מהנה!


יום ראשון, 18 באוגוסט 2013

Tracing High Performance Applications - Event Tracing for Windows - ETW

ביצוע instrumentation אפליקטיבי הוא חלק הכרחי במערכות תוכנה סבוכות. כתיבה עשירה של tracing ו logging מאפשר לנו לעקוב אחר ההתנהגות של האפליקציה שלנו - במיוחד בסביבות פרודקשין שבו אנחנו לא יכולים להתחיל לדאבג את המערכות.
זה מתחיל להיות קריטי עבור מערכות High-performance (למשל אפליקציות BIZTALK)שבהם כל כתיבה ללוג או ל Trace יכול להפחית את ביצועי המערכת.

האופציות השכיחות לכתוב ל Trace (באמצעות קומפוננטות צד שלישי):

  1. System.Diagnostic.Trace.Component
  2. Enterprise Library Tracing Component
  3. Log4Net
אבל:
  • כאשר נפתח את DebugView כדי לצפות בEvent-ים הרבים , הכלי יצרוך כ 85% ממשאבי ה CPU. (הכלי  DebugView מתפקד  כ Debugger  ונרשם ל OUTPUT_DEBUG_STRING_EVENT) כתוצאה מכך יש פגיעה משמעותי ב Performance של האפליקציה. אם מדברים על אפליקציית Biztalk, יתכן מצב שכתוצאה מכך יכנסו לנו תהליכים למצב של Suspended.
  • ביצועים נמוכים-System.Diagnostic.Trace.Component יודע לכתוב כ 2000-8000 כתיבות בשניה (כאשר DebugView Event Capture במצב Enabled)
  • כל שינוי ב tracing configuration דורש אתחול של האפליקציה (לרוב..). בפרויקט ביזטוק נדרש לאתחל את ה Host Instance- לפעמים זה לא מתאפשר בסביבת ה Prod.
לכן, הפתרון המומלץ:

Event Tracing For Windows-ETW- תשתית לוגים סקאלאבילית שמערכת ההפעלה מספקת לנו (מאז שנת 2000). החסרון זה שלא קל לעבוד מול התשתית הזאת, אך יש המון דוגמאות באינטרנט כיצד לעבוד מול התשתית.

אז מה התשתית מספקת לנו:
  • בניסוי שנערך על מחשב עם 4 ליבות, התשתית כתבה כ 1.4 מיליון!!! של Trace event לשניה!
  • כל שינוי ב tracing configuration לא דורש אתחול של האפליקציה
  • צריכה נמוכה של משאבי CPU- התשתית משתמשת בפתרון Buffering שממומש על גבי הKernel, שכותב באופן א-סינכרוני לדיסק ע"י Thread נפרד.
להלן בדיקות הביצועים שנעשו על גבי כל 4 התשתיות שציינתי:

הבדיקה נעשתה ע"י הרצה של מטודה שכותבת ל Trace רשומה אחת, ואותה הריצו בלולאה 100,000 פעמים. בסיום ההרצה התשתיות היו צריכות לספק קובץ לוג מבוסס Text


אפשר לראות בבירור את השימוש ב ETW עבור מערכות High-Performance.


תהנו!

יום חמישי, 1 באוגוסט 2013

בדיקות אוטומאטיות באמצעות BizUnit

אצלי בצוות כבר עובדים המון זמן עם Biztalk. יש אפליקציות Biztalk שקיימות כבר כמה שנים. לאחר כל שינוי באפליקציה מבצעים בדיקות כמובן. לרוב אלו בדיקות אינטגרציה עם מערכות אחרות.
עד היום, הבדיקות בוצעו באופן ידני (למשל: לוקחים קובץ לדוגמא, זורקים לתיקייה, ובודקים לאן הקובץ נשלח, ואת התוכן שלו). תתארו כמה זמן הטסט הידני הזה לוקח..

לאחרונה התחלנו להשתמש עם ה BizUnit ככלי בדיקות אוטומאטי עבור כל בדיקות האינטגרציה.
כמובן שלוקח קצת זמן ללמוד להשתמש, ולהתחיל לכתוב טסטים מקצה לקצה, אך לאורך זמן המאמץ ישתלם.
BizUnit זוהי פלטפורמה לבדיקות דקלרטיבית חינמית לבדיקות Black-Box אשר מיועדת בעיקר לבדיקות אפליקציות ה Biztalk, אך כמובן אפשר להשתמש בכלי עבור כל אפליקציה שאנחנו יוצרים ( תהיו יצירתיים..).

הסבר להורדה, התקנה, ודוגמאות אפשר להוריד מכאן.
את מגוון הבדיקות שאפשר לעשות עם הפלטפורמה אפשר למצוא כאן

את הבדיקות אפשר לכתוב כקובץ XML, או לכתוב כקוד #C. היתרון הגדול שאפשר להשתמש ולמחזר קומפוננטות בדיקה שונות שכתבתם במהירות, עבור טסטים שונים.

בהתחלה נתקלתי במספר קשיים בכתיבת הבדיקות, אך לאחר מחקר קטן באינטרנט אפשר היה למצוא פתרון.
אני אציג סנריו קצר לבדיקות באמצעות הפלטפורמה, שמהווה (גם לי) בסיס ליצירת בדיקות חדשות.

את כל ה step נכתוב כמובן בתוך פונקציית בדיקה רגילה- [TestMethod]

שלב 1-נגדיר TestCase שהוא בעצם מייצג את הסנריו שלנו:

public class CarShop
 var totalItemsTest = new TestCase { Name = "my test case" };
 string dbConnectionString = @"Data...";

שלב 2- מחיקת כל הקבצים (במקרה שלנו קבצי XML) מהנתיב שבו נוצר הקובץ שאנחנו רוצים לבדוק (למשל לאחר ה send-port)


DeleteStep deleteStep = new DeleteStep();
            deleteStep.FilePathsToDelete = new Collection<string> { @"c:\out\*.xml" };
            totalItemsTest.SetupSteps.Add(deleteStep);

שלב 3- מחיקת רשומות מה DB שלנו (במקרה הזה SQL SERVER)


     var dbTestStep = new BizUnit.TestSteps.Sql.DbQueryStep();
            dbTestStep.ConnectionString = dbConnectionString;

            dbTestStep.SQLQuery = new SqlQuery
            {
                RawSqlQuery = @"Delete From SomeDb.dbo.table; select 1;"
            };

            dbTestStep.NumberOfRowsExpected = 1;
            dbTestStep.DelayBeforeCheck = 10;

            totalItemsTest.ExecutionSteps.Add(dbTestStep);


שלב 4- בעצם כאן מתחילים את הבדיקה עצמה- העברת קובץ מתיקיית מקור ליעד ( לתיקייה שהביזטוק יקרא ממנה)
     
            var moveTeststep = new CreateStep();
            moveTeststep.CreationPath = @"c:\destination";

            var dataLoader = new FileDataLoader();

            dataLoader.FilePath = @"c:\fromLocation";
            moveTeststep.DataSource = dataLoader;

            totalItemsTest.ExecutionSteps.Add(moveTeststep);

שלב 5- כעת הקובץ עבר לתיקייה, והביזטוק (או המערכת) תקרא את הקובץ, נמתין זמן מסויים (עד שהמערכת שלנו "תעבד" את הבקשה לפני שנמשיך עם שאר הבדיקות:

var waitTestStep = new DelayStep();
            waitTestStep.DelayMilliSeconds = 10000;
            totalItemsTest.ExecutionSteps.Add(waitTestStep);


שלב 6- נתשאל רשומה ב DB ונבדוק אם קיבלנו מה שציפינו לקבל:

     var dbSelectTestStep = new DbQueryStep();

            dbSelectTestStep.ConnectionString = dbConnectionString;
            dbSelectTestStep.SQLQuery = new SqlQuery
            {
                RawSqlQuery = @"select top(1) from tbl where cell='11' "
            };

            dbSelectTestStep.NumberOfRowsExpected = 1;
            dbSelectTestStep.DbRowsToValidate = new Collection();
            DbRowToValidate row = new DbRowToValidate();
            DbCellToValidate cell = new DbCellToValidate();
            cell.ColumnName = "colName";
            cell.ExpectedValue = "12312";
            row.Cells.Add(cell);
            dbSelectTestStep.DbRowsToValidate.Add(row);
            totalItemsTest.ExecutionSteps.Add(dbSelectTestStep);

שלב 7- נבדוק שקיבלנו קובץ XML אחד בתיקיית ה OUTPUT שלנו, ושהוא בנוי על פי סכמה מסויימת:


      var validatingFileStep = new FileReadMultipleStep()
            {
                DirectoryPath=@"c:\out",
                SearchPattern="*.xml",
                Timeout=1000,
                ExpectedNumberOfFiles=1
            };

            var validation = new XmlValidationStep();
            var schemaSummary = new SchemaDefinition
            {
                XmlSchemaPath=@"c:\schema.xsd",
                XmlSchemaNameSpace="http://Schemas/namespace"
            };

            validation.XmlSchemas.Add(schemaSummary);

שלב 8- נבדוק שקיבלנו ב XML ערכים כפי שציפינו - על ידי XPATH


      var xpathStep = new XPathDefinition() 
            {
            
            Description="type",
            XPath="/*[local-name().....",
            Value="expected value"
            };



            validatingFileStep.SubSteps.Add(validation);
totalItemsTest.ExecutionSteps.Add(validatingFileStep);

ובשלב האחרון פשוט מריצים את כל ה STEP'S:

       var bizUnit = new BizUnit.BizUnit(totalItemsTest);
            bizUnit.RunTest();

כעת כל ה STEP ירוצו אחד אחרי השני.
אם אחד ה Step ים יכשל, יופיע Exception מסודר, שמפרט בדיוק באיזה Step הבדיקה נכשלה.

בדיקות מהנות!