January 16, 2012

Visual Studio 2008 Macros

I recently tried Macros in Visual Studio 2008. You can do various customization to the IDE using the Macro editor. For an example:

  • Google the selected word.
  • Display a message box after a build is completed.
  • Record an operation and play it.
  • Etc...
As an example I will be covering first two from the above.

To create Macros, go to Tools menu select Macros and then select Macros IDE. This will open up a new program (Microsoft Visual Studio Macros) and using the editor you can add macros in to the IDE.


If you haven’t used the Macro IDE before, the first thing you’ll notice is that it resembles the regular VS.NET IDE, except the Macro IDE is used solely for the creation, editing, and compiling of macros that can be used within your Visual Studio IDE.


Open up the MyMacros project and see if it already contains an EnvironmentEvents module. 

Add the following code snippet by adding a new VB code file.

    Private Const BROWSER_PATH As String = "C:\Program Files (x86)\Mozilla Firefox\firefox.exe"

    Sub SearchGoogle()
        Dim cmd As String
        cmd = String.Format("{0} http://www.google.com/search?hl-en&q={1}", BROWSER_PATH, DTE.ActiveDocument.Selection.Text)
        Shell(cmd, AppWinStyle.NormalFocus)
    End Sub


This will spawn a new Firefox Window and send the selected text to Google.


You can access Visual Studio environment (EnvDTE) instance in a Macro to do various things such as customize pre-build event or a post-build event. EnvDTE is an assembly-wrapped COM library containing the objects and members for Visual Studio core automation.


For an example the following code displays an message box after a build is completed. For this you hook into the OnBuildDone event the same way you did for the project build complete.



    Private Sub BuildEvents_OnBuildDone(ByVal Scope As EnvDTE.vsBuildScope, ByVal Action As EnvDTE.vsBuildAction) Handles BuildEvents.OnBuildDone
        'Alert that we finished building!
        If DTE.MainWindow.WindowState = EnvDTE.vsWindowState.vsWindowStateMinimize Or DTE.MainWindow.Visible = False Then
            System.Windows.Forms.MessageBox.Show("Build is complete!", _
                IO.Path.GetFileNameWithoutExtension(DTE.Solution.FullName), _
                MessageBoxButtons.OK, _
                MessageBoxIcon.Information, MessageBoxDefaultButton.Button1, _
                MessageBoxOptions.DefaultDesktopOnly)
        End If
    End Sub


These are some pretty simple examples, but you have the power of the .NET Framework in your hands for any of these events so you are limited only by what you can come up with to handle.


The post build event will automatically run after a build if the IDE is invisible or minimized. However to run the Google macro, you can assign this Macro to a toolbar by going into Tools, Customize, Command, Macros and drag and dropping SearchGoogle function.




January 10, 2012

Sprint Burndown Charts

This article shows how to draw sprint burndown charts using Dojo. Here is the JavaScript code:

// Some globals.
defaultTitleColor = "darkblue";
defaultXAxisTitle = "Days";
defaultYAxisTitle = "Remaining / Verified Tasks";
dojoInitialized = false;
chartArgs = new Array();
holidayDatesArray = new Array();

var themeClasses = new Object();

// Some utility functions.
$.getScript = function(url, callback, cache){
 $.ajax({
   type: "GET",
   url: url,
   success: callback,
   dataType: "script",
   cache: cache
 });
};

var addBusinessDays = function(d, n) {
 d = new Date(d.getTime());
 var day = d.getDay();
 d.setDate(d.getDate() + n + (day === 6 ? 2 : + !day) + (Math.floor((n - 1 + (day % 6 || 1)) / 5) * 2));
 return d;
}

var setDayValues = function (seriesIndex, days) {
 for (var i = 0; i < days; i++) {
  this.data.setValue(i, seriesIndex, "" + (i + 1));
 }
}

var datesEqual = function(a, b) {
   return (!(a > b || b > a));
}

var setDayLabels = function(startDate, days, dayLabels) {
 var daysSkiped = 0;
 for (var i = 0; i < days + 1; i++) {
  if (i == 0) {
   dayLabels.push({value: i + 1, text: ""});
   continue;
  }
  
  // We need to skip the weekends.
  var date = addBusinessDays(new Date(startDate), i - 1 + daysSkiped);
  
  // We need to skip the holidays.
  var isHoliday = false;
  while (holidayDatesArray != null && !isHoliday) {
   for (var j = 0; j < holidayDatesArray.length; j++) {
    if (datesEqual(date, holidayDatesArray[j])) {
     isHoliday = true;
     break;
    }
   }
   
   if (isHoliday) {
    date = addBusinessDays(date, 1);
    daysSkiped++;
    isHoliday = false;
   } else {
    break;
   }
  }

  var label = date.toDateString();
  label = label.substring(label.indexOf(" ") + 1, label.lastIndexOf(" "));
  dayLabels.push({value: i + 1, text: label});
 }
}

// A function to initialize the chart.
var initChart = function(chart, tensionValue, animationDuration, startDate, days, useDayLabels, xAxisTitle, yAxisTitle, theme) {
 var dayLabels = [];
 var xAxisLabelsRotationValue = 0;
 
 // Set the x axis labels. We do this only if the client tell us to do so.
 // Otherwise the default values will be used, which is 1, 2, 3, ...
 if (useDayLabels) {
  xAxisLabelsRotationValue = -15;
  setDayLabels(startDate, days, dayLabels);
 }
 
 // Set the chart theme.
 chart.setTheme(themeClasses[theme.toLowerCase()]);

 // Set the chart plots and axies.
 chart.addPlot("default", {
  type: "Lines", 
  animate: { 
   duration: animationDuration, 
   easing: dojox.fx.easing.linear}, 
  markers: true, 
  tension: tensionValue}
 );
 
 chart.addAxis("x", {
  minorTicks: true, 
  minorTickStep: 1, 
  stroke: "gray", 
  font: "normal normal normal 8pt Tahoma", 
  title: xAxisTitle, 
  titleOrientation: "away", 
  titleFontColor: "gray", 
  labels: dayLabels, 
  maxLabelSize: 80, 
  rotation: xAxisLabelsRotationValue}
 );
 
 chart.addAxis("y", {
  vertical: true, 
  includeZero: true, 
  minorTicks: false, 
  stroke: "gray", 
  font: "normal normal normal 8pt Tahoma", 
  title: yAxisTitle, 
  titleFontColor: "gray"}
 );
}

// Sets the values for ideal burndown.
var getIdealBurndownData = function(days, tasksRemainingInitValue) {
 var values = new Array();
 for (var i = 0; i < days + 1; i++) {
  values.push(tasksRemainingInitValue - (tasksRemainingInitValue / days) * i);
 }
 return values;
}

// Sets the values for a data series.
var plotChartData = function(chart, args) {
 chart.addSeries("Ideal Breakdown", getIdealBurndownData(args.days, args.tasksRemaining[0]));
 chart.addSeries("Remaining Tasks", args.tasksRemaining);
 chart.addSeries("Verified Tasks", args.tasksVerified);
}

// Initializes the holiday dates array from a string array.
var stringsToDates = function(strings, dates) {
 for (var i = 0; i < strings.length; i++) {
  dates.push(new Date(strings[i]));
 }
}

// Draws the burndown chart.
var drawBurndownInternal = function(args) {
 // Initialize the variables.
 var titleColor = args.titleColor;
 if (!titleColor) titleColor = defaultTitleColor;
 
 var chartName = "burndownChart";
 if (args.chartName) chartName = args.chartName;
 
 var legendName = "burndownChartLegend";
 if (args.legendName) legendName = args.legendName;
 
 var xAxisTitle = args.xAxisTitle;
 if (!xAxisTitle) xAxisTitle = defaultXAxisTitle;
 
 var yAxisTitle = args.yAxisTitle;
 if (!yAxisTitle) yAxisTitle = defaultYAxisTitle;

 var tensionValue = "S";
 if (args.curvedLines == false) tensionValue = "";
 
 var animationDuration = 500;
 if (args.animate == false) animationDuration = 0;
 
 var useDayLabels = true;
 if (!isNaN(args.useDayLabels)) useDayLabels = args.useDayLabels;
 
 if (args.holidays != null && args.holidays.length > 0) {
  stringsToDates(args.holidays, holidayDatesArray);
 }
 
 var theme = "WatersEdge";
 if (args.theme) theme = args.theme;

 // Create the chart object.
 var burndownChart = new dojox.charting.Chart2D(chartName, {
  title: args.title,
  titlePos: "top",
  titleGap: 25,
  titleFont: "normal normal bold 12pt Arial",
  titleFontColor: titleColor});

 initChart(burndownChart, tensionValue, animationDuration, args.startDate, args.days, useDayLabels, xAxisTitle, yAxisTitle, theme);
 plotChartData(burndownChart, args);

 // Set tooltips, magnification, etc...
 new dojox.charting.action2d.Magnify(burndownChart, "default", {scale: 2});
 //new dojox.charting.action2d.Tooltip(burndownChart, "default");
 //new dojox.charting.action2d.Highlight(burndownChart, "default");
 
 // Render the chart.
 burndownChart.render();

 // Create the legend.
 var legend = new dojox.charting.widget.Legend({
   chart: burndownChart, 
   horizontal: false, 
   style: "font-family:arial;color:black;font-size:10px;"}, 
  legendName);
}

// The function gets called after the Dojo is initialized.
var onDojoInitialized = function() {
 dojoInitialized = true;
 
 themeClasses["wetland"] = dojox.charting.themes.Wetland;
 themeClasses["tufte"] = dojox.charting.themes.Tufte;
 themeClasses["shrooms"] = dojox.charting.themes.Shrooms;
 themeClasses["watersedge"] = dojox.charting.themes.WatersEdge;
 
 for (var i = 0; i < chartArgs.length; i++) {
  drawBurndownInternal(chartArgs[i]);
 }
}

// The clients call this function to draw the burndown charts.
var drawBurndown = function(args) {
 if (!dojoInitialized) {
  $.getScript('http://ajax.googleapis.com/ajax/libs/dojo/1.6.0/dojo/dojo.xd.js', function() {
   dojo.require("dojo.fx");
   dojo.require("dojox.fx.easing");
   dojo.require("dojox.charting.Chart2D");
   dojo.require("dojox.charting.action2d.Magnify");
   dojo.require("dojox.charting.action2d.Tooltip");
   dojo.require("dojox.charting.action2d.Highlight");
   dojo.require("dojox.charting.widget.Legend");
   dojo.require("dojox.charting.themes.WatersEdge");
   dojo.require("dojox.charting.themes.Wetland");
   dojo.require("dojox.charting.themes.Tufte");
   dojo.require("dojox.charting.themes.Shrooms");
   
   // This is a HACK!
   // When I tried 2 charts on the same page, only one chart was rendered.
   // I guess this is because of the second request is not going into onDojoInitialized function.
   // So as a hack, we cache the arguments and then after the Dojo is initialized we start drawing the charts.
   chartArgs.push(args);
   
   dojo.ready(function() { onDojoInitialized(); });
  });
 } else {
  drawBurndownInternal(args);
 }
};

Here is the HTML code:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<body style="font-family: Arial;border: 0 none;">
 <script src="http://code.jquery.com/jquery-latest.js"></script>
 <script type="text/javascript" src="burndownChart.js"></script>
 <table border="0"><tr valign="top">
  <td><div id="burndownChart" style="width: 800px; height: 400px;"></div></td>
  <td><div style="width: 100px; margin-top:100px; border: 1px solid #C8C8C8; padding: 5px; background-color: rgba(255, 255, 221, 0.8);"><div id="burndownChartLegend" ></div>
  </div></td>
 </tr></table>

 <script type="text/javascript">
  drawBurndown({
   title: "Linking 2.0 Sprint 9 Burdown Chart",
   startDate: "2011/10/24",
   days: 15,
   tasksRemaining: [50,49,49,53,57,54,33,31,28,28,27,24,23,23,23,16],
   tasksVerified: [0,1,1,5,6,6,7,13,16,16,17,20,21,21,21,30]
  });
 </script> 
</body>

</html>

December 22, 2011

How to improve VC++ project build time

How to improve VC++ project build time

Introduction

Microsoft Visual C++ can compile code very quickly, if it is setup properly. There are few basic things to look at:

  • Precompiled headers
  • Pragma once
  • File level parallel building
  • Project level parallel building

Build Statistics

Before we go into the details of each of these items let's see how we can enable some build time statistics. Please note that all my examples and notes are related to Visual Studio 2008 Professional Edition.

Measuring the build time

In VC++ you can get build time by going into Tools > Options > Projects and Solutions > VC++ Project Settings > Build Timing. Set this value to true. This will print the complete build time to the output window. Enable this before you try to improve the build time, then you will be able to measure the performance correctly.

Getting the list of files get included into each compilation unit

In VC++ you can get the list of files gets included into the each compilation unit (CPP) by enabling "Show Includes" in the project properties by going into C/C++ > Advanced > Show Includes. Set this value to true. This will print the included files with the compiler output.

Precompiled Headers

It is often desirable to avoid recompiling a set of header files, especially when they introduce many lines of code and the primary source files that #include them are relatively small. The C++ compiler provides a mechanism for, in effect, taking a snapshot of the state of the compilation at a particular point and writing it to a disk file before completing the compilation; then, when recompiling the same source file or compiling another file with the same set of header files, it can recognize the snapshot point, verify that the corresponding precompiled header (PCH) file is reusable, and read it back in. Under the right circumstances, this can produce a dramatic improvement in compilation time; the trade-off is that PCH files can take a lot of disk space.

Enabling Precompiled Headers

The precompiled-header options are /Yc (Create Precompiled Header File) and /Yu (Use Precompiled Header File). Use /Yc to create a precompiled header. Select /Yu to use an existing precompiled header in the existing compilation. These settings can be changed by going into Project Properties > Configuration Properties > Precompiled Headers.

The idea here is to use one CPP file as the source for creating the pre compiled header and the rest of the CPP files are use the created header file. First you need a header file that every source file in your project will include. In VC++ this is typically called as "stdafx.h". If you do not have one, create it and also the corresponding CPP file which contains a single line to include the "stdafx.h". Change the rest of the CPP files in your project to include the "stdafx.h" file. This include has to be the first include statement (as the first non-comment thing that they do). If you forget to include your precompiled header file then you will get the following error message: "fatal error C1010: unexpected end of file while looking for precompiled header directive."

Now, change the project properties to generate / use pre compiled headers. Make sure you select "All Configurations" so that your fixes will affect both debug and release builds of your project. Select the project properties, go to the C/C++ tab, precompiled headers category. Select "Use precompiled header file" and type in the name of your precompiled header file (stdafx.h). Select "stdafx.cpp" properties, go to the C/C++ tab, precompiled headers category. With "All Configurations" selected, and select "Create precompiled header file", and type in the name of your precompiled header file.

What should be in the stdafx.h?

The precompiled header file should include the big header files which slow down your builds. Possible candidates are STL header files.

The rule of thumb is not to include any header files from your project, since if you modify those header file, the whole project will be rebuilt.

Build with Precompiled Headers

All set now. Try out the build and gather the build statistic and compare with the original.

#pragma once

In the C and C++ programming languages, #pragma once is a non-standard but widely supported preprocessor directive designed to cause the current source file to be included only once in a single compilation. Using #pragma once instead of include guards will typically increase compilation speed since it is a higher-level mechanism; the compiler itself can compare filenames without having to invoke the C preprocessor to scan the header for #ifndef and #endif.

If the header contains only a guard, when compiling the included file (CPP) the included file has to be fully loaded to scan for #ifdef and #endif statements. To improve the time and to support all compilers it is recommended to use both header guards and #pragma once statements in the header files.

For an example, take a header file that you use / include many times form your project. Remove the pragma once from it (if it exists) and compile the project by enabling "Show Includes" discussed previously. Check the number of times the included file gets loaded when the project gets compiled.

File Level Parallel Building

Visual Studio 2008 can take advantage of systems that have multiple processors, or multiple-core processors. A separate build process is created for each available processor. The /MP option can reduce the total time to compile the source files on the command line. The /MP option causes the compiler to create one or more copies of itself, each in a separate process. Then these copies simultaneously compile the source files. Consequently, the total time to build the source files can be significantly reduced. The /MP option can be given to the project settings in the IDE by going into project properties C/C++ > Command Line > Additional Options. The option takes an optional argument to specify the number of processors / cores. If you omit this argument, the compiler retrieves the number of effective processors on your computer from the operating system, and creates a process for each processor. This option does not work with if you have used /Yc (Create Precompiled Header File) or Show Includes. The compiler will warn you, but you can simply ignores it. Otherwise omit the /MP option for /Yc enabled source files (stdafx.cpp) and disable Show Includes option after figuring out the include file fiasco.

With this option along if you are running your compiler on a quad core system, you can gain 3 - 4 times compile time improvement.

Project Level Parallel Building

The number of projects that can build concurrently in the IDE is equal to the value of the Maximum number of parallel project builds property. For example, if you build a solution that comprises several projects while this property is set to 2, then up to two projects will build concurrently at a time. To enable this you need to go into Tools > Options > Projects and Solutions > Build and Run. Set the "maximum number of parallel project to build" to number of processors / cores.

This option only allows you to build non-dependent projects in parallel.

Points of Interest

By doing all of these we were able to improve the build time of a project by 10 times. Hope this article will help you too.

October 25, 2011

Visual Studio add-in: ReviewMyCode

Recently I've written a simple Visual Studio add-in called ReviewMyCode. Here is the summary:

Peer code reviews are a proven way to improve software quality. ReviewMyCode helps you overcome some barriers to this best practice with a simple Visual Studio 2008 add-in that fits your process. The team members can contribute to the review process without leaving the development environment.

The add-in works well within a team development environment by integrating the review process with the SVN source control management system.

July 22, 2011

SVN - Who has created the branch?

I wanted to get the author name who has created a branch in our SVN. With some doco read, I was able to find the answer.
svn log -r 1:HEAD --limit 1 --stop-on-copy
With --limit we can limit the number of results we get from the query, in this case we need only one.

When creating a branch we copy from another SVN location, thus --stop-on-copy comes handy here.

SVN Sparse Directories (partial checkouts)

SVN 1.5 introduces a feature called sparse directories (or shallow checkouts or partial checkouts) that allows you to easily check out a working copy or a portion of a working copy.

How do we do it? On the command prompt use the following commands:
# svn co --depth empty
# pushd
(Assume: foo is a folder in SVN. With the following command you will not get any leaf folders or files.)
# svn up --depth empty foo
(Assume: bar is a sub-folder in foo. With this you will get all from foo/bar)
#svn up foo/bar
(With the following command, you get the only the latest copies of the checked out folders from SVN)
# popd
# svn up
(You can also perform a SVN commit at this level)

Performing SVN checkout (co) for each and every folder you require from SVN is not the way to go, since you cannot do a global SVN update or a commit.



July 21, 2011

HOWTO: Pass more than 10 arguments to a DOS batch file

Batch files can only handle parameters %0 to %9
%0 is the program name as it was called,
%1 is the first command line parameter,
%2 is the second command line parameter,
and so on till %9.

The batch file's limitation to handle parameters up to %9 only can be overcome by using SHIFT.

After NT 4, we do not need to use SHIFT...
FOR %%A IN (%*) DO (
      rem Now your batch file handles %%A instead of %1
)
%* will always expand to all original parameters, sadly. But you can use the following snippet of code to build a variable containing all but the first parameter:

:my_function
rem skip the first arag
shift
set args=%1
:my_loop
shift
if [%1]==[] goto my_end_loop
set args=%args% %1
goto :my_loop
:my_end_loop
rem Do somthing with the args 
goto :EOF

You can call this inside your batch file:
call :my_function %*