Thursday, December 11, 2008

Taking some time to keep on learning.

Since I am unemployed and not having anything better to do with my time I decided I would check out "Apache JMeter" by Emily H. Halili (Packt, 2008).

It's a light read and I found that Chapter 7 (Advanced Features) was the most helpful to myself as I am not a n00b to JMeter. However, I felt that Chapter 7 would have been better served if BeanShell processing would have been tackled. I've found from my load testing experience in the past that at some point the return HTML is gonna have to be sliced and diced and data extracted that cannot be done with a simple RegEx extraction (like what is covered in Chapter 7 of the book).

Other than that, I think that if you are a total n00b to JMeter it isn't half bad as a simple introduction to using JMeter for performance/load testing. There's a lot more to performance/load testing than the book covers such as metrics collection, number crunching, et cetera but the book doesn't purport itself to be the end all be all of explaining performance/load testing so I can't complain.

Three stars.

Wednesday, December 3, 2008

And that job is toast!

Well. Got laid off today with 20 other folks from the Dallas area. Cannot say that I am surprised. Good thing I've been saving up for this possibility. Didn't get to keep the Uberlaptop of Powah (and I wasn't gonna pay the $3700 to keep it).

I don't expect to get another job in December but I have some possibilities lined up in January. What am I gonna do with that time? Hmmmm. Wasn't GTA IV just released? Too bad I don't have a machine capable of playing the PC version and I'm not going to pay for a console while laid off just to play GTA IV.

I have no doubt that my load testing adventure will continue in January.

One thing for sure. The past seven months has been a waste. Thanks a lot, VT. Same back atcha.

Thursday, November 13, 2008

Hey. What's going on in this blog?

Hey. What's going on in this blog?

Not a damn thing.

*sigh*

Not even working on WiX stuff right now. Working on manually deploying with NAnt, which is fine with me as it is proven that it works time after time. I suspect that MercScum is gonna wanna go back to WiX after initial deployment prototyping. I mean, why stick with something that is proven and works, right?

Tuesday, October 28, 2008

Almost there... Stay on target...

Getting closer to getting the first general WiX task done.

I now have WiX creating my user, adding that user to the Administrators group (No comments from the peanut gallery, please!) and then creating a custom AppPool and creating a Web Application that is bound to that custom AppPool with a created VDir. Pretty nifty.

Here is what it looks like so far:



   1:  <Component Id="MySoftwareRequirements" Guid="{SOME-VALID-GUID}">

   2:    <CreateFolder/>

   3:    <util:User Id="MySoftwareUser" Name="MySoftwareUser" Password="supersekrit">

   4:      <util:GroupRef Id="Administrators"/>

   5:    </util:User>

   6:    <iis:WebSite Id          = "DefaultWebSite" 

   7:                 Description = "DefaultWebSite"

   8:                 Directory   = "MySoftwareSubDir">

   9:      <iis:WebAddress Id = "AllUnassigned" Port="80" />

  10:        <iis:WebDirProperties Id                    = "WebVirtualDirProperties"

  11:                              Execute               = "yes"

  12:                              Script                = "yes"

  13:                              Read                  = "yes"

  14:                              WindowsAuthentication = "no"

  15:                              AnonymousAccess       = "yes"

  16:                              AnonymousUser         = "MySoftwareUser"

  17:                              IIsControlledPassword = "no" />

  18:    </iis:WebSite>

  19:    <iis:WebAppPool Id="MySoftware" Identity="other" Name="MySoftware" User="MySoftwareUser"/> 

  20:    <iis:WebVirtualDir Id            = "MySoftwareVDir"

  21:                       Alias         = "MySoftwareVDir" 

  22:                       Directory     = "MySoftwareSubDir" 

  23:                       WebSite       = "DefaultWebSite"

  24:                       DirProperties = "WebVirtualDirProperties">

  25:      <iis:WebApplication Id         = "WebApplication"

  26:                          Name       = "MySoftware"

  27:                          WebAppPool = "MySoftware" />

  28:    </iis:WebVirtualDir>

  29:  </Component>



Of course, you can't see the reference to the Administrators group that is up at the top of the .wxs file under the Package element (see previous post). It is starting to come together.

More WiX stuff: Creating a user and adding them to a group.

Now that I am able to successfully create a new user I need to add that user to the Administrators group. Yeah, I hear you already, "That's a major security issue!" and I would tend to agree but this has been "mandated" by MercScum so there isn't much I can do about it.

Anyhoo. To do it I had do the following.

After the Product and Package element declarations I had to add a Group element, like this:



   1:  <Product Id="{F0C1692F-2D79-4D46-9BDF-DCBAD1DBCDDB}" Language="1033" Manufacturer="SomeManufacturer" Name="SomeProduct" UpgradeCode="{SOME-VALID-GUID}" Version="1.0.0.0">

   2:    <Package Compressed="yes" InstallerVersion="200" />

   3:      <util:Group Id="Administrators" Name="Administrators" />



It took some trial and error to find out where to put the util:Group element. The documentation says that it is a child of the Product element but I couldn't put it directly after. It had to go under the Package element. Good to know!

In my previously declared util:User element I had to nest a GroupRef element so the entire bit of XML reads like this:



   1:  <Component Id="RequiredUser" Guid="{SOME-VALID-GUID}">

   2:    <util:User Id="NewUser" Name="NewUserName" Password="supersekrit">

   3:      <util:GroupRef Id="Administrators"/>

   4:    </util:User>

   5:    <CreateFolder/>

   6:  </Component>



I was able to create the .msi and installed it on a VM and it worked like a charm. Luckily I only banged my head against the WiX brick wall for a few hours this time, unlike the create a new user head-banging. I suspect everything else will start to fall into place now that I am getting more comfortable with WiX and XML in general. Yes, I've managed to be in industry for almost 20 years without having to bow down to the great XML monster but now I am paying the price for being behind the curve. Back in my day we had CSVs and we liked 'em!

Monday, October 27, 2008

Can I has user created by WiX?

Ok. I'm starting to get this stuff figured out.

The User tag is part of WixUtilExtension.dll but it is not enough to simply reference WixUtilExtension on the command line.

You must also add an XML name space reference in the .wxs file. Also, another gotcha. The namespace spelling is different from the WixUtilExtension spelling. D'oh!

I had to add the following:



   1:  <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"

   2:       xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">



Now, the User tag that I created has to have the "util" prefix to let the linker know that the User tag is being referenced for the WixUtilExtension:



   1:  <Component Id="RequiredUser" Guid="{SUPER-DUPER-GUID}">

   2:    <util:User Id="UserStuff" Name="UserStuff" Password="supersekrit" />

   3:    <CreateFolder/>

   4:  </Component>



Notice that I had to add the CreateFolder tag in my User tag to prevent error ICE18. Otherwise, it'll fail and make me cry.

I was able to link with candle with a "-ext WixUtilExtension.dll" reference and the wixobj was created and with the CreateFolder I avoided the dreaded ICE18 error. I was also able to create my .msi and I will test that tomorrow on my VM.

Wasn't that all intuitively obvious?

My thanks go out to the wix-users listserv that pointed out where I was banging my head against the brick wall of wix. Now with this information I should be able to go forward with the other tasks required and be a tad bit more efficient with WiX (Finally!).

My head hurts...

*bangs head against desk*

Search for creating users with WiX and you see the handy dandy User tag.

Go ahead and try that with WiX v3. Doesn't work?

Google the documentation. Aha! I need the sca.wixlib and associated sca*.dll files?

Search for files.

Not shipped with v3?

Hmmmm...

Download WiX v2.

Try using with WiX v3.

"The extension 'sca.wixlib' could not be loaded."

*continue to bang head against desk*

Now off to try this again with WiX v2. Can't use heat to harvest files? Guess I need to re-learn the process with tallow.

*continue to bang head against desk*

Do some more Googling. Hey, automated tasks are in the Wix*Extension.dlls. That's what I thought but couldn't get to load with -ext. Hmmmmm?

Some more Googling and reading of blogs. Oh, you can't just use the Wix*Extension.dll by name. Needs full path. Ok. That's not so bad.

Which Wix*Extension.dll has the task for using with the User tag? Dunno. I'll just copy all Wix*Extension.dll to my subdir and try 'em all.

Oops. WinDifxAppExtension.dll can't be loaded? Remove it from command line.

Oops. WixIsolatedAppExtension.dll can't be loaded. Remove it from command line.

Will by User tag be recognized now? *crosses fingers* Let's give it a try!

8<--------------------------------------------------
C:\SomeProduct\SP\Dist>candle -o SP.wixobj SP.wxs -ext WixDifxAppExtension.dll -ext WixDirectXExtension.dll -ext WixFirewallExtension.dl
l -ext WixGamingExtension.dll -ext WixIIsExtension.dll -ext WixMsmqExtension.dll -ext WixNetFxExtension.dll -ext WixPSExtension.dll
-ext WixSqlExtension.dll -ext WixUIExtension.dll -ext WixUtilExtension.dll -ext WixVSExtension.dll
Microsoft (R) Windows Installer Xml Compiler version 3.0.4318.0
Copyright (C) Microsoft Corporation. All rights reserved.

SP.wxs
C:\SomeProduct\SP\Dist\SP.wxs(461) : error CNDL0005 : The Component element contains an unexpected child element 'User'.
-------------------------------------------------->8

Fsck.

*sigh*

Hey. Wasn't I hired to be the performance SME? Hmmmm. What happened to that?

Friday, October 24, 2008

The pain of the WiX

Much like the use of candles in BDSM play, I am feeling the pain of the WiX wax motif windows installer. But like those candles, I think it'll pay off in the long run.

Once again, into the depths of XML fun! At least it's not XML scripting languages at this point but I am definitely feeling the pain of heat, candle and not seeing the light at the moment. WiX has a very steep learning curve.

So far I've learned how to harvest a bunch of files with heat, link with candle and assemble a .msi with light. With a simple and straightforward project it's actually pretty simple but unfortunately I am not on a simple project.

The first thing that I've found that has burned me is that we have a bunch of duplicated DLLs nested in different subdirs that I want to package as a product and apparently that is a no-no and can't be done. I've had to separate the subdirs generated by MSBuild into two seperate trees with parallel structures and create two .msi packages that when executed create the desired tree.

I banged my head up against that for long enough.

Now I'm trying to figure out how to create a user and that is pretty painful. There is a most excellent older tutorial located here but it doesn't document all the gotchas. I'm trying to find the extra DLL I have to reference via -ext on the command line for candle.exe that recognizes the user tag.

Unlike the nant to MSBuild conversion (which I don't think it was necessary) I figure that learning this WiX stuff will pay off. I mean, if WiX was good enough for MS to deploy SQL Server 2005 and Office 2007 it should do everything I ever want. It's just the learning curve that has to be climbed.

I can really see why deployment/build engineering can be a full time position on a team. I don't want to be a full time build/deployment engineer but I appreciate the knowledge and suspect that it'll pay off in the long run.

Tuesday, October 21, 2008

From MSBuild to WiX

Now that I've converted from NAnt to MSBuild scripts I am now learning the wonderful world of WiX to create .msi install packages. What does this have to do with my performance testing duties? Not a damn thing.

The hired mercenary scum that is running the project now is having do all sorts of things that are not part of our normal activities. I was hired to be a performanc SME and to build up a testing framework which is about 80% complete. My last work on that was over a month ago.

I suspect I shall not see another performance test on this project.

Saturday, October 11, 2008

NAnt versus MSBuild

At work we are currently converting from NAnt to MSBuild scripts as we convert from CruiseControl.NET to TeamCity. Why are we changing from NAnt to MSBuild as we change CI servers? Nobody really knows for sure besides the guy that is running the project basically says, "I've mandated it." That's the only reason. Seems like a silly reason to be to go through all the trouble to rewrite all the scripts.

After working with NAnt and MSBuild I have come to one conclusion: I don't like XML based scripting languages. That's right, you heard it here. They are both a pain. Way too verbose and too much of a PITA. My personal belief is that we should just wrap all the functionality with a perl script and be done with it. :-) Just think about it: Simple if/then/else statements and simple loops without all the XML filler junque and all the dynamic data structures you want without a bunch more XML filler stuff. It's beautiful! Back in my day, you darn whipper snappers, we had .CSV files and liked 'em! And like Ant/NAnt, perl works under both Unix and Win32 (I've been using Win32 for a gazillion years but now that I run a 64-bit OS should I start saying Win32/64? That's a discussion for another time! Now back to my MSBuild and NAnt rant!) so you have multiple platform functionality.

MSBuild is taking me longer to wrap my head around. I don't like that I can create a property inside of a target, but that property is not available until the target where it was created as finished. Makes it a pain to attempt to modularize common calls for database work. And why doesn't MSBuild have a foreach construct? Sure, you can use the metabase accessing `%` to invoke multiple calls but how non-intuitive is that? Argh! I shouldn't have to write a helper function to make multiple calls with the % operator.

Yeah. I stand by my convictions on this one. Junque both of 'em and write a perl wrapper and be happy.

Tuesday, September 30, 2008

Asynchronous Invocation of a Load Test from a Continuous Integration System

Why the fancy pants title? A Microsoft recruiter asked for my resume for some load testing related stuff and my blog is on my resume so I figured I needed some high falutin' titles. Hopefully they don't read anything but the titles. :-)

Anyway...

I'm getting to the point where I need to be able to invoke a load test after the nightly build and automagically run a load test. Now, I know, right now you're saying to yourself, "Hey, a nightly build doesn't constitute a continuous integration system!" And I agree, however, "Continuous Integration System" sounds much more impressive than just "nightly build." ;-)

Back to the important stuff: How am I going to invoke a Load Test on another machine after the nightly build is done? Right now I am working everything out with that super-duper handy utility, psexec from the UberBrains at SysInternals (now part of Microsoft).

I have recently build a stand alone server that I had installed SQL Server 2005, VS2008 TSTE and my custom LoadTestResults database where I store summary information from runs that I will be reporting from. On that server which I will refer to as my Load Test Controller, I setup a machine account user account by the name of TestRunner. During my trial and errors I found that I had to add TestRunner to the Administrators group but I need to go back and play around with that some to see if there is a more secure way to invoke the nightly automagic load test.

On the Load Test Controller I have two batch files: trigger.bat and loadtest.bat.

trigger.bat:

8<----------------------------
@echo off
echo Cry havoc and let slip loose the dogs of loadtest!
start C:\LoadTest\Trigger\loadtest.bat
---------------------------->8

The start command allows the async execution of the second batch file loadtest.bat, where the load test is really started.

loadtest.bat:

8<----------------------------
call "C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\vcvarsall.bat"
cd "C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE\"
MSTest.exe /testcontainer:c:\LoadTest\LoadTests\MyLoadTest\MyLoadTest\MyReallyCoolLoadTest.loadtest
---------------------------->8

I can call trigger.bat from psexec with any automated process that can shell out to the command line or execute a batch file. Here is an example of starting a load test on the Load Test Controller using psexec from the command line:

8<----------------------------
C:\>psexec \\loadtestbox -u testrunner -p testrunner cmd /c c:\loadtest\trigger\trigger.bat

PsExec v1.92 - Execute processes remotely
Copyright (C) 2001-2007 Mark Russinovich
Sysinternals - www.sysinternals.com


Cry havoc and let slip loose the dogs of loadtest!
c:\loadtest\trigger\trigger.bat exited on loadtestbox with error code 0.
---------------------------->8

Even though psexec exit back to the command line, the load test is actually running on my Load Test Controller:



I was having problems getting results to be automagically saved to the Load Test Results Repository. I figured the SQL connection string was stored in the VS2008 solution but I had to open up VS2008 on my Load Test Controller machine and manually add the connection string for my TestRunner profile. After that, the LoadTest database is updated with each execution. w00t!

Now it's off to add the automagic reporting SP calls via osql.

Saturday, September 6, 2008

DHCP problems with Win2k8

Last weekend I brought home the Portable Server of Powah! home to do some work over the weekend and when I fired things up I wasn't getting an IP addressed assigned by the DHCP server on my FreeBSD box that is the gateway to the intarweb here at the house.

I thought that very odd and when I checked /var/log/dhcpd.log it appeared that a request was going out for an IP address and being sent back but was never accepted by Win2k8. How odd...

Here is what I saw in the logs:

8<---------------------------------
Sep 6 14:42:09 firewall dhcpd: DHCPDISCOVER from 10:80:f4:6f:0c:23 (auswipelaptop) via xl0
Sep 6 14:42:10 firewall dhcpd: DHCPOFFER on 10.0.0.204 to 10:80:f4:6f:0c:23 (auswipelaptop) via xl0
Sep 6 14:42:13 firewall dhcpd: DHCPDISCOVER from 10:80:f4:6f:0c:23 (auswipelaptop) via xl0
Sep 6 14:42:13 firewall dhcpd: DHCPOFFER on 10.0.0.204 to 10:80:f4:6f:0c:23 (auswipelaptop) via xl0
Sep 6 14:42:22 firewall dhcpd: DHCPDISCOVER from 10:80:f4:6f:0c:23 (auswipelaptop) via xl0
Sep 6 14:42:22 firewall dhcpd: DHCPOFFER on 10.0.0.204 to 10:80:f4:6f:0c:23 (auswipelaptop) via xl0
Sep 6 14:42:39 firewall dhcpd: DHCPDISCOVER from 10:80:f4:6f:0c:23 (auswipelaptop) via xl0
Sep 6 14:42:39 firewall dhcpd: DHCPOFFER on 10.0.0.204 to 10:80:f4:6f:0c:23 (auswipelaptop) via xl0
--------------------------------->8

There should have been an DHCPACK after the first two messages. Now this really confused me as I have had the Portable Server of Powah! home many times and had never had any problems getting an IP address before.

But, through playing around I found out that the wireless interface could get an IP address lickitysplit and being the slacker that I am, I kept on trucking with the wifi so that I could get my work done over the holiday.

I once again brought home the Portable Server of Powah! and once again, no IP address. This made me very sad as I wanted to have a wired connection to use the Gigabit goodness, that and it was just odd that I couldn't get an IP address.

I bowed down before the Great Google and queried it's great knowledge and came across this KB. Here is the problem in a nutshell: "Windows Vista cannot obtain an IP address from certain routers or from certain non-Microsoft DHCP servers"

If you follow the directions of the KB the problem can be fixed but they leave out a very important part of the puzzle. Basically, you drill down into the registry to where the various TCP/IP settings are stored and you have to find the GUID of the interface. Since you don't have "friendly" interface names like xl0, xl1 (FreeBSD interface drivers for 3Com NICs) or even friendlier Linux names like eth0, eth1, etc this becomes quite a chore.

Here is what I was looking at:



At this point I'm doing a Kelly-esque "What the hell?" Which one of these GUIDs represents my physical NIC and which one represents my virtual NIC that Hyper-V uses? Do I modify the entry for the physical NIC or the Hyper-V NIC? Confusion abounds!

I manually assigned an IP address and found the GUID entry that represented the Hyper-V virtual NIC and made the changes in the Registry that the above mentioned KB required.

I closed regedit, attempted to do a ipconfig /release && ipconfig /renew and watched the /var/log/dhcpd.log as Win2K8 tried to get an IP address with no love. Then I remembered, this is Windows...

I reboot the machine and sure enough, as it is firing up I see this entry come across /var/log/dhcpd.log:

8<-------------------------------
Sep 6 14:44:48 firewall dhcpd: DHCPDISCOVER from 10:80:f4:6f:0c:23 (auswipelaptop) via xl0
Sep 6 14:44:49 firewall dhcpd: DHCPOFFER on 10.0.0.204 to 10:80:f4:6f:0c:23 (auswipelaptop) via xl0
Sep 6 14:44:49 firewall dhcpd: DHCPREQUEST for 10.0.0.204 (10.0.0.1) from 10:80:f4:6f:0c:23 (auswipelaptop) via xl0
Sep 6 14:44:49 firewall dhcpd: DHCPACK on 10.0.0.204 to 10:80:f4:6f:0c:23 (auswipelaptop) via xl0
------------------------------->8

Hazzah! It works!

Apparently during an update something got tweaked and I could no longer pull an IP address from the FreeBSD dhcpd and had to apply this KB hack to get things to work. Or it could have been when I found how to get the Client for Microsoft Networks working with Hyper-V again. No matter, something got tweaked and now it is fixed which is good 'nuff for me. I'm easy like that.

Friday, September 5, 2008

Extracting Perfmon data from the VS2008 Load Test Repository

Ok. You are performing load tests with VS2008 TSTE and you've enabled the logging of perfmon metrics in your test by tweaking the "Timing Details Storage" property of the Run Settings in your load test. Every 5 seconds the perfmon metrics you specified are being stored to the load test repository and after the run you get some metrics. That's great and all but what if you want more detailed statistics such as average AND standard deviation.

Or better yet! You want at that data to generate graphs of the metrics over the duration of the test. Sure, you can do this by firing up good 'ol Perfmon and logging the values or you can just get them from your load test repository with some SQL. Why have Perfmon logging when VS2008 already does this for you?

I have a need for just such an action and I've been working on the SQL statements to extract the data I need. It's not complete, but this is a major step forward to allowing me to finish up the SPs I want to collect the data dynamically for consumption by managers and other QA folks.

Looking at the handy dandy schema of the load test repository (located here for all to gander) I have created some SQL statements that take care of the leg work for me.



   1:  SELECT LoadTestPerformanceCounterInstance.InstanceId, 

   2:         LoadTestPerformanceCounterCategory.CategoryName, 

   3:         LoadTestPerformanceCounter.CounterName, 

   4:         LoadTestPerformanceCounterInstance.InstanceName, LoadTestPerformanceCounterCategory.MachineName, 

   5:         AVG(LoadTestPerformanceCounterSample.ComputedValue) AS AverageValue,

   6:         stdev(LoadTestPerformanceCounterSample.ComputedValue) as StdDev,

   7:         count(LoadTestPerformanceCounterSample.ComputedValue) as Count

   8:  FROM  LoadTestPerformanceCounterCategory INNER JOIN

   9:                 LoadTestPerformanceCounter INNER JOIN

  10:                 LoadTestPerformanceCounterInstance ON LoadTestPerformanceCounter.LoadTestRunId = LoadTestPerformanceCounterInstance.LoadTestRunId AND 

  11:                 LoadTestPerformanceCounter.CounterId = LoadTestPerformanceCounterInstance.CounterId ON 

  12:                 LoadTestPerformanceCounterCategory.CounterCategoryId = LoadTestPerformanceCounter.CounterCategoryId AND 

  13:                 LoadTestPerformanceCounterCategory.LoadTestRunId = LoadTestPerformanceCounter.LoadTestRunId INNER JOIN

  14:                 LoadTestPerformanceCounterSample ON LoadTestPerformanceCounterInstance.LoadTestRunId = LoadTestPerformanceCounterSample.LoadTestRunId AND 

  15:                 LoadTestPerformanceCounterInstance.InstanceId = LoadTestPerformanceCounterSample.InstanceId

  16:  WHERE (LoadTestPerformanceCounter.LoadTestRunId = 83)

  17:  GROUP BY LoadTestPerformanceCounterCategory.CategoryName, LoadTestPerformanceCounter.CounterName, LoadTestPerformanceCounterInstance.InstanceName, 

  18:                 LoadTestPerformanceCounterInstance.InstanceId, LoadTestPerformanceCounterCategory.MachineName

  19:  ORDER BY LoadTestPerformanceCounterInstance.InstanceId



In the above example, the reference to LoadTestRunId is hard coded to a previous run that I have in my results repository. For all my other SPs I use the GUID of the test and will convert the above SQL statement to reference GUID all in good time. The query above returns results like this:



   1:  0     .NET CLR Memory     Gen 0 heap size                             w3wp     10.0.0.11     944824777.209431     185400315.203616     721

   2:  1     .NET CLR Memory     Large Object Heap size                      w3wp     10.0.0.11     86994000.5298197     29625345.9051784     721

   3:  2     .NET CLR Memory     # Gen 0 Collections                         w3wp     10.0.0.11     132.608876560333     69.5129454689118     721

   4:  3     .NET CLR Memory     Gen 2 heap size                             w3wp     10.0.0.11     197119053.001387     78824416.0154383     721

   5:  4     .NET CLR Memory     Allocated Bytes/sec                         w3wp     10.0.0.11     63521791.4160888     91335026.9218956     721

   6:  5     .NET CLR Memory     # Gen 2 Collections                         w3wp     10.0.0.11     29.5104022191401     12.1351287581502     721

   7:  6     .NET CLR Memory     Promoted Memory from Gen 0                  w3wp     10.0.0.11     42540355.2815534     13356693.0175676     721

   8:  7     .NET CLR Memory     # Induced GC                                w3wp     10.0.0.11     0                    0                    721

   9:  8     .NET CLR Memory     Gen 0 Promoted Bytes/Sec                    w3wp     10.0.0.11     418557.084366331     1120132.98732717     721

  10:  9     .NET CLR Memory     Promoted Memory from Gen 1                  w3wp     10.0.0.11     21740368.0721221     25126733.8606902     721

  11:  10    .NET CLR Memory     # GC Handles                                w3wp     10.0.0.11     3750.73370319001     216.191892870829     721

  12:  11    .NET CLR Memory     # Gen 1 Collections                         w3wp     10.0.0.11     70.0693481276005     35.4698332819129     721

  13:  12    .NET CLR Memory     Gen 1 heap size                             w3wp     10.0.0.11     62394980.9237171     31034211.4233198     721

  14:  13    .NET CLR Memory     Finalization Survivors                      w3wp     10.0.0.11     8217.1040221914      3024.02383909951     721

  15:  14    .NET CLR Memory     Promoted Finalization-Memory from Gen 0     w3wp     10.0.0.11     11415345.6768377     11434973.2174913     721

  16:  15    .NET CLR Memory     % Time in GC                                w3wp     10.0.0.11     3.48545611573799     6.73310861817717     721



The first column is the InstanceId of the perfmon counter and the information for the counter is split up into three separate columns (CategoryName, CounterName and InstanceName) but the column that ties them all together is the InstanceId.

Ok. That's fine and dandy but what about graphing the metrics over the duration of the load test?

Well, that's another SQL query I wrote that is in the alpha stage of development. In the above example I displayed 15 out of 466 metrics that were logged. Let's choose to graph the .NET CLR Memory Gen 2 heap size for w3wp. This particular metric happens to be InstanceId #3 this time around and we can use it in the SQL statement below to extract the data that can eventually be used to graph the results.



   1:  SELECT LoadTestPerformanceCounterInstance.InstanceName,

   2:         LoadTestPerformanceCounterSample.ComputedValue

   3:  FROM  LoadTestPerformanceCounterInstance INNER JOIN

   4:        LoadTestPerformanceCounterSample ON LoadTestPerformanceCounterInstance.LoadTestRunId = LoadTestPerformanceCounterSample.LoadTestRunId AND 

   5:        LoadTestPerformanceCounterInstance.InstanceId = LoadTestPerformanceCounterSample.InstanceId

   6:  WHERE (LoadTestPerformanceCounterInstance.LoadTestRunId = 83) AND (LoadTestPerformanceCounterInstance.InstanceId = 3)

   7:  ORDER BY LoadTestPerformanceCounterSample.TestRunIntervalId



This query yields the results of:



   1:  w3wp    0

   2:  w3wp    0

   3:  w3wp    96

   4:  w3wp    3231264

   5:  w3wp    2.170597E+07

   6:  w3wp    2.170597E+07

   7:  w3wp    2.170597E+07

   8:  w3wp    2.976022E+07

   9:  w3wp    2.976022E+07

  10:  w3wp    2.976022E+07

  11:  w3wp    2.976022E+07

  12:  w3wp    4.127677E+07

  13:  w3wp    4.127677E+07

  14:  w3wp    4.127677E+07



and so on and so on until all 722 metrics are displayed. We can take those 722 metrics and copypasta into Excel and generate a quick and dirty graph:



Viola! Quick and dirty graph from Excel!

I'll need to change the column output to show more details on the metrics to display all three columns to better identify the perfmon metric and also display the time that the metric was taken.

After I do all that I'll need to write some code to generate graphs on the fly. I'm thinking that this project on SourceForge will fit the bill nicely and there seems to be a bunch of examples out on the 'net how to use ZedGraph to it's fullest.

Oh yeah! Almost forgot. I need to double check the results between the first query and the individual information for InstanceId 3 metric (the .NET CLR Gen 2 heap size for w3wp). The first query output says the average value is 197119053.001387. Take the average of output that I did the copypasta into Excel gonkulates to an average of 197119053.4. The difference between the two? A measly 0.355. I'm thinking that is good 'nuff for government work.

Now I just need to finish up the queries and get them into SPs and integrate them with the other code that I've written that does automagic before/after comparison of transaction response times (more on that at a later date).

Tuesday, September 2, 2008

Using NAnt custom task to wrap SDC Task to create VDir

I got my VDir code working, but there are some caveats that have to be watched, I've found.



   1:  using System;

   2:  using System.Text;

   3:  using NAnt.Core;

   4:  using NAnt.Core.Attributes;

   5:  using Microsoft.Sdc.Tasks;

   6:  using Microsoft.Build.Utilities;

   7:  using Microsoft.Build.Framework;

   8:  namespace Auswipe.NAntTasks.VDirTasks {

   9:    # region Delete VDir

  10:    [TaskName("deletevdir")]

  11:    public class DeleteVDir : NAnt.Core.Task {

  12:      private string vDirToDelete;

  13:      private string machineName;

  14:      private string webSiteName;

  15:      [TaskAttribute("vdirtodelete", Required = true)]

  16:      [StringValidator(AllowEmpty = false)]

  17:      public string VDirToDelete {

  18:        get { return vDirToDelete; }

  19:        set { vDirToDelete = value; }

  20:      }

  21:      [TaskAttribute("machinename", Required = true)]

  22:      [StringValidator(AllowEmpty = false)]

  23:      public string MachineName {

  24:        get { return machineName; }

  25:        set { machineName = value; }

  26:      }

  27:      [TaskAttribute("websitename", Required = true)]

  28:      [StringValidator(AllowEmpty = false)]

  29:      public string WebSiteName {

  30:        get { return webSiteName; }

  31:        set { webSiteName = value; }

  32:      }

  33:      protected override void ExecuteTask() {

  34:        Project.Log(Level.Info, "Auswipe.NAntTasks.VDirTasks : The following AppPool will be deleted: '" + vDirToDelete + "'");

  35:        try {

  36:          Microsoft.Sdc.Tasks.Web.WebSite.DeleteVirtualDirectory delVDirObject = new Microsoft.Sdc.Tasks.Web.WebSite.DeleteVirtualDirectory();

  37:          delVDirObject.MachineName          = machineName;

  38:          delVDirObject.VirtualDirectoryName = vDirToDelete;

  39:          delVDirObject.WebSiteName          = webSiteName;

  40:          if (delVDirObject.Execute()) {

  41:            Project.Log(Level.Info, "Auswipe.NAntTasks.VDirTasks : The VDir '" + vDirToDelete + "' was successful deleted.");

  42:          } else {

  43:            Project.Log(Level.Error, "Auswipe.NAntTasks.VDirTasks : ERROR: The VDir '" + vDirToDelete + "' was NOT deleted but an exception was not thrown.");

  44:          };

  45:        } catch (Exception e) {

  46:          Project.Log(Level.Error, "Auswipe.NAntTasks.VDirTasks : VDir deletion failed with the resulting exception:");

  47:          Project.Log(Level.Error, e.ToString());

  48:        };

  49:      }

  50:    }

  51:    #endregion

  52:    # region Create VDir

  53:    [TaskName("createvdir")]

  54:    public class CreateVDir : NAnt.Core.Task {

  55:      private string vDirName;

  56:      private string webSiteName;

  57:      private string appPoolName;

  58:      private string machineName;

  59:      private string fullPath;

  60:      [TaskAttribute("apppoolname", Required = true)]

  61:      [StringValidator(AllowEmpty = false)]

  62:      public string AppPoolName {

  63:        get { return appPoolName; }

  64:        set { appPoolName = value; }

  65:      }

  66:      [TaskAttribute("vdirname", Required = true)]

  67:      [StringValidator(AllowEmpty = false)]

  68:      public string VDirName {

  69:        get { return vDirName; }

  70:        set { vDirName = value; }

  71:      }

  72:      [TaskAttribute("website", Required = true)]

  73:      [StringValidator(AllowEmpty = false)]

  74:      public string WebSite {

  75:        get { return webSiteName; }

  76:        set { webSiteName = value; }

  77:      }

  78:      [TaskAttribute("machinename", Required = true)]

  79:      [StringValidator(AllowEmpty = false)]

  80:      public string MachineName {

  81:        get { return machineName; }

  82:        set { machineName = value; }

  83:      }

  84:      [TaskAttribute("path", Required = true)]

  85:      [StringValidator(AllowEmpty = false)]

  86:      public string FullPath {

  87:        get { return fullPath; }

  88:        set { fullPath = value; }

  89:      }

  90:      protected override void ExecuteTask() {

  91:        Project.Log(Level.Info, "Auswipe.NAntTasks.VDirTasks : The following VDir will be created: '" + vDirName + "'");

  92:        try {

  93:          Microsoft.Sdc.Tasks.Web.WebSite.CreateVirtualDirectory createVDirObject = new Microsoft.Sdc.Tasks.Web.WebSite.CreateVirtualDirectory();

  94:          createVDirObject.AppCreate            = true;

  95:          createVDirObject.AppPoolId            = appPoolName;

  96:          createVDirObject.MachineName          = machineName;

  97:          createVDirObject.VirtualDirectoryName = vDirName;

  98:          createVDirObject.WebSiteName          = webSiteName;

  99:          createVDirObject.Path                 = fullPath;

 100:          if (createVDirObject.Execute()) {

 101:            Project.Log(Level.Info, "Auswipe.NAntTasks.VDirTasks : The VDir '" + vDirName + "' was successfuly created.");

 102:          } else {

 103:            Project.Log(Level.Error, "Auswipe.NAntTasks.VDirTasks : ERROR: The VDir '" + vDirName + "' was NOT created but an exception was not thrown.");

 104:          };

 105:        } catch (Exception e) {

 106:          Project.Log(Level.Error, "Auswipe.NAntTasks.VDirTasks : VDir creation failed with the resulting exception:");

 107:          Project.Log(Level.Error, e.ToString());

 108:        };

 109:      }

 110:    }

 111:    #endregion

 112:  }



A caveat that I've found though is that even though the invoked SDC Task might run into a problem during the .Execute call, .Execute still returns a true so my NAnt task doesn't know if the process has ran into a problem.

In the example below, I'm trying to create a VDir with an AppPool that doesn't exist:

8<-------------------------------------------
NAnt 0.85 (Build 0.85.2478.0; release; 10/14/2006)
Copyright (C) 2001-2006 Gerry Shaw
http://nant.sourceforge.net

Buildfile: file:///C:/customtask/default.build
Target framework: Microsoft .NET Framework 2.0
Target(s) specified: test-task

[loadtasks] Scanning assembly "Auswipe.NAntTasks.VDirTasks" for extensions.
[loadtasks] Scanning assembly "Auswipe.NAntTasks.AppPoolTasks" for extensions.

test-task:

[echo] Creating VDir
Auswipe.NAntTasks.VDirTasks : The following VDir will be created: 'someVDir'
Creating virtual directory "someVDir".
A task error has occured.
Message = Exception has been thrown by the target of an invocation.
VirtualDirectoryName = someVDir
Path = c:\somewebsite
MachineName = Auswipel-rfs2we
WebSiteName = Default Web Site
WebAppName =
AppPoolId = AppPoolDoesNotExist
AppCreate = True
AnonymousUserName =
AnonymousUserPassword =
UncUserName =
UncPassword =
AuthFlags =
AccessFlags =

at System.DirectoryServices.DirectoryEntry.Invoke(String methodName, Object[] args)
at Microsoft.Sdc.Tasks.Configuration.Web.VirtualDirectory.AppCreate3() in c:\projects\codeplex\sdctasks\Solutions\Main\Tasks\Configuration\Web\VirtualDirectory.cs:line 324
at Microsoft.Sdc.Tasks.Web.WebSite.CreateVirtualDirectory.InternalExecute() in c:\projects\codeplex\sdctasks\Solutions\Main\Tasks\Web\WebSite\CreateVirtualDirectory.cs:line 315
at Microsoft.Sdc.Tasks.TaskBase.Execute() in c:\projects\codeplex\sdctasks\Solutions\Main\Tasks\TaskBase.cs:line 66
Element not found. (Exception from HRESULT: 0x80070490)

PseudoEngineException
at Microsoft.Sdc.Tasks.PseudoBuildEngine.LogErrorEvent(BuildErrorEventArgs eventArgs) in c:\projects\codeplex\sdctasks\Solutions\Main\Tasks\PseudoBuildEngine.cs:line 77
at Microsoft.Build.Utilities.TaskLoggingHelper.LogError(String subcategory, String errorCode, String helpKeyword, String file, Int32 lineNumber, Int32 columnNumber, Int32 endLineNumber, Int32 endColumnNumber, String message, Object[] messageArgs)
at Microsoft.Build.Utilities.TaskLoggingHelper.LogError(String message, Object[] messageArgs)
at Microsoft.Sdc.Tasks.TaskBase.Execute() in c:\projects\codeplex\sdctasks\Solutions\Main\Tasks\TaskBase.cs:line 95
Auswipe.NAntTasks.VDirTasks : VDir creation failed with the resulting exception:
System.ApplicationException: PseudoEngineException
at Microsoft.Sdc.Tasks.PseudoBuildEngine.LogErrorEvent(BuildErrorEventArgs eventArgs) in c:\projects\codeplex\sdctasks\Solutions\Main\Tasks\PseudoBuildEngine.cs:line 77
at Microsoft.Build.Utilities.TaskLoggingHelper.LogError(String subcategory, String errorCode, String helpKeyword, String file, Int32 lineNumber, Int32 columnNumber, Int32 endLineNumber, Int32 endColumnNumber, String message, Object[] messageArgs)
at Microsoft.Build.Utilities.TaskLoggingHelper.LogErrorFromException(Exception exception, Boolean showStackTrace, Boolean showDetail, String file)
at Microsoft.Build.Utilities.TaskLoggingHelper.LogErrorFromException(Exception exception, Boolean showStackTrace)
at Microsoft.Sdc.Tasks.TaskBase.Execute() in c:\projects\codeplex\sdctasks\Solutions\Main\Tasks\TaskBase.cs:line 99
at Auswipe.NAntTasks.VDirTasks.CreateVDir.ExecuteTask()

BUILD SUCCEEDED - 2 non-fatal error(s), 0 warning(s)

Total time: 0.2 seconds.
------------------------------------------->8

You would think that .Execute should return a False for the call but it doesn't. Notice the verbage of "2 non-fatal error(s)" so it appears that the SDC Task does not treat the non-existant Applicaiton Pool as a fatal error. Good to know!

Going back and looking at the IIS Manager we see this for the create VDir:



Notice how the Application Pool is set to "<Invalid Application Pool>."

So, the VDir was created, despite the lack of a valid Application Pool. That's something to be aware of when testing out NAnt tasks that wrap SDC Task functionality.

Sunday, August 31, 2008

Hey, whaddya know? It works!

I finished the first part of my task to create a custom NAnt task that utilizes the SDC Tasks that were originally built for MSBuild. What I am interested in is deleting and creating custom AppPools for IIS as well as deleting and creating vdirs bound to custom AppPools and not DefaultAppPool.

I did some quick and dirty coding today and came up with this first pass:



   1:  using System;

   2:  using System.Text;

   3:  using NAnt.Core;

   4:  using NAnt.Core.Attributes;

   5:  using Microsoft.Sdc.Tasks;

   6:  using Microsoft.Build.Utilities;

   7:  using Microsoft.Build.Framework;

   8:  namespace Auswipe.NAntTasks.AppPoolTasks {

   9:    # region DeleteAppPool

  10:    [TaskName("deleteapppool")]

  11:    public class DeleteAppPool : NAnt.Core.Task {

  12:      private string appPoolName;

  13:      private string machineName;

  14:      [TaskAttribute("apppoolname", Required = true)]

  15:      [StringValidator(AllowEmpty = false)]

  16:      public string AppPoolName {

  17:        get { return appPoolName; }

  18:        set { appPoolName = value; }

  19:      }

  20:      [TaskAttribute("machinename", Required = true)]

  21:      [StringValidator(AllowEmpty = false)]

  22:      public string MachineName {

  23:        get { return machineName; }

  24:        set { machineName = value; }

  25:      }

  26:      protected override void ExecuteTask() {

  27:        Project.Log(Level.Info, "Auswipe.NAntTasks.AppPoolTasks : The following AppPool will be deleted: '" + appPoolName + "'");

  28:        try {

  29:          Microsoft.Sdc.Tasks.Web.DeleteAppPool.DeleteAppPool delAppPoolObject = new Microsoft.Sdc.Tasks.Web.DeleteAppPool.DeleteAppPool();

  30:          delAppPoolObject.AppPoolName = appPoolName;

  31:          delAppPoolObject.MachineName = machineName;

  32:          if (delAppPoolObject.Execute()) {

  33:            Project.Log(Level.Info, "Auswipe.NAntTasks.AppPoolTasks : The AppPool '" + appPoolName + "' was successful deleted.");

  34:          } else {

  35:            Project.Log(Level.Error, "Auswipe.NAntTasks.AppPoolTasks : ERROR: The AppPool '" + appPoolName + "' was NOT deleted but an exception was not thrown.");

  36:          };

  37:        } catch (Exception e) {

  38:          Project.Log(Level.Error, "Auswipe.NAntTasks.AppPoolTasks : AppPool deletion failed with the resulting exception:");

  39:          Project.Log(Level.Error, e.ToString());

  40:        };

  41:      }

  42:    }

  43:    #endregion

  44:    # region Create AppPool

  45:    [TaskName("createapppool")]

  46:    public class CreateAppPool : NAnt.Core.Task {

  47:      private string appPoolName;

  48:      private string userName;

  49:      private string passWord;

  50:      private string userDomain;

  51:      private string machineName;

  52:      [TaskAttribute("apppoolname", Required = true)]

  53:      [StringValidator(AllowEmpty = false)]

  54:      public string AppPoolName {

  55:        get { return appPoolName; }

  56:        set { appPoolName = value; }

  57:      }

  58:      [TaskAttribute("username", Required = true)]

  59:      [StringValidator(AllowEmpty = false)]

  60:      public string UserName {

  61:        get { return userName; }

  62:        set { userName = value; }

  63:      }

  64:      [TaskAttribute("password", Required = true)]

  65:      [StringValidator(AllowEmpty = false)]

  66:      public string PassWord {

  67:        get { return passWord; }

  68:        set { passWord = value; }

  69:      }

  70:      [TaskAttribute("machinename", Required = true)]

  71:      [StringValidator(AllowEmpty = false)]

  72:      public string MachineName {

  73:        get { return machineName; }

  74:        set { machineName = value; }

  75:      }

  76:      protected override void ExecuteTask() {

  77:        Project.Log(Level.Info, "Auswipe.NAntTasks.AppPoolTasks : The following AppPool will be created: '" + appPoolName + "'");

  78:        try {

  79:          Microsoft.Sdc.Tasks.Web.AppPool.Create createAppPoolObject = new Microsoft.Sdc.Tasks.Web.AppPool.Create();

  80:          createAppPoolObject.AppPoolName  = appPoolName;

  81:          createAppPoolObject.IdentityType = "SpecifiedUserAccount";

  82:          createAppPoolObject.Identity     = userName;

  83:          createAppPoolObject.Password     = passWord;

  84:          createAppPoolObject.MachineName  = machineName;

  85:          if (createAppPoolObject.Execute()) {

  86:            Project.Log(Level.Info, "Auswipe.NAntTasks.AppPoolTasks : The AppPool '" + appPoolName + "' was successfuly created.");

  87:          } else {

  88:            Project.Log(Level.Error, "Auswipe.NAntTasks.AppPoolTasks : ERROR: The AppPool '" + appPoolName + "' was NOT created but an exception was not thrown.");

  89:          };

  90:        } catch (Exception e) {

  91:          Project.Log(Level.Error, "Auswipe.NAntTasks.AppPoolTasks : AppPool creation failed with the resulting exception:");

  92:          Project.Log(Level.Error, e.ToString());

  93:        };

  94:      }

  95:    }

  96:    #endregion

  97:  }



After I created my Auswipe.NAntTasks.AppPoolTasks.dll I had to copy both Auswipe.NAntTasks.AppPoolTasks.dll and Microsoft.Sdc.Tasks.dll to the subdir where I put my default.build that I've been playing with so that the code could properly execute and create my AppPool.

Here is my default.build script for NAnt:

8<-----------------------------------------


   1:   

   2:  <?xml version="1.0"?>

   3:  <project name="Task Test" default="test-task" basedir="." xmlns="http://nant.sf.net/release/0.86-beta1/nant.xsd">

   4:   

   5:    <loadtasks assembly="Auswipe.NAntTasks.AppPoolTasks.dll" />

   6:   

   7:    <target name="test-task">    

   8:      <echo message="Deleting AppPool."/>

   9:      <deleteapppool apppoolname = "AuswipeAppPool"

  10:                     machinename = "Auswipel-rfs2we"

  11:       />

  12:   

  13:      <echo message="Create AppPool."/>

  14:      <createapppool apppoolname = "AuswipeAppPool"

  15:                     username    = "AuswipeAppPoolUser"

  16:                     password    = "supersekrit"

  17:                     machinename = "Auswipel-rfs2we"

  18:      />

  19:    </target>                                   

  20:      

  21:  </project>


----------------------------------------->8

And here is the output from the execution:

8<-----------------------------------------


   1:  C:\customtask>nant /f:default.build test-task

   2:  NAnt 0.85 (Build 0.85.2478.0; release; 10/14/2006)

   3:  Copyright (C) 2001-2006 Gerry Shaw

   4:  http://nant.sourceforge.net

   5:   

   6:  Buildfile: file:///C:/customtask/default.build

   7:  Target framework: Microsoft .NET Framework 2.0

   8:  Target(s) specified: test-task

   9:   

  10:  [loadtasks] Scanning assembly "Auswipe.NAntTasks.AppPoolTasks" for extensions.

  11:   

  12:  test-task:

  13:   

  14:       [echo] Deleting AppPool.

  15:  Auswipe.NAntTasks.AppPoolTasks : The following AppPool will be deleted: 'AuswipeAppPool'

  16:  Auswipe.NAntTasks.AppPoolTasks : The AppPool 'AuswipeAppPool' was successful deleted.

  17:       [echo] Create AppPool.

  18:  Auswipe.NAntTasks.AppPoolTasks : The following AppPool will be created: 'AuswipeAppPool'

  19:  Creating app pool "AuswipeAppPool".

  20:  Auswipe.NAntTasks.AppPoolTasks : The AppPool 'AuswipeAppPool' was successfuly created.

  21:   

  22:  BUILD SUCCEEDED

  23:   

  24:  Total time: 0.1 seconds.


----------------------------------------->8

Here we see a screen shot of the system before NAnt execution:



Now I execute the NAnt script and re-fresh the IIS manager view of AppPools and lo-and-behold! It worked!



I checked the properties of the newly created AppPool and we see that it has the credentials I specified in the default.build. Hazzah!



Now the next step is to go forth and code the deletion and creation of vdirs with the SDC Tasks.

Nifty!

Friday, August 29, 2008

Creating my own custom NAnt tasks in C#

One of the things that I find myself doing when not crunching numbers as a performance enginerd is learning the ways of the Buildmiester. We use NAnt for deploying our code from the build server to a target test machine and UAT test environment.

During the process we use NAnt to delete and create vdirs under IIS with mkiisdir which is handy dandy and all but it doesn't fully get the job done as some of the vdirs we need should not belong to DefaultAppPool. And also unfortunately it appears that NAntContrib doesn't have all the tools that I need to delete and create a custom AppPool that I need to create with a special local username/password.

I found the command line code to create the AppPool that I want and I was just going to wrap the execution of the command line stuff with an <exec> task but I still couldn't find how to create a vdir on the command line and have it binding to my custom AppPool that I wanted.

This morning after Scrum another coder working on a sister project that uses MSBuild instead of NAnt suggested that I take a gander at the SDC Tasks for MSBuild up on CodePlex. After pulling down the latest and greatest SDC Tasks it appears that I can delete, then create my custom AppPool and then delete/create a customer vdir bound to my custom AppPool. How great is that?

Except that SDC Tasks was designed for MSBuild and we are already heavily into NAnt. Bummer, right? Well. I know that NAnt has a <msbuild> task but going from NAnt into MSBuild that would reference a custom MSBuild script and pass the params around seemed like a real PITA, not to mention a documentation nightmare. But then again, I really want this functionality of the SDC Tasks.

What do I do? What do I do?

After consulting the Great Google, I decided that it was time to "Man up, Nancy Boy!" as one of the female trainers at my gym would say (actually shout during a middle of a grueling spin class with the grunting and sweating and the pain!) and learn how to code my own custom NAnt task and build a custom wrapper for the SDC stuff so that I could invoke the functionality from within my NAnt script.

After consulting some blogs and some pain and suffering I've been able to create a basic useless NAnt task. I haven't written the wrapper code yet, but I think that'll be simple compared to actually getting a custom NAnt task up and running.

This is what I did to create my very own useless NAnt task.

First, I created a .NET Class project by the name of "NAntCustomAuswipeTasks." It seems to be very important that the assembly name postfix be "Tasks.dll." The second thing is that it is important to use class decorators for specifying various things that NAnt needs to get the job done. Two of those being "TaskName" and "TaskAttribute."

Another important thing that I learned is that when calling the task from inside the NAnt script that the task name is case sensitive to the decorator string.

Below is my code for my useless NAnt task:



   1:  using System;

   2:  using NAnt.Core;

   3:  using NAnt.Core.Attributes;

   4:  namespace NAntCustomAuswipeTasks {

   5:    [TaskName("auswipetask")]

   6:    public class AuswipeTask : Task {

   7:      private string customData;

   8:      [TaskAttribute("customdata", Required = true)]

   9:      [StringValidator(AllowEmpty = false)]

  10:      public string CustomData {

  11:        get { 

  12:          return customData; 

  13:        }

  14:        set { 

  15:          customData = value; 

  16:        }

  17:      }

  18:      protected override void ExecuteTask() {

  19:        Project.Log(Level.Info, "[AuwipeTask] The parameter passed is '" + customData + "'.");

  20:      }

  21:    }

  22:  }



Take notice of the TaskName, TaskAttribute and StringValidator decorators. Very important or otherwise your attempts to call your custom NAnt task will fail. The blog entry that I was learning on didn't have the TaskName decorator and so my call to my task was failing. Very frustrating.

After compiling into my assembly of "NAntCustomAuswipeTasks.dll" I copied the DLL into the same subdir where my NAnt build file resides. In my case, this is a folder off of my Desktop subdir where I am playing around checking to see if it really works. I'm using a default.build file right now just to make things simple:

8<--------------------------------
C:\Users\Auswipe\Desktop\NAntTask>dir
Volume in drive C has no label.
Volume Serial Number is A463-3DC5

Directory of C:\Users\Auswipe\Desktop\NAntTask

08/29/2008 11:44 PM <DIR> .
08/29/2008 11:44 PM <DIR> ..
08/29/2008 11:44 PM 404 default.build
08/29/2008 11:45 PM 4,608 NAntCustomAuswipeTasks.dll
2 File(s) 5,012 bytes
2 Dir(s) 332,392,656,896 bytes free
-------------------------------->8

8<--------------------------------


   1:  <?xml version="1.0"?>

   2:  <project name="Custom NAnt Task Test" default="test-task" basedir="." xmlns="http://nant.sf.net/release/0.86-beta1/nant.xsd">

   3:    <loadtasks assembly="NAntCustomAuswipeTasks.dll" />

   4:    <target name="test-task">

   5:      <echo message="Here goes nothing...." />

   6:      <auswipetask customdata="Killroy was here." />

   7:    </target>

   8:  </project>


-------------------------------->8

And here I am testing out my useless NAnt task:

8<--------------------------------
C:\Users\auswipe\Desktop\NAntTask>nant /f:default.build test-task
NAnt 0.85 (Build 0.85.2478.0; release; 10/14/2006)
Copyright (C) 2001-2006 Gerry Shaw
http://nant.sourceforge.net

Buildfile: file:///C:/Users/auswipe/Desktop/NAntTask/default.build
Target framework: Microsoft .NET Framework 2.0
Target(s) specified: test-task

[loadtasks] Scanning assembly "NAntCustomAuswipeTasks" for extensions.

test-task:

[echo] Here goes nothing....
[AuwipeTask] The parameter passed is 'Killroy was here.'.

BUILD SUCCEEDED

Total time: 0 seconds.

-------------------------------->8

Notice that I go ahead and specify the default.build file on the command line along with the target despite having the test-task as the defacto target of default.build and knowing full well that NAnt looks for default.build. Why'd I do that? Just because I hate seeing demonstrations of some technology where the author assumes that the readers know all the ins and out of the subject. Sure, I can get the same results with `nant` as I did with `nant /f:default.build test-task` but having been in the RTFM mode before, I can gleam more information from `nant /f:default.build test-task` and learn the rest later. But that is just my own opinion. YMMV, IANAL, ROFLWTFBBQ.

So, now that I've created my useless NAnt task successfully I can work on wrapping the SDC Tasks that I need to make my world (or at least my deploy) a better place. And the less time I'm spending cleaning up after a deploy changing settings is more time I can be sitting on my butt surfing the web, or something like that.

Thursday, August 28, 2008

Interesting blog post about Apdex

Here is an interesting post from all around smart dude, Tim Koopmans at 90kts.com concerning reporting response time to managers and other non-techy folks and it is interesting.

Check it out here.

Basically, it is a way to gonkulate a value and color code scheme to show if the response time for a transaction falls within acceptable limits for the type of transaction.

Checking out http://www.apdex.org/using.html you can see some of the target response times for the Apdex coefficient gonkulations.

This might be a better a cool way to supplement the way that I am planning on comparing before/after tests with Z-score values and a lot easier for management folk.

Thursday, August 14, 2008

w00t! I can keep Hyper-V after all!

I can keep Hyper-V after all! How did I get back Client for Microsoft Networking do you ask?

In my intarweb searchings I came across this blog entry.

Here is the solution in a nutshell.

Blow away any pre-existing virtual NICs with the Hyper-V manager. With your physical NIC go ahead and un-bind all protocols. Once that is done, go back into Hyper-V manager and re-create a virtual NIC and viola, you'll have a virtual NIC with Client for Microsoft Networks bound. Just reboot your machine and it should be good to go.

Back to using Hyper-V for prototyping! Yay!

Thanks, Jeff! You rock!

Thursday, August 7, 2008

Coding like Ike and Tina...

I'm working on a WebTestRequest that changes an address and in the post back the POST data contains a bunch of information that is displayed in the HTML and I need to extract data from text boxes, check boxes and drop down lists. Extracting the textboxes and checkboxes are pretty simple, it is the drop down boxes that are a pain.

The .NET HTML parser treats the HtmlTags that reside inside the SELECT tag separately so I cannot just get the attributes for the select and then slice and dice and get the default selected value for the drop in.

I tried to be "elegant" in my solution and use the HTML Agility Pack and a XPath pulled from FireBug but alas, there was no love to be found. I'm not sure what the exact problem is other than me being a total XPath n00b (I normally use an open source Java project to extract XPaths from XML docs) but something was amiss. After wasting several hours trying to get the elegant solution I decided to be like Ike and Tina and just brute force the solution of identifying the default selected value for the drop down lists. I populate the Context of the WebTestRequest with all the values that are extracted and just reference them by element name in the address change POST ala the HIDDEN1$ that VS2008 does for hidden field extraction and it seems to work pretty good.

Here is the extraction code that I wrote to pull the fields that I needed to reference in the Context:



   1:  void ExtractCustomerDataToContext(object sender, ExtractionEventArgs e) {

   2:    Dictionary<string, string> customerData = new Dictionary<string, string>();

   3:    string lastDropDown = null;

   4:    foreach (HtmlTag tag in e.Response.HtmlDocument.HtmlTags) {

   5:      string type = tag.GetAttributeValueAsString("type");

   6:      if (type != null) {

   7:        lastDropDown = null ;

   8:        if (type == "text" || type == "checkbox") {

   9:          if (tag.GetAttributeValueAsString("checked") != null) {

  10:            if (tag.GetAttributeValueAsString("checked") == "checked") {

  11:              customerData.Add(tag.GetAttributeValueAsString("name"), "on");

  12:            } else {

  13:              customerData.Add(tag.GetAttributeValueAsString("name"), "off");

  14:            };

  15:          } else {

  16:            customerData.Add(tag.GetAttributeValueAsString("name"), tag.GetAttributeValueAsString("value"));

  17:          };

  18:        };

  19:      } else {

  20:        if (tag.GetAttributeValueAsString("class") != null) {

  21:          if (tag.GetAttributeValueAsString("class") == "dropDownListDefault") {

  22:            lastDropDown = tag.GetAttributeValueAsString("name");

  23:          };

  24:        } else {

  25:          if (tag.GetAttributeValueAsString("selected") != null) {

  26:            if (lastDropDown != null) {

  27:              customerData.Add(lastDropDown, tag.GetAttributeValueAsString("value"));

  28:            };

  29:          };

  30:        };

  31:      };

  32:    };

  33:    foreach (KeyValuePair<string, string> kvp in customerData) {

  34:      if (this.Context.ContainsKey("CUSTOMER_UPDATE_DATA." + kvp.Key)) {

  35:        this.Context.Remove("CUSTOMER_UPDATE_DATA." + kvp.Key);

  36:      };

  37:      if (kvp.Value == null) {

  38:        this.Context.Add("CUSTOMER_UPDATE_DATA." + kvp.Key, "");

  39:      } else {

  40:        this.Context.Add("CUSTOMER_UPDATE_DATA." + kvp.Key, kvp.Value);

  41:      };

  42:    };

  43:  }



So, if the previous page HTML has a text field by the name of "txtCustomerAccountNumber" I can simply add that information to the post with:



   1:  changeAddressRequestBody.FormPostParameters.Add("txtCustomerAccountNumber", this.Context["CUSTOMER_UPDATE_DATA.txtCustomerAccountNumber"].ToString());



As long as the information was displayed via HTML in the prior hit, I should be good to go.